Python's little-known stories

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:

> Strings can be tricky sometimes/ subtle string*

>>> 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?

**? Description: **


> Time for some hash brownies!/It's time for some cake!

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"?

**? Description: **

>>>5==5.0
 True
 >>> hash(5)==hash(5.0)
 True

**Note: ** Objects with different values may also have the same hash value (hash collision).


**> Return return everywhere!/Return everywhere! **

def some_func():try:return'from_try'finally:return'from_finally'

Output:

>>> some_func()'from_finally'

**? Description: **


**> Deep down, we're all the same./In essence, we are all the same. ***

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

**? Description: **

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.


> For what?/why?

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'}

**? Description: **

 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, some_dict[i]=(0,'w')>>> i, some_dict[i]=(1,'t')>>> i, some_dict[i]=(2,'f')>>> some_dict

> Evaluation time discrepancy/execution timing difference

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]

? Description


> 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

**? Description: **

The difference between is and ==

>>>[]==[]
 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

> A tic-tac-toe where X wins in the first attempt!/Swipe!

# 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?

**? Description: **

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','',''],['','',''],['','','']]

> The sticky output function/troublesome output

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]

**? Description: **

 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

**? Description: **


> The surprising comma/accidental comma

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

**? Description: **

> Backslashes at the end of string/Backslashes at the end of string

Output:

>>> print("\\ C:\\")
\ C:\
>>> print(r"\ C:")
\ C:>>>print(r"\ C:\")

 File "<stdin>", line 1print(r"\ C:\")^
SyntaxError: EOL while scanning string literal

**? Description: **

>>> print(repr(r"wt\"f"))'wt\\"f'

> not knot!/Don't entangle!

x = True
y = False

Output:

>>> not x == y
True
>>> x == not y
 File "<input>", line 1
 x == not y
           ^
SyntaxError: invalid syntax

**? Description: **


> Half triple-quoted strings/three quotation marks

Output:

>>> print('wtfpython''')
wtfpython
>>> print("wtfpython""")
wtfpython
>>> # The following statement will throw`SyntaxError`abnormal
>>> # print('''wtfpython')>>> # print("""wtfpython")

**? Description: **

>>> print("wtf""python")
 wtfpython
 >>> print("wtf""") # or "wtf"""
 wtf

> Midnight time doesn't exist?/Doesn't exist midnight?

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.

**? Description: **

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.


> What's wrong with booleans?/Booleans?

# 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
''

**? Description: **

>>> isinstance(True, int)
 True
 >>> isinstance(False, int)
 True
>>> True ==1==1.0 and False ==0==0.0
 True

> Class attributes and instance attributes/Class attributes and instance attributes

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

**? Description: **


> yielding None/Generate None

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']

**? Description: **


> Mutating the immutable!/It’s hard for a strong man

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...

**? Description: **

( 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)


> The disappearing variable from outer scope/The disappearing external variable

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

**? Description: **

 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)

  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]
>>> e
 Exception()>>> print e
 # Nothing is printed!

> When True is actually False/真亦假

True = False
if True == False:print("I've lost faith in truth!")

Output:

I've lost faith in truth!

**? Description: **


> From filled to None in one instruction…/from there to nothing…

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

**? Description: **

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)


> Subclass relationships*

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.)

**? Description: **


> The mysterious key type conversion/The mysterious key type conversion*

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

**? Description: **

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)

> Let's see if you can guess this?/See if you can guess this?

a, b = a[b]={},5

Output:

>>> a
{5:({...},5)}

**? Description: **

( 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.

>>> 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)

Recommended Posts

Python&#39;s little-known stories
python&#39;s @abstractmethod