Python mutable default function arguments are static

This particular quirk of Python has been discussed in various places before, but I think it bears repeating as it’s different to the behaviour that you might intuitively expect and consequently catches a lot of people out when writing class methods. When declaring a function or a class method, any default arguments are only evaluated at the point when the function is declared, not when the function is called. For mutable default arguments such as lists or dictionaries, this has the effect of making them static. Consider the following example:

class a:
	def __init__( self, b = {} ):
		self.b = b
c = a()
d = a()
print( hex( id( c.b ) ) )
0x7fdf77b72050
print( hex( id( d.b ) ) )
0x7fdf77b72050

As you can see, both instances of class a share the same member dictionary b, as demonstrated by the fact that both of their b members point to the same address.

Having spent a couple of days going over some code with a fine-toothed comb trying to track down the source of a bug and discovering that this was the culprit, I am keen to avoid making it again! To me, it feels counterintuitive and makes default arguments a lot less useful, but them’s the breaks. Below is the recommended way to deal with this situation.

class a:
	def __init__( self, b = None ):
		if b is None:
			self.b = {}
		else:
			self.b = b

Comments

Something to be more generally aware of, of which this is a symptom, is that unlike C/C++ etc, PHP conditional statements do not define a scope at all (only functions have their own scope in PHP).

if (true) {
	$declared_in_if = "Hello world";
}
echo $declared_in_if; // outputs "Hello world"

In C a variable declared in the control statement conditional itself remains in scope for all subsequent blocks of the same control statement (i.e. all subsequent elses of an if)... whereas in PHP not even variables inside the blocks will go out of scope :D

I’m not sure I see that as the cause of this particular issue as it occurs in Python rather than PHP, and I personally expect a constructor to be evaluated when it is called rather than when it is defined. Perhaps that’s just me though! A fairer comparison might be with temporary loop counter variables such as for( $i = 0; $i < count( $array ); ++$i ) { ... } going out of scope when the loop ends, but again that’s a construct that’s rarely used in Python.

Add new comment

(If you're a human, don't change the following field)
Your first name.
(If you're a human, don't change the following field)
Your first name.

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.