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()