6 ms·
Actually, if you understand the way Python is evaluated (dig in, the core is pretty transparent), it's the only behavior that makes sense in this case. It's als
by jsprinkles 14y ago
Actually, if you understand the way Python is evaluated (dig in, the core is pretty transparent), it's the only behavior that makes sense in this case. It's also documented as such[1], so it's quite expected. Default parameters are still just as useful for constants, such as:
def f(x=0, y="foo", z=3.14159):
This, however, is a perfectly Pythonic idiom:
def f(L=None):
if L is None:
L = []
[1]: http://docs.python.org/reference/compound_stmts.html#function-definitions http://docs.python.org/reference/compound_stmts.html#functio...
- swdunlop 14y agoOr just embrace the tao of the tuple: def f(L=()): ...
- ollysb 14y agoWhile it seems logical when you understand what's going on, from a practical point of view I can't see how this would ever be useful. The tradeoff appears to be that the functions are first class objects. I'm not sure what the benefit here is though. Does having them as first class objects allow some useful idioms? (I'm a ruby dev but I'm genuinely curious to know what this allows you to do)
- jsprinkles 14y agoIt's not designed to be useful, it just is. Functions are objects, yes, and the def statement brings them into being and assigns them to the given name in the current scope: >>> def f(x): ... return x + 5 >>> type(f) <type 'function'> >>> dis.dis(f.func_code) 2 0 LOAD_FAST 0 (x) 3 LOAD_CONST 1 (5) 6 BINARY_ADD 7 RETURN_VALUE >>> g = f >>> g(10) 15 >>> g is f True They're just variables in the current scope. If you're quite clever, your brain is already figuring out that has some interesting implications that some libraries use: >>> import socket >>> socket.gethostbyname('www.google.com') '74.125.71.103' >>> socket.gethostbyname = lambda i: '10.0.0.1' >>> socket.gethostbyname('www.google.com') '10.0.0.1' I will not pass judgement on monkey patching like this, just pointing out it's doable. I know for a fact Ruby can as well. Functions just being variables has useful properties when you're doing something like fancy switch/case type things (the readability of this is questionable, but it's cool to look at it, like a Duff's device): >>> i = 1 >>> { str: func1, unicode: func2, int: func3 }.get(type(i), func1)(i) in func3 Also consider something like this, which is how decorators work (and they're incredibly useful), sort of like a closure: >>> def maker(i): ... def ret(x): ... return x + i ... return ret >>> f, g = maker(10), maker(100) >>> f(5), g(10) (15, 110) I'd be surprised if Ruby couldn't do everything I just did.
- mattmanser 14y agoIt's not designed to be useful By useful he means mucking up your program in totally unexpected ways. He's being nice about it being a silly decision to have it behave that way. The entire post reads more like a list of unexpected things that will bite you in the ass.
- adi92 14y agoThanking you for showing me the 'dis' module. Dis is going to be fun to play with!!
- philwelch 14y agoFunctions (methods) are first class objects also in Ruby: methods are instances of the class Method, while Procs are a lightweight alternative. Default arguments in Ruby are not mutable in any kind of function object, be it a lambda proc, a regular proc, or a method. You can use a mutable default argument as an ersatz static variable, e.g. for memoization.
- bodhi 14y ago> Default arguments in Ruby are not mutable in any kind of function object Maybe I misunderstood you, but they are perfectly mutable: class A attr_accessor :a def initialize @a = [] end def b x = a x << 1 end end obj = A.new # => #<A:0x007fd92b2e9850 @a=[]> obj.b # => [1] obj.b # => [1, 1] obj.a # => [1, 1] If you mean "inline default arguments are not mutable", that's not true either. What is true is that the default argument is evaluated when the function is called, not when it is defined: a = 0 # => 0 x = lambda {|y = (a + 1)| y } # => #<Proc:0x007fd92b1dae50@(irb):33 (lambda)> x[] # => 1 a = 5 # => 5 x[] # => 6
- philwelch 14y agoGood catch, I hadn't thought of the case where the default argument is expressed in terms of another variable.
- mratzloff 14y agoAnd this is expected behavior. What is unexpected is when this behavior is afforded to new list or dictionary arguments in Python, just as it would be unexpected to say ruby> class A; end => nil ruby> def foo(bar = A.new); return bar; end => nil ruby> foo => #<A:0x00000101985060> ruby> foo => #<A:0x0000010197a6b0> and get back the same object each time `foo` is called in Ruby. I've even seen a major Python library with this bug (I'm sorry, I don't recall which off-hand). It's really surprising behavior for new Python devs.
- anthonyb 14y agoThere are a few use cases for default variables on effbot's site: http://effbot.org/zone/default-values.htm http://effbot.org/zone/default-values.htm Basically, sometimes you do want to reuse the mutable between function calls, and in those cases it can save a fair bit of code passing it in repeatedly.
- iki23 14y agoGood coverage. I use it quite often for cache dictionary, it's much simpler API and overall code, than creatng a new class for it. Demo snippet from effbot's site: def calculate(a, b, c, memo={}): try: value = memo[a, b, c] # return already calculated value except KeyError: value = heavy_calculation(a, b, c) memo[a, b, c] = value # update the memo dictionary return value
- ollysb 14y agoThis seems a bit leaky. You're exposing the caching mechanism in the method signature(yeah, ok, in practice it's unlikely to be a problem).
- anthonyb 14y agoThe other option is to create your own cache object and pass that in (and around, if it's a recursive function). Of course, in Python pretty much every cache object follows the dictionary interface anyway, so it doesn't really matter. One of the benefits of duck typing :)
- chernevik 14y agoI am but an egg, but isn't this the same but shorter? def f(L=None): L = L or []
- coderdude 14y agoIt might work the same depending on your use of it but it's not the same. In that instance L will become a blank list if it is equal to None, zero, or a zero length string. There are many cases where this wouldn't affect anything, but there can also be instances where that will cause you to define L as a blank list when you really wanted to keep L's value. I think it's always better to be explicit and test for the value(s) you expect.
- chernevik 14y agoAh. These are the little assumptions that keep blowing off my feet. Thanks.
- SoftwareMaven 14y agoIt is these cases that brought the ternary operator to Python: def f(x=None): x if x is not None else []
- iki23 14y agoYes, but why? Actually, it's No, because python culture aims to use one way to do thing, the least surprising one. In this case it's: def f(x=None): if x is None: x = []
- SoftwareMaven 14y agoBecause that "if" is a statement whereas the ternary expression is, well, an expression. There are places expressions can be used that statements can't (eg lambdas) and that x or [] won't work (eg when x is False). That said, people still seem to favor your form as the more Pythonic way. Personally, I think that's just because the ternary expression is relatively new.