Python's Mutable Default Problem
blog.objectmentor.com
blog.objectmentor.com
"the mutable default parameter quirk is an ugly corner worth avoiding"
could also be described as:
"a natural outcropping of python's late binding, "names are references" variable model, and closure mechanisms, which provide a consistency to the language that is often crufted up in others"
I do somewhat agree with the author that this particular functionality should be a "use only when needed" feature. I don't think it should be avoided at all costs tho, because there are times where the mutable default allows for a lot of saved code. In fact in a few cases the code to work around using mutable defaults can get into some serious voodoo because frequently the writer is actually trying to work around the bigger mutable/immutable objects and names are references "issues" in python.
This also reminds me of something I was reading on the front page today about the old 'use the whole language' vs 'simplicity is king' holy war.
My mileage varies.
I'd prefer default parameters to honor referential transparency, whatever hoops the runtime has to jump through to make this happen.
>>> class Evil(object):
def __init__(self):
self.evil = 1
def __add__(self, other):
result = ("%s" % self.evil) + other
self.evil += 1
return result
>>> def referentially_transparent_concat(a, b):
return a + b
>>> e = Evil()
>>> print referentially_transparent_concat(e, "hi")
1hi
>>> print referentially_transparent_concat(e, "hi")
2hi
You can program in a referentially-transparent style with Python, but you'll have to do it by adding your own restrictions to the code you write. Python will not help you with that.One thing Python lacks is the ability to use preceding arguments in defaults, e.g. you cannot do this:
def f(a=3, b=a+1):
return (a + b) / 2
NameError: name 'a' is not defined
Oops. f(b=3)Common Lisp does this right.
def f(a=3, b=None):
b = b or a+1 # or use a more explicit version
return (a + b) / 2- "Let B be one greater than A unless otherwise specified."
- "We have no default value for B. If B has a value, then let B be equal to that value. If B does not have a value, then let B be one greater than A."
I think Python's behaviour is confusing and basically never what anyone actually wants. Regardless of whether or not you can use other params in defaults, the defaults should be evaluated on each call.
hm... that's debatable. The implementation could have just as easily chosen to evaluate the default arguments each time the function is invoked and that decision wouldn't have broken any of the existing mental models of variable binding/closures.
stuff = stuff or []
because if you pass an empty list, you'll get a new one rather than mutating the one you passed. stuff = stuff if stuff is not None else []
is wordy, but at least it's correct. if stuff is None:
stuff = []Dammit, people - in Python, "That's clever" is an insult. Learn the philosophy of a language and you'll be much happier using it.
Especially when that keyword is not necessary and when it breaks standard programmer expectations (if I declare a method with 3 parameters, I should call it with 3, not 2).
foo.bar(baz, sputz)
^1 ^2 ^3
When you write a class method, it has more information available to it than a similar subroutine; thus, another parameter.GvR brings up the deeper reasons for explicit self at http://neopythonic.blogspot.com/2008/10/why-explicit-self-ha... .
Python is doing this totally weird stuff that sits in the middle and that makes no logical sense at all.
The fact that Python forces you to declare the "self" parameter is simply due to the fact that it's old, old, old. Nothing wrong with that, but post rationalizing it by saying it's okay to declare a method with 3 parameters but calling with just 2 is just silly.
It lets you define the name of the this parameter in exactly the same way you pass it.
type Bar(p) =
member x.Foo(bar) = printf "%s %d" bar p
let x = new Bar(10)
x.Foo("Foo bar")
Output: Foo bar 10
And yes, the variables bar and p are type inferred from the %s and %d respectively class Flip(object):
foo=4
def flop(x,num):
x.bar=x.foo+numThe error messages related to `self` in method definitions caused me some confusion when starting out with python. For instance if you define a method with no arguments, calling it results in "TypeError: your_method() takes no arguments (1 given)".
stuff = stuff is not None and stuff or []
That said, the `if stuff is not None:` is the most Pythonic way of doing it.If I were being cavalier and had an extra wish to burn, I'd request
x else y
to mean x unless x is None.That way you get a fresh array each time, and you can even use defaults that depend on previous arguments:
sub integrate(&integrand, $from, $to, $step = ($to - $from) / 100) { ... } import types
def function(item, stuff = lambda: []):
if type(stuff) == types.FunctionType:
stuff = stuff()
stuff.append(item)
print stuff
function(1)
# prints '[1]'
function(2)
# prints '[2]'
In Scala it has a better syntax because of typed function object: trait Map[A, B] {
…
def getOrElse (key: A, default: ⇒ B): B
…
}
the `default` parameter is a function, so when you do getOrElse(someKey, defaultValue)
The `defaultValue` becomes a function that generates the value you put there when called.This idiom is baked into the perl community. It's kind of funny to see a Pythonista deciding it's a good idea. (You can tell it hurts him too).
$foo // "default"
evaluates to "default" if $foo is undefined, and to $foo otherwise (even if it is defined but false). stuff = stuff||[]
since JavaScript doesn't even support default argument values.And since the site's comments seem to be taken over by link spam, is this mention on Hacker News just a clever way to juice the Google rank of said spam?
See if you can determine what this does:
def cbk(match, nb = [0] ):
if len(match.group())==len(nb):
nb[-1] += 1
elif len(match.group())>len(nb):
nb.append(1)
else:
nb[:] = nb[0:len(match.group())]
nb[-1] += 1
return match.group()+' '+('.'.join(map(str,nb)))
str = re.compile('^(#+)',re.MULTILINE).sub(cbk,str) ##
#
To: ## 0.1
# 1
str is builtin, don't use it as a variable name especially if you use it in its original role.Which is very, very messed up (but not the first thing Python messed up).
http://stackoverflow.com/questions/1132941/least-astonishmen...
This would fail to have expected behaviour here:
fill_list = []
stuff = function(info, full_list)
print fill_list
use the if list is None: paradigm
@statics(blah=[])
def foo(normal_args, **kwargs):
# or
def foo(normal_args, blah):
It messes up the idea of looking at the definition to find the function signature.def function(item, stuff): .... blah blah def function(item): function(item, [])