A Python Guide for the Ages
gto76.github.io
gto76.github.io
def f(xs = []):
xs.append(5)
return xs
print(f())
print(f())
will print: [5]
[5,5]
and I love python despite this horrendous decision, but it should be mentioned more often in beginner resources.edit: thanks for the downvote? I've answered on the order of dozens of questions about this very mechanic on SO, via IRC, and more... it's a well-known confusing factor
On the other hand this behavior means that the default value will only get computed once, so I guess Guido thought it's useful if that value comes e.g. from an expensive function.
On the other other hand you could achieve that latter behavior with a lazy function even if the arguments were reevaluated every time you call the function.
What do others like Common Lisp and Ruby do here?
Without this it’s not clear how you’d introspect the default kwarg values of a function? Would they be computed every time the function is inspected? If they are lazy then what if the default value is expensive to compute or has side effects?
So I would not say it’s a horrendous decision, it’s a clear trade off that IMO fits quite well in the Python ethos - everything is an object, and everything can be introspected.
Not to say that it can’t is not confusing (at least to begin with), but it’s not an oversight. It’s a conscious choice and I think the only safe one you can make.
https://perso.limsi.fr/pointal/_media/python:cours:mementopy...
Other cheatsheets (excluding learnxinyminutes already mentioned in another comment)
* Python Crash Course: https://ehmatthes.github.io/pcc_2e/cheat_sheets/cheat_sheets...
* Scientific Python: https://ipgp.github.io/scientific_python_cheat_sheet/
I think the idea is cool (albeit rarely useful), but a different word than "else" for the same functionality would go a long way.
Generator return, on the other hand, is something I didn’t even know about (basically, return X in a generator raises StopIteration(X)). I don’t think it’s super useful because even inspecting the arguments to an exception tends to be uncommon practice in most code I’ve seen (more frequently, you just want to report or suppress the error depending on its type). I’m sure there’s a niche use for it somewhere though!
for ...:
...
else:
# loop ended, no positions found
return ... for … :
…
else: # break didn’t occur
…
I typically make it even shorter, but this is probably the most readable.I'd like a different name for for "for's" else.
I've been using method/function redefinition in place of conditionals related to initialization.
In a for loop, the else clause only runs if the loop successfully completes (isn't broken), so the else in for-else means the total opposite of what it means in every other pattern.
I think it would make a lot more sense if it were replaced with "done" or "upon" or something else that communicates how it works.
Edit: This is wrong. See replies.
However, that's exactly how it doesn't work and that's my point. The else clause is only ran if the loop DOESN'T break.
>When used with a loop, the else clause has more in common with the else clause of a try statement than it does with that of if statements: a try statement’s else clause runs when no exception occurs, and a loop’s else clause runs when no break occurs.
Inside any loop are a conditional and a jump. In pseudocode:
if not <loop end condition> then
<do loop things>
<jump to top of loop>
else //loop is done
<do things in the case that the loop has terminated naturally>
If we 'break' in the body of the loop for some reason, we will never hit the 'else' in this chunk of code. As Mr. Hettinger explains, this is obvious to anyone reading Knuth or coming from a 'goto' style of control flow. This is not an insult, but an observation. (Un)Fortunately, structured programming is the absolute norm now, and we learn looping constructs directly, rather than learning 'goto' and then building to looping constructs. Especially in a language with rich iteration protocols, such as Python, it is very much unapparent that the looping constructs are fancy wrappers around 'goto'.Link to the talk: https://youtu.be/OSGv2VnC0go?t=948
It's actually a super useful pattern for any kind of search iteration. I've used it many times.
def my_generator():
yield 1
yield 2
return 3
If you use that generator in a for loop then it will only put 1 and 2 into the iterator variable. You have to use the generator in a more direct way to get access to the 3.I haven't made use of return values from generators in my code either, but I believe they're used under the hood in coroutines in async code.
def lex(src):
state = skip_whitespace
while state is not None:
state = yield from state()
It wasn't particularly essential to use the return value from the generator here, as I could have just made the state variable available in the scope of the state functions for them to mutate, but this seemed like a cleaner way to do it as it enforced the idea that each new state corresponded to a new function.I used it recently in some mutating code in which I wanted to make a change, and also know if it did actually make a change. If the routine gets to the end of the loop without finding a place to make a change, it hits the "else" and returns False.
for i in range(n):
if search(i) == val:
break
else:
raise KeyError("not found")
found_index = i
It reduces the need for an additional flag. More importantly it makes it easier to ensure that the break condition is satisfied such that the loop variable can be used properly later on. Personally, I think an else condition should almost always be there for loops that gets broken early, similarly to always finishing if else chain with a final else.ETA: Another pattern where it's really useful is to replace
while True:
with a safer guaranteed terminating loop, for i in range(MAX_ITER):
...
else:
raise RuntimeError("exceeded max iterations") for i in range(n):
if search(i) == val:
break
raise KeyError("not found")
found_index = iThe point of the for / else is that the else only gets evaluated when the for terminates without a break. So in the example you only get a KeyError if the search() never returns val.
Part of the confusion I guess is that the else: in my example is paired with for, not with if, Python indentation being significant etc.
Before being introduced to `for`/`else`, I'd have written the example you gave as:
result = None
for i in range(n):
if search(i) == val:
result = i
if not result:
raise KeyError("not found")I don't think I've seen it in the wild either. Perhaps too new?
For those of you for whom this list is too terse, check out the excellent LearnXinYminutes site at https://learnxinyminutes.com/docs/python/
These thing are useful for me who uses python every couple of months. Not often enough to remember it, so I need some reference to refresh my memory.
https://www.techrepublic.com/article/programming-languages-w...