Suppose you want to get the last n
elements of a sequence for several values of n
:
>>> for n in (1, 5, 2, 0):
... print('ABCDEFGH'[-n:])
...
H
DEFGH
GH
ABCDEFGH
The last iteration may have not printed out what we expected. The reason is quite obvious: sequence[-0:]
is the same as sequence[0:]
, which means such a slice will produce a copy of the whole sequence.
Suppose you have a simple function to restrict user input to certain permitted values:
def get_answer_from_options(message, options):
while True:
answer = input(message)
if answer in options:
return answer
As a container for the options, you can use e.g. a tuple:
>>> get_answer_from_options('yes or no: ', ('y', 'n'))
yes or no: boo
yes or no: n
'n'
>>> get_answer_from_options('yes or no: ', ('y', 'n'))
yes or no:
yes or no: y
'y'
When your options are just a few single-char answers like the above, you may decide to use a string as the options container:
>>> get_answer_from_options('yes or no: ', 'yn')
yes or no: boo
yes or no: n
'n'
>>> get_answer_from_options('yes or no: ', 'yn')
yes or no:
''
Not exactly the same behavior when the user enters an empty string. The reason is that char in ('y', 'n')
and char in 'yn'
are not entirely equivalent:
>>> '' in ('y', 'n')
False
>>> '' in 'yn'
True
Suppose you have the following list, and you want to swap two values, e.g. put bar
where foo
is, and vice versa. But you don't know their exact position in the list, so you use the index
method.
>>> a = ['', '', 'foo', '', 'bar', '']
>>> a[a.index('foo')], a[a.index('bar')] = 'bar', 'foo'
>>> a
['', '', 'foo', '', 'bar', '']
Why didn't that work? The reason is that the two assignments are in fact executed separately, from left to right:
>>> a = ['', '', 'foo', '', 'bar', '']
>>> a[a.index('foo')] = 'bar'
>>> a
['', '', 'bar', '', 'bar', '']
>>> a[a.index('bar')] = 'foo'
>>> a
['', '', 'foo', '', 'bar', '']
Fun fact: should the order of elements in the original list be different, the multiple assignment would actually swap the values:
>>> a = ['', '', 'bar', '', 'foo', '']
>>> a[a.index('foo')], a[a.index('bar')] = 'bar', 'foo'
>>> a
['', '', 'foo', '', 'bar', '']
I think this makes this gotcha even more sneaky.
The in
operator checks for equality, not identity:
>>> a, b, c, d = [1], [2], [3], [1]
>>> for list_ in (a, b, c, d): print(id(list_))
...
140460797957952
140460797958016
140460797958080
140460797957824
>>> a == d
True
>>> list_of_lists = [b, c, d]
>>> a in list_of_lists
True
>>> any(sublist is a for sublist in list_of_lists)
False
>>> id(a) in (id(sublist) for sublist in list_of_lists)
False
Similar example with custom objects:
>>> class A:
... def __init__(self, a, b):
... self.a = a
... self.b = b
... def __eq__(self, other):
... return self.a == other.a and self.b == other.b
...
>>> a, b, c, d = A(1, 2), A(3, 4), A(1, 2), A(5, 6)
>>> for obj in (a, b, c, d): print(id(obj))
...
140691008330336
140691008331104
140691008331248
140691008331440
>>> a == c
True
>>> list_of_objects = [b, c, d]
>>> a in list_of_objects
True
>>> any(obj is a for obj in list_of_objects)
False
>>> id(a) in (id(obj) for obj in list_of_objects)
False
Suppose you have a list called main
in the global scope, and you need to add to it elements from another sequence. You write two functions that, at first glance, do the same thing:
>>> main = [1, 2, 3]
>>> def extend_main(sequence):
... main.extend(sequence)
...
>>> def add_to_main(sequence):
... main += sequence
...
The first function works as expected, while the other throws an error.
>>> extend_main([4, 5, 6])
>>> main
[1, 2, 3, 4, 5, 6]
>>> add_to_main([7, 8, 9])
UnboundLocalError: local variable 'main' referenced before assignment
>>> print(f'spam{" " * 4}eggs')
spam eggs
>>> print(f'spam{"\n" * 4}eggs')
SyntaxError: f-string expression part cannot include a backslash
>>> newline = '\n'
>>> print(f'spam{newline * 4}eggs')
spam
eggs