/ test / lint / lint-python-mutable-default-parameters.py
lint-python-mutable-default-parameters.py
 1  #!/usr/bin/env python3
 2  #
 3  # Copyright (c) 2019-2022 The Bitcoin Core developers
 4  # Distributed under the MIT software license, see the accompanying
 5  # file COPYING or http://www.opensource.org/licenses/mit-license.php.
 6  
 7  """
 8  Detect when a mutable list or dict is used as a default parameter value in a Python function.
 9  """
10  
11  import subprocess
12  import sys
13  
14  
15  def main():
16      command = [
17          "git",
18          "grep",
19          "-E",
20          r"^\s*def [a-zA-Z0-9_]+\(.*=\s*(\[|\{)",
21          "--",
22          "*.py",
23      ]
24      output = subprocess.run(command, stdout=subprocess.PIPE, text=True)
25      if len(output.stdout) > 0:
26          error_msg = (
27              "A mutable list or dict seems to be used as default parameter value:\n\n"
28              f"{output.stdout}\n"
29              f"{example()}"
30          )
31          print(error_msg)
32          sys.exit(1)
33      else:
34          sys.exit(0)
35  
36  
37  def example():
38      return """This is how mutable list and dict default parameter values behave:
39  
40  >>> def f(i, j=[], k={}):
41  ...     j.append(i)
42  ...     k[i] = True
43  ...     return j, k
44  ...
45  >>> f(1)
46  ([1], {1: True})
47  >>> f(1)
48  ([1, 1], {1: True})
49  >>> f(2)
50  ([1, 1, 2], {1: True, 2: True})
51  
52  The intended behaviour was likely:
53  
54  >>> def f(i, j=None, k=None):
55  ...     if j is None:
56  ...         j = []
57  ...     if k is None:
58  ...         k = {}
59  ...     j.append(i)
60  ...     k[i] = True
61  ...     return j, k
62  ...
63  >>> f(1)
64  ([1], {1: True})
65  >>> f(1)
66  ([1], {1: True})
67  >>> f(2)
68  ([2], {2: True})"""
69  
70  
71  if __name__ == "__main__":
72      main()