def f():
if not hasattr(f, "counter"):
f.counter = 0
f.counter += 1
return f.counter
print(f(),f(),f())
> 1 2 3 even 0 = true
even n = not (odd n-1)
odd 0 = false
odd n = not (even n-1) 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.I looked at python dis output before writing this, you can look at how it specializes in 3.11. But there's also 4 occurences of LOAD_GLOBAL f in the disassembly of this function, all four self-references to f go through module globals, which shows the kind of "slow" indirections Python code struggles with (and can still be optimized, maybe?)
You could scratch your head and wonder why even inside itself, why is the reference to the function itself going through globals? In the case of a decorated or otherwise monkeypatched function, it has to still refer to the same name.
``` def myfunc(x=None): x = x if x is not None else [] ... ```
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. def listify(item, li=[]):
li.append(item)
return li
listify(1) # [1]
listify(2) # [1, 2] 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.
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([])Why not other places?