It takes about 10 minutes to read this article.
Python, is a beautifully designed, interpreted high-level language. It provides many features that make programmers feel comfortable. But sometimes, some of the output results of Python may not seem so obvious to beginners.
This interesting project aims to collect incomprehensible and counter-intuitive examples and little-known features in Python, and try to discuss the real principles behind these phenomena!
Although some of the following examples may not make you think of WTFs, they may still tell you some interesting features of Python that you don’t know. I think this is a good way to learn the inner principles of programming languages, and I believe You will also have fun from it!
If you are a more experienced Python programmer, you can try to challenge to see if you can find the correct answer to the examples in one go. You may be familiar with some of these examples, then this may arouse you to step on these pits. Sweet memories of time.
So, let's get started...
**Note: ** All examples have been tested on the interactive interpreter of Python 3.5.2, and should be applicable to all Python versions unless otherwise specified.
I personally recommend that it is best to read the following examples in turn, and for each example:
>>> a ="some_string">>>id(a)140420665652016>>>id("some"+"_"+"string") #Note that the id values of the two are the same.140420665652016
>>> a ="wtf">>> b ="wtf">>> a is b
True
>>> a ="wtf!">>> b ="wtf!">>> a is b
False
>>> a, b ="wtf!","wtf!">>> a is b #Only applicable to 3.7 or less,3.The return result after 7 is False.
True
>>>' a'*20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>>' a'*21 is 'aaaaaaaaaaaaaaaaaaaaa'
False
It's easy to understand, right?
'wtf' will be resident, but ''.join(['w','t','f'] will not be resident)'wtf!' is not resident because it contains !. CPython's implementation of this rule can be found here.a and b are set to "wtf!" on the same line, the Python interpreter will create a new object, and then reference the second variable at the same time (Translation: only applicable to 3.7 or less, details Please see here for the situation). If you perform assignment operations on different rows, it will not "know" that there is already a wtf! Object (because "wtf!" is not implicitly resident in the way mentioned above). It is a compiler optimization, especially suitable for interactive environments.'a'*20 will be replaced with 'aaaaaaaaaaaaaaaaaaaa' to reduce runtime Clock cycle. Only strings with a length less than 20 will undergo constant folding. (Why? Imagine the size of the .pyc file generated due to the expression 'a'*10**10). Related source code The implementation is here.some_dict ={}
some_dict[5.5]="Ruby"
some_dict[5.0]="JavaScript"
some_dict[5]="Python"
Output:
>>> some_dict[5.5]"Ruby">>> some_dict[5.0]"Python">>> some_dict[5]"Python"
" "Python" eliminates the existence of "JavaScript"?
>>>5==5.0
True
>>> hash(5)==hash(5.0)
True
**Note: ** Objects with different values may also have the same hash value (hash collision).
some_dict[5] = "Python" statement, because Python recognizes 5 and 5.0 as the same key of some_dict, the existing value "JavaScript" is overwritten by "Python".def some_func():try:return'from_try'finally:return'from_finally'
Output:
>>> some_func()'from_finally'
return, break or continue in the try of the "try...finally" statement, the finally clause will still be executed.return statement. Since the finally clause must be executed, the return in the finally clause will always be the last executed statement.classWTF:
pass
Output:
>>> WTF()==WTF() #Two different objects should not be equal
False
>>> WTF() is WTF() #Not the same
False
>>> hash(WTF())==hash(WTF()) #The hash value should also be different
True
>>> id(WTF())==id(WTF())
True
id function is called, Python creates an object of the WTF class and passes it to the id function. Then the id function gets its id value (that is, the memory address), and then discards the object. The object is Was destroyed.is operation is False? Let's look at this code.classWTF(object):
def __init__(self):print("I")
def __del__(self):print("D")
Output:
>>> WTF() is WTF()
I
I
D
D
False
>>> id(WTF())==id(WTF())
I
D
I
D
True
As you can see, the order of object destruction is responsible for all the differences.
some_string ="wtf"
some_dict ={}for i, some_dict[i]inenumerate(some_string):
pass
Output:
>>> some_dict #Index dictionary created.{0:'w',1:'t',2:'f'}
for in Python syntax is: for_stmt:'for' exprlist 'in' testlist ':' suite ['else'':' suite]
Among them, exprlist refers to the allocation target. This means that an operation similar to {exprlist} = (next_value} is performed for each item in the iterable object.
An interesting example illustrates this:
for i inrange(4):print(i)
i =10
Output:
0123
Do you think this loop will only run once?
**? Description: **
i = 10 does not affect the iteration loop. Before each iteration starts, the next element generated by the iterator (here, range(4)) is unpacked And assigned to the variable of the target list (here refers to i).enumerate(some_string) function generates a new value i (counter increment) and obtains a character from some_string. Then the dictionary some_dict key i (just assigned ) Is set to this character. The expansion of the loop in this example can be simplified to:>>> i, some_dict[i]=(0,'w')>>> i, some_dict[i]=(1,'t')>>> i, some_dict[i]=(2,'f')>>> some_dict
array =[1,8,15]
g =(x for x in array if array.count(x)>0)
array =[2,8,22]
Output:
>>> print(list(g))[8]
array_1 =[1,2,3,4]
g1 =(x for x in array_1)
array_1 =[1,2,3,4,5]
array_2 =[1,2,3,4]
g2 =(x for x in array_2)
array_2[:]=[1,2,3,4,5]
Output:
>>> print(list(g1))[1,2,3,4]>>>print(list(g2))[1,2,3,4,5]
array has been reassigned to [2, 8, 22], so for the previous 1, 8 and 15, only the result of count(8) is greater than 0, so the generator will only generate 8.g1 and g2 in the second part is caused by the way the variables array_1 and array_2 are reassigned.array_1 is bound to the new object [1,2,3,4,5], because the in clause is executed at the time of declaration, so it still refers to the old object [1,2,3,4] (not destroyed).array_2 will update the same old object [1,2,3,4] in place to [1,2,3,4,5]. Therefore g2 and array_2 still refer to the same object (this object has now been updated to [1,2,3,4,5]). is is not what it is!/Unexpectedly is!Below is a very famous example on the Internet.
>>> a =256>>> b =256>>> a is b
True
>>> a =257>>> b =257>>> a is b
False
>>> a =257; b =257>>> a is b
True
The difference between is and ==
The is operator checks whether two operands refer to the same object (that is, it checks whether the two budget objects are the same).== The operator compares whether the values of two operands are equal.is means the same reference, == means the value is equal. The following example can illustrate this point well,>>>[]==[]
True
>>>[] is [] #The two empty lists are located at different memory addresses.
False
**256 Is an existing object, and 257 is not **
When you start Python, the values from -5 to 256 are already assigned. These numbers are suitable for being prepared in advance because they are frequently used.
Quoted from https://docs.python.org/3/c-api/long.html
The current implementation keeps an array of integer objects for all integers between -5 and 256. When you create an integer in this range, you only need to return a reference to the existing object. So it is possible to change the value of 1. . I suspect this behavior is undefined behavior in Python. :-)
>>> id(256)10922528>>> a =256>>> b =256>>>id(a)10922528>>>id(b)10922528>>>id(257)140084850247312>>> x =257>>> y =257>>>id(x)140084850247440>>>id(y)140084850247344
The interpreter here is not smart enough to realize that we have created an integer 257 when executing y = 257, so it creates another object in memory.
When a and b are initialized with the same value in the same line, they will point to the same object.
>>> a, b =257,257>>>id(a)140640774013296>>>id(b)140640774013296>>> a =257>>> b =257>>>id(a)140640774013392>>>id(b)140640774013488
257 on the same line, the Python interpreter will create a new object and refer to the second variable at the same time. If you do it on different lines, it will not "know" that it has been There is a 257 object..py file In this example, you will not see the same behavior because the file is compiled all at once.# We first initialize a variable row
row =[""]*3 #row i['','','']
# And create a variable board
board =[row]*3
Output:
>>> board
[['','',''],['','',''],['','','']]>>> board[0]['','','']>>> board[0][0]''>>> board[0][0]="X">>> board
[[' X','',''],['X','',''],['X','','']]
Have we ever assigned 3 "X"s?
When we initialize the row variable, the following picture shows the situation in memory.

When the board is initialized by multiplying the row, the situation in the memory is as shown in the figure below (each element board[0], board[1] and board[2] are all Refers to the same list as row.)

We can avoid this situation by not using variable row to generate board. (This issue proposes this requirement.)
>>> board =[['']*3for _ inrange(3)]>>> board[0][0]="X">>> board
[[' X','',''],['','',''],['','','']]
funcs =[]
results =[]for x inrange(7):
def some_func():return x
funcs.append(some_func)
results.append(some_func()) #Note that this function is executed
funcs_results =[func()for func in funcs]
Output:
>>> results
[0,1,2,3,4,5,6]>>> funcs_results
[6,6,6,6,6,6,6]
Even if the value of x before adding some_func to funcs in each iteration is different, all functions still return 6.
// Another example
>>> powers_of_x =[lambda x: x**i for i inrange(10)]>>>[f(2)for f in powers_of_x][512,512,512,512,512,512,512,512,512,512]
funcs =[]for x inrange(7):
def some_func(x=x):return x
funcs.append(some_func)
**Output:**
>>> funcs_results =[func()for func in funcs]>>> funcs_results
[0,1,2,3,4,5,6]
is not … is not is (not …)/is not … is not is (not …)>>>' something' is not None
True
>>>' something'is(not None)
False
is not is a single binary operator, which is different from using is and not separately.is not is False, otherwise the result is True.Output:
>>> def f(x, y,):...print(x, y)...>>> def g(x=4, y=5,):...print(x, y)...>>> def h(x,**kwargs,):
File "<stdin>", line 1
def h(x,**kwargs,):^
SyntaxError: invalid syntax
>>> def h(*args,):
File "<stdin>", line 1
def h(*args,):^
SyntaxError: invalid syntax
Output:
>>> print("\\ C:\\")
\ C:\
>>> print(r"\ C:")
\ C:>>>print(r"\ C:\")
File "<stdin>", line 1print(r"\ C:\")^
SyntaxError: EOL while scanning string literal
r, the backslash has no special meaning.>>> print(repr(r"wt\"f"))'wt\\"f'
x = True
y = False
Output:
>>> not x == y
True
>>> x == not y
File "<input>", line 1
x == not y
^
SyntaxError: invalid syntax
== operator in Python is higher than the not operator.not x == y is equivalent to not (x == y), and also equivalent to not (True == False), and the final operation result is True.x == not y throws a SyntaxError exception is because it will be considered equivalent to (x == not) y instead of the x == (not y )`.not token to be part of the not in operator (because the == and not in operators have the same precedence), but it cannot find the in after the not token Flag, so a SyntaxError exception will be thrown.Output:
>>> print('wtfpython''')
wtfpython
>>> print("wtfpython""")
wtfpython
>>> # The following statement will throw`SyntaxError`abnormal
>>> # print('''wtfpython')>>> # print("""wtfpython")
>>> print("wtf""python")
wtfpython
>>> print("wtf""") # or "wtf"""
wtf
''' And "" are also string delimiters in Python. When the Python interpreter encounters three quotation marks first, it will try to find three terminating quotation marks as delimiters. If it does not exist, it will cause a SyntaxError Exception.from datetime import datetime
midnight =datetime(2018,1,1,0,0)
midnight_time = midnight.time()
noon =datetime(2018,1,1,12,0)
noon_time = noon.time()if midnight_time:print("Time at midnight is", midnight_time)if noon_time:print("Time at noon is", noon_time)
Output:
(' Time at noon is', datetime.time(12,0))
midnight_time is not output.
Before Python 3.5, if datetime.timeObject Storage is UTC's midnight time (translation: is 00:00), then its boolean value will be considered as False. When using If obj: statement is used to check whether obj is null or some "empty" value, it is easy to make mistakes.
# A simple example,Count the number of boolean values and integer values in the iterable objects below
mixed_list =[False,1.0,"some_string",3, True,[], False]
integers_found_so_far =0
booleans_found_so_far =0for item in mixed_list:ifisinstance(item, int):
integers_found_so_far +=1
elif isinstance(item, bool):
booleans_found_so_far +=1
Output:
>>> integers_found_so_far
4>>> booleans_found_so_far
0
another_dict ={}
another_dict[True]="JavaScript"
another_dict[1]="Ruby"
another_dict[1.0]="Python"
Output:
>>> another_dict[True]"Python"
>>> some_bool = True
>>>" wtf"*some_bool
' wtf'>>> some_bool = False
>>>" wtf"*some_bool
''
int>>> isinstance(True, int)
True
>>> isinstance(False, int)
True
True is 1, and the integer value of False is 0.>>> True ==1==1.0 and False ==0==0.0
True
classA:
x =1classB(A):
pass
classC(A):
pass
Output:
>>> A.x, B.x, C.x(1,1,1)>>> B.x =2>>> A.x, B.x, C.x(1,2,1)>>> A.x =3>>> A.x, B.x, C.x(3,2,3)>>> a =A()>>> a.x, A.x(3,3)>>> a.x +=1>>> a.x, A.x(4,3)
classSomeClass:
some_var =15
some_list =[5]
another_list =[5]
def __init__(self, x):
self.some_var = x +1
self.some_list = self.some_list +[x]
self.another_list +=[x]
Output:
>>> some_obj =SomeClass(420)>>> some_obj.some_list
[5,420]>>> some_obj.another_list
[5,420]>>> another_obj =SomeClass(111)>>> another_obj.some_list
[5,111]>>> another_obj.another_list
[5,420,111]>>> another_obj.another_list is SomeClass.another_list
True
>>> another_obj.another_list is some_obj.another_list
True
**dict** attribute). If you can't find it in the dictionary of the current class, go to its parent class.+= Operators modify mutable objects in place, instead of creating new ones. Therefore, modifying the properties of one instance will affect other instance and class properties.some_iterable =('a','b')
def some_func(val):return"something"
Output:
>>>[ x for x in some_iterable]['a','b']>>>[(yield x)for x in some_iterable]<generator object <listcomp> at 0x7f70b0a4ad58>>>>list([(yield x)for x in some_iterable])['a','b']>>>list((yield x)for x in some_iterable)['a', None,'b', None]>>>list(some_func((yield x))for x in some_iterable)['a','something','b','something']
some_tuple =("A","tuple","with","values")
another_tuple =([1,2],[3,4],[5,6])
Output:
>>> some_tuple[2]="change this"
TypeError:'tuple' object does not support item assignment
>>> another_tuple[2].append(1000) #No error here
>>> another_tuple([1,2],[3,4],[5,6,1000])>>> another_tuple[2]+=[99,999]
TypeError:'tuple' object does not support item assignment
>>> another_tuple([1,2],[3,4],[5,6,1000,99,999])
I thought tuples are immutable...
+= The operator modifies the list in place. The element assignment operation does not work, but when the exception is thrown, the element has been modified in place.( Translation: For immutable objects, this refers to tuple, += is not an atomic operation, but two actions of extend and =, where the = operation will throw an exception, but the extend operation has been The modification was successful. Detailed explanation can be found here)
e =7try:
raise Exception()
except Exception as e:
pass
Output (Python 2.x):
>>> print(e)
# prints nothing
Output (Python 3.x):
>>> print(e)
NameError: name 'e' is not defined
as to assign an exception to the target, the exception will be cleared at the end of the except clause. except E as N:
foo
Will be translated into
except E as N:try:
foo
finally:
del N
This means that the exception must be assigned to other variables before it can be referenced after the except clause. The reason why the exception is cleared is that the additional traceback information (trackback) will be formed with the stack frame Circular references, so that all local variables in the stack frame are active before the next garbage collection occurs. (Translation: that is, will not be collected)
e will be deleted due to the execution of the except clause. For those with independent internal scope The situation is different for functions. The following example illustrates this: def f(x):del(x)print(x)
x =5
y =[5,4,3]
**Output:**
>>> f(x)
UnboundLocalError: local variable 'x' referenced before assignment
>>> f(y)
UnboundLocalError: local variable 'x' referenced before assignment
>>> x
5>>> y
[5,4,3]
Exception() instance is assigned to the variable e, so when you try to print the result, its output is empty. (Translation: a normal Exception instance is printed out as empty)>>> e
Exception()>>> print e
# Nothing is printed!
True = False
if True == False:print("I've lost faith in truth!")
Output:
I've lost faith in truth!
True, False, and bool, but, in order to For backward compatibility, they can't set True and False as constants, but set them as built-in variables.some_list =[1,2,3]
some_dict ={"key_1":1,"key_2":2,"key_3":3}
some_list = some_list.append(4)
some_dict = some_dict.update({"key_4":4})
Output:
>>> print(some_list)
None
>>> print(some_dict)
None
Most methods of modifying sequence/mapping objects, such as list.append, dict.update, list.sort, etc., modify the object in place and return None. The reason for this is that if The operation can be done in situ, so you can avoid creating a copy of the object to improve performance. (refer here)
Output:
>>> from collections import Hashable
>>> issubclass(list, object)
True
>>> issubclass(object, Hashable)
True
>>> issubclass(list, Hashable)
False
The subclass relationship should be transitive, right? (i.e., if A is a subclass of B and B is a subclass of C, then Ashould be C Subclass.)
**subclasscheck** in the metaclass at will.issubclass(cls, Hashable) is called, it just looks for the "**hash__" method or the method inherited from "__hash**" in cls.object is hashable, but list is not hashable, it breaks this transitive relationship.classSomeClass(str):
pass
some_dict ={'s':42}
Output:
>>> type(list(some_dict.keys())[0])
str
>>> s =SomeClass('s')>>> some_dict[s]=40>>> some_dict #expected:Two different key-value pairs
{' s':40}>>>type(list(some_dict.keys())[0])
str
SomeClass will automatically inherit the **hash** method from str, the hash value of the s object and the "s" string are the same.SomeClass("s") == "s" is True because SomeClass also inherits the **eq** method of the str class.**eq** method of SomeClass.classSomeClass(str):
def __eq__(self, other):return(type(self) is SomeClass
and type(other) is SomeClass
and super().__eq__(other))
# When we customize__eq__Method time,Python will no longer automatically inherit__hash__method
# So we also need to define it
__ hash__ = str.__hash__
some_dict ={'s':42}
Output:
>>> s =SomeClass('s')>>> some_dict[s]=40>>> some_dict
{' s':40,'s':42}>>> keys =list(some_dict.keys())>>>type(keys[0]),type(keys[1])(__main__.SomeClass, str)
a, b = a[b]={},5
Output:
>>> a
{5:({...},5)}
( target_list "=")+(expression_list | yield_expression)
The assignment statement evaluates the expression list (remember this can be a single expression or a comma-separated list, the latter returns a tuple) and assigns a single result object to each item in the target list from left to right.
( The + in target_list "=")+ means there can be one or more target lists. In this example, the target lists are a, b and a[b] (note the expression There can only be one expression list, in our example it is (), 5).(), 5 tuples Assign values to a, b, then we can get a = () and b = 5. a The assigned () is a mutable object.a[b] (you may think that an error will be reported here, because in the previous statement, both a and b have not yet been defined. But don’t forget, we just added a Assign () and assign b to 5).5 in the dictionary to the tuple ({}, 5) (the {...} in the output refers to the same object as a) . The following is a simpler circular reference example>>> some_list = some_list[0]=[0]>>> some_list
[[...]]>>> some_list[0][[...]]>>> some_list is some_list[0]
True
>>> some_list[0][0][0][0][0][0]== some_list
True
Our example is this situation (a[b][0] and a are the same object)
a, b ={},5
a[b]= a, b
And it can be proved that it is a circular reference by a[b][0] and a being the same object
>>> a[b][0] is a
True
If you’re tired, let’s take a break, and we will continue to share similar stories later.
( Finish)