00:00:00

Pièges et bizarreries de (C)Python

Alex Marandon

PyConFr 2015

Notes

Makina Corpus

http://makina-corpus.com

Notes

>>> def func(things=[]):
...     things.append("PyConFr")
...     return things
... 
>>> func()
???

Notes

>>> def func(things=[]):
...     things.append("PyConFr")
...     return things
... 
>>> func()
['PyConFr']
>>> func()
???

Notes

>>> def func(things=[]):
...     things.append("PyConFr")
...     return things
... 
>>> func()
['PyConFr']
>>> func()
['PyConFr', 'PyConFr']
>>> func()
['PyConFr', 'PyConFr', 'PyConFr']

Notes

>>> func.__defaults__
(['PyConFr', 'PyConFr', 'PyConFr'],)

Notes

>>> func.__defaults__
(['PyConFr', 'PyConFr', 'PyConFr'],)
>>> del func.__defaults__[0][:]
>>> func.__defaults__
([],)
>>> func()
['PyConFr']
>>> func.__defaults__[0].append(
... 'Django Carrots')
>>> func()
['PyConFr', 'Django Carrots', 'PyConFr']

Notes

>>> def func(things=[]):
...     print(id(things))
...     things.append("PyConFr")
...     return things
... 
>>> func()
140014883158640
['PyConFr']
>>> func()
140014883158640
['PyConFr', 'PyConFr']
>>> func()
140014883158640
['PyConFr', 'PyConFr', 'PyConFr']

Notes

Notes

>>> functions = []
>>> for i in range(5):
...     def func():
...         return i
...     functions.append(func)
... 
>>> for func in functions:
...     func()
... 
?
?
?
?
?

Notes

>>> functions = []
>>> for i in range(5):
...     def func():
...         return i
...     functions.append(func)
... 
>>> for func in functions:
...     func()
... 
4
4
4
4
4

Notes

>>> from dis import dis
>>> dis(functions[0])
  3           0 LOAD_GLOBAL              0 (i)
              3 RETURN_VALUE        
>>> dis(functions[4])
  3           0 LOAD_GLOBAL              0 (i)
              3 RETURN_VALUE

Doc du module dis:

LOAD_GLOBAL(namei)
    Loads the global named co_names[namei] onto the stack.

Notes

>>> functions = []
>>> for i in range(5):
...     def func(i=i):
...         return i
...     functions.append(func)
... 
>>> for func in functions:
...     func()
... 
0
1
2
3
4

Notes

>>> dis(functions[0])
  3           0 LOAD_FAST                0 (i)
              3 RETURN_VALUE        
>>> dis(functions[4])
  3           0 LOAD_FAST                0 (i)
              3 RETURN_VALUE

Doc du module dis:

LOAD_FAST(var_num)
    Pushes a reference to the local co_varnames[var_num] onto the stack.

Notes

>>> x = 10
>>> def bar():
...     print(x)
>>> bar()
??

Notes

>>> x = 10
>>> def bar():
...     print(x)
>>> bar()
10

Notes

>>> x = 10
>>> def foo():
...     print(x)
...     x += 1
... 
>>> foo()
??

Notes

>>> x = 10
>>> def foo():
...     print(x)
...     x += 1
... 
>>> foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in foo
UnboundLocalError: local variable 'x' referenced before assignment

Notes

>>> def bar():
...     print(x)
... 
>>> dis(bar)
  2           0 LOAD_GLOBAL              0 (x)
              3 PRINT_ITEM          
              4 PRINT_NEWLINE       
              5 LOAD_CONST               0 (None)
              8 RETURN_VALUE

Notes

>>> def foo():
...     print(x)
...     x += 1
... 
>>> dis(foo)
  2           0 LOAD_FAST                0 (x)
              3 PRINT_ITEM          
              4 PRINT_NEWLINE

  3           5 LOAD_FAST                0 (x)
              8 LOAD_CONST               1 (1)
             11 INPLACE_ADD         
             12 STORE_FAST               0 (x)
             15 LOAD_CONST               0 (None)
             18 RETURN_VALUE

Notes

Python Programming FAQ: Why am I getting an UnboundLocalError when the variable has a value?

when you make an assignment to a variable in a scope, that variable becomes local to that scope and shadows any similarly named variable in the outer scope

Notes

>>> x = 10
>>> def foo():
...     global x
...     print(x)
...     x += 1
... 
>>> foo()
10

Notes

>>> a = 256   
>>> b = 256   
>>> a is b     
???

Notes

>>> a = 256   
>>> b = 256   
>>> a is b     
True

Notes

>>> a = 257   
>>> b = 257   
>>> a is b     
???

Notes

>>> a = 257   
>>> b = 257   
>>> a is b     
False

Notes

CPython

https://docs.python.org/2/c-api/int.html

The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you actually just get back a reference to the existing object.

Notes

PyPy

http://pypy.readthedocs.org/en/latest/cpython_differences.html#object-identity-of-primitive-values-is-and-id

Object identity of primitive values works by value equality, not by identity of the wrapper. This means that x + 1 is x + 1 is always true, for arbitrary integers x

Notes

>>> a = 257; b = 257;
>>> a is b
???

Notes

>>> a = 257; b = 257;
>>> a is b
True

Notes

>>> a = 257   
>>> b = 257

est équivalent à :

>>> code1 = compile("a = 257", filename="",
...                 mode="exec")
>>> code2 = compile("b = 257", filename="",
...                 mode="exec")
>>> code1.co_consts
(257, None)
>>> code2.co_consts
(257, None)
>>> code1.co_consts[0] is code2.co_consts[0]
False

Notes

>>> a = 257; b = 257;
>>> a is b

est équivalent à :

>>> code = compile("a = 257; b = 257",
                   filename="", mode="exec")
>>> code.co_consts
(257, None)

Notes

>>> "100" > 1
???

Notes

>>> "100" > 1
True

Notes

>>> "100" > 1000
???

Notes

>>> "100" > 1000
True

Notes

>>> [1000] > [500]
???

Notes

>>> [1000] > [500]
True

Notes

>>> [1000] > (500, )
???

Notes

>>> [1000] > (500, )
False

Notes

Comparisons:

Objects of different types except numbers are ordered by their type names

Notes

>>> type([1000]).__name__
'list'
>>> type((500, )).__name__
'tuple'
>>> 'list' > 'tuple'
False

Notes

Python 3

>>> [1000] > (500, )
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: list() > tuple()
>>> "100" > 1000
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: str() > int()

Notes

Inspiration

Une présentation en anglais de Amy Hanlon : Investigating Python Wats

Notes

Notes