[Python-ideas] Documenting Python warts

Steven D'Aprano steve at pearwood.info
Wed Jan 2 01:55:58 CET 2013


On 02/01/13 11:01, Oleg Broytman wrote:
> On Tue, Jan 01, 2013 at 03:17:34PM -0800, alex23<wuwei23 at gmail.com>  wrote:
>> On Jan 2, 8:16 am, Chris Angelico<ros... at gmail.com>  wrote:
>>> It's a little odd what you can and can't do,
>>> until you understand the underlying system fairly well. It's something
>>> that's highly unlikely to change; one of the premises would have to be
>>> sacrificed (or at least modified) to achieve it.
>>
>> By this definition, though, every feature of Python that someone
>> doesn't understand is a wart. For a new user, mutable default
>> parameters is a wart, but once you understand Python's execution&
>> object models, it's just the way the language is.
>>
>> Generally, I find "wart" means "something the user doesn't like about
>> the language even if it makes internal sense".
>
>     What about warts that don't have internal sense? Mutable default
> parameters are just artifacts of the implementation. What is their
> "internal sense"?

They are not artifacts of the implementation, they are a consequence
of a deliberate design choice of Python.

Default values in function definitions are set *once*, when the function
object is created. Only the function body is run every time the function
is called, not the function definition. So whether you do this:

def ham(x=0):
     x += 1
     return x

or this:

def spam(x=[]):
     x.append(1)
     return x


the default value for both functions is a single object created once and
reused every time you call the function.

The consequences of this may be too subtle for beginners to predict, and
that even experienced coders sometimes forget makes it a wart, but it
makes perfect internal sense:

* in Python, bindings ALWAYS occur when the code is executed;

* in Python, "x=<whatever>" is a binding;

* even inside a function definition;

* def is a statement which is executed at run time, not something
   performed at compile time;

* therefore, inside the statement "def spam(x=[]): ..." the binding
   x=[] occurs ONCE ONLY. The same list object is always used for the
   default value, not a different one each time.

Early binding of function defaults should, in my opinion, be preferred
over late binding because:

* given early binding, it is clean to get late binding semantics with
   just one extra line. Everything you need remains encapsulated inside
   the function:

   def spam(x=None):
       if x is None: x = []
       x.append(1)
       return x


* given late binding, it is ugly to get early binding semantics,
   since it requires you to create a separate global "constant"
   for every argument needing an early binding:

   _SPAM_DEFAULT_ARG = []  # Don't touch this!
   def spam(x=None):
       if x is None: x = _SPAM_DEFAULT_ARG
       x.append(1)
       return x




-- 
Steven



More information about the Python-ideas mailing list