Skip to content

Commit cc40b55

Browse files
author
MarcoFalke
committed
Merge #16726: tests: Avoid common Python default parameter gotcha when mutable dict/list:s are used as default parameter values
e4f4ea4 lint: Catch use of [] or {} as default parameter values in Python functions (practicalswift) 25dd867 Avoid using mutable default parameter values (practicalswift) Pull request description: Avoid common Python default parameter gotcha when mutable `dict`/`list`:s are used as default parameter values. Examples of this gotcha caught during review: * bitcoin/bitcoin#16673 (comment) * bitcoin/bitcoin#14565 (comment) Perhaps surprisingly this is how mutable list and dictionary default parameter values behave in Python: ``` >>> def f(i, j=[], k={}): ... j.append(i) ... k[i] = True ... return j, k ... >>> f(1) ([1], {1: True}) >>> f(1) ([1, 1], {1: True}) >>> f(2) ([1, 1, 2], {1: True, 2: True}) ``` In contrast to: ``` >>> def f(i, j=None, k=None): ... if j is None: ... j = [] ... if k is None: ... k = {} ... j.append(i) ... k[i] = True ... return j, k ... >>> f(1) ([1], {1: True}) >>> f(1) ([1], {1: True}) >>> f(2) ([2], {2: True}) ``` The latter is typically the intended behaviour. This PR fixes two instances of this and adds a check guarding against this gotcha going forward :-) ACKs for top commit: Sjors: Oh Python... ACK e4f4ea4. Testing tip: swap the two commits. Tree-SHA512: 56e14d24fc866211a20185c9fdb274ed046c3aed2dc0e07699e58b6f9fa3b79f6d0c880fb02d72b7fe5cc5eb7c0ff6da0ead33123344e1a872209370c2e49e3f
2 parents 119e97a + e4f4ea4 commit cc40b55

File tree

3 files changed

+58
-2
lines changed

3 files changed

+58
-2
lines changed

test/functional/test_framework/messages.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -803,7 +803,9 @@ def get_siphash_keys(self):
803803
return [ key0, key1 ]
804804

805805
# Version 2 compact blocks use wtxid in shortids (rather than txid)
806-
def initialize_from_block(self, block, nonce=0, prefill_list = [0], use_witness = False):
806+
def initialize_from_block(self, block, nonce=0, prefill_list=None, use_witness=False):
807+
if prefill_list is None:
808+
prefill_list = [0]
807809
self.header = CBlockHeader(block)
808810
self.nonce = nonce
809811
self.prefilled_txn = [ PrefilledTransaction(i, block.vtx[i]) for i in prefill_list ]

test/functional/wallet_importmulti.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,10 @@ def skip_test_if_missing_module(self):
4444
def setup_network(self):
4545
self.setup_nodes()
4646

47-
def test_importmulti(self, req, success, error_code=None, error_message=None, warnings=[]):
47+
def test_importmulti(self, req, success, error_code=None, error_message=None, warnings=None):
4848
"""Run importmulti and assert success"""
49+
if warnings is None:
50+
warnings = []
4951
result = self.nodes[1].importmulti([req])
5052
observed_warnings = []
5153
if 'warnings' in result[0]:
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Copyright (c) 2019 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+
# Detect when a mutable list or dict is used as a default parameter value in a Python function.
8+
9+
export LC_ALL=C
10+
EXIT_CODE=0
11+
OUTPUT=$(git grep -E '^\s*def [a-zA-Z0-9_]+\(.*=\s*(\[|\{)' -- "*.py")
12+
if [[ ${OUTPUT} != "" ]]; then
13+
echo "A mutable list or dict seems to be used as default parameter value:"
14+
echo
15+
echo "${OUTPUT}"
16+
echo
17+
cat << EXAMPLE
18+
This is how mutable list and dict default parameter values behave:
19+
20+
>>> def f(i, j=[], k={}):
21+
... j.append(i)
22+
... k[i] = True
23+
... return j, k
24+
...
25+
>>> f(1)
26+
([1], {1: True})
27+
>>> f(1)
28+
([1, 1], {1: True})
29+
>>> f(2)
30+
([1, 1, 2], {1: True, 2: True})
31+
32+
The intended behaviour was likely:
33+
34+
>>> def f(i, j=None, k=None):
35+
... if j is None:
36+
... j = []
37+
... if k is None:
38+
... k = {}
39+
... j.append(i)
40+
... k[i] = True
41+
... return j, k
42+
...
43+
>>> f(1)
44+
([1], {1: True})
45+
>>> f(1)
46+
([1], {1: True})
47+
>>> f(2)
48+
([2], {2: True})
49+
EXAMPLE
50+
EXIT_CODE=1
51+
fi
52+
exit ${EXIT_CODE}

0 commit comments

Comments
 (0)