6 ms·
I would not recommend the default arguments hack. Any decent linter or IDE will flag that as an error and complain about the default argument being mutable (in
by m4r71n 3y ago
I would not recommend the default arguments hack. Any decent linter or IDE will flag that as an error and complain about the default argument being mutable (in fact, mutable default arguments are the target of many beginner-level interview questions). It's much easier to decorate a function with `functools.cache` to achieve the same result.
- Smaug123 3y agoMore concretely, one of the classic Python bugs is to use `[]` as a default argument and then mutate what "is obviously" a local variable.
- nighthawk454 3y agoI think it's even more safe/preferable to use non-mutable `None`s as a default and do: ``` def myfunc(x=None): x = x if x is not None else [] ... ```
- memco 3y agoIn some cases you can also do: x = x or [] Your method is best when you might get falsy values but if that’s not an issue the `or` method is handy.
- aunderscored 3y agoI tend to dislike this method as it's unclear what or returns unless you already know that or behaves this way. x if x is not None else default is cleaner in my opinion
- gcanyon 3y agoI'm learning python, and I hit this milestone about a week ago!
- at_compile_time 3y agoWhat's it do?
- travisjungroth 3y agoWhen you set an object as a default that object is the default for all calls to that function/method. This also holds true if you create the object, like that empty list. So in this case, every call that uses the default argument is using the same list. def listify(item, li=[]): li.append(item) return li listify(1) # [1] listify(2) # [1, 2]
- Spivak 3y agoI would hate to get an interview question where the very premise of it is wrong. Python does have mutable arguments, but so does Ruby. def func(arr=[]) # Look ma we mutated it. arr.append 1 puts arr end Why calling this function a few times outputs [1], [1],... instead of [1], [1, 1],... isn't because Ruby somehow made the array immutable and hid it with copy-on-write or anything like that. It's because Ruby, unlike Python, has default expressions instead of default values. Whenever the default it needed Ruby reevaluates the expression in the scope of the function definition and assigns the result to the argument. If your default expression always returned the same object you would fall into the same trap as Python. The sibling comment is wrong too -- it is a local variable, or as much one as Python can have since all variables, local or not, are names.
- Jtsummers 3y agoJust as a demo of what you're saying: If you were to do (the following is from memory, probably has typos): def func(arr=[]): print(locals) You'd see `arr` there. The `[]` value lives in `func.__defaults__`: def func(arr=[]): print(locals) print(func.__defaults__) # will print: ([],) If you assign to `arr` nothing changes with defaults: def func(arr=[]): print(locals) arr = 10 print(func.__defaults__) # will still print: ([],) But since lists are mutable, calling a mutating function on the list referenced by `arr` will cause a mutation of the list stored in defaults: def func(arr=[]): print(locals) arr.append(10) print(func.__defaults__) # will print: ([10],) But only when `func` is called without something to assign to `arr`: # if pristine and it has not been run before def func(arr=[]): print(locals) arr.append(10) print(func.__defaults__) # will print: ([],) func([])
- TrianguloY 3y agoOr, if you need a "static" variable for other purposes, the usual alternative is to just use a global variable, but if for some reason you can't (or you don't want to) you can use the function itself! def f(): if not hasattr(f, "counter"): f.counter = 0 f.counter += 1 return f.counter print(f(),f(),f()) > 1 2 3
- data-ottawa 3y agoI didn’t realize that the function was available in its own scope. This information is going to help me do horrible things with pandas.
- kevincox 3y agoThis is very important for self-recursion.
- capableweb 3y agoIs there something that isn't "self-recursion"?
- Jtsummers 3y agoMutual recursion. Horrible example, don’t use this: even 0 = true even n = not (odd n-1) odd 0 = false odd n = not (even n-1)
- bewaretheirs 3y agoThat should be even 0 = true even n = odd n-1 odd 0 = false odd n = even n-1 I fed a C version of this (with unsigned n to keep the nasal daemons at bay) to clang and observed that it somehow manages to see through the mutual recursion, generating code that doesn't recurse or loop.
- 3y ago
- sanderjd 3y agoAgreed, I found that example very confusing.
- klyrs 3y agofunctools.cache is pretty new; py3.8 is still supported for another year and a bit.
- xavdid 3y agofunctools.cache is basically `functools.lru_cache(maxsize=None)`. `lru_cache` was added in py3.3, which is widely available.
- deleted 3y ago[deleted]
- bilsbie 3y agoWhy does that issue only come up with default arguments? Why not other places?
- ironSkillet 3y agoDefault arguments are evaluated and created when the function definition is evaluated, not when the function itself is evaluated. This means that the scope of the default argument is actually the entire module, not just a single invocation of the method. This is what throws people off.