After reading this article, you may feel like you don’t know anything about Python syntax, which is so concise and easy to learn that we mistakenly think it’s easy to learn.



> Strings can be tricky sometimes/

1.

>>> a = “some_string”

>>> id(a)

140420665652016

>>> id(“some” + “_” + “string”) #

140420665652016

2.

>>> a = “wtf”

>>> b = “wtf”

>>> a is b

True

>>> a = “wtf!”

>>> b = “wtf!”

>>> a is b

False

>>> a, b = “wtf!” , “wtf!”

>>> a is b

The True # 3.7 version returns False.

3.

>>> ‘a’ * 20 is ‘aaaaaaaaaaaaaaaaaaaa’

True

>>> ‘a’ * 21 is ‘aaaaaaaaaaaaaaaaaaaaa’

The False # 3.7 version returns True

It makes sense, right?

💡 description:

  • This behavior is due to Cpython compilation optimizations that in some cases try to use existing immutable objects instead of creating a new one each time (this behavior is called string resident).

  • After resident, many variables may point to the same string object in memory (thus saving memory).

  • In the code above, strings reside implicitly. When implicit resident occurs depends on the implementation. Here are some ways to guess whether a string will be hosted:


    • All strings of length 0 and 1 are hosted.

    • String is implemented at compile time (‘ WTF ‘will be hosted, but’. Join ([‘ w ‘, ‘t’, ‘f’]) will not be reside)

    • The string will reside if it contains only letters, numbers, or underscores. So ‘WTF! ‘Due to inclusion! CPython’s implementation of this rule can be found here.

  • When the values of a and B on the same line are set to “WTF!” The Python interpreter creates a new object and references a second variable. If you do an assignment on a different line, it won’t “know” there is already a WTF! Object (because “WTF! Not implicitly resident as mentioned above). It is a compiler optimization especially suitable for interactive environments.

  • Constant folding is a Peephole optimization technique in Python. This means that at compile time the expression ‘a’*20 is replaced with ‘aaAAAAAAAAAAAAAAaaaa’ to reduce runtime clock cycles. Constant folding occurs only for strings of length less than 20. Imagine the size of the.pyc file generated due to the expression ‘a’*10**10). The relevant source code implementation is here.

  • If you run the above sample code in version 3.7, you’ll see that some of the code runs the same as the comments. This is because constant folding has been migrated from the peep-hole optimizer in version 3.7 to the new AST optimizer, which can perform optimizations with greater consistency. (Contributed by Eugene Toder and INADA Naoki in Bpo-29469 and Bpo-11549.)

  • However, in the latest version 3.8, the result has been changed back. Although version 3.8 and 3.7 use the AST optimizer. It is unclear what changes have been officially made to the AST for version 3.8.

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

  • A hash brownie is a cake that contains marijuana, so it’s a pun

1.

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 “JavaScript”?

💡 description:

  • The Python dictionary determines whether two keys are the same by checking for equality and comparing hash values.

  • Immutable objects with the same value always have the same hash value in Python.

    > > > 5 = = 5.0

    True

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

    True

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

  • When the statement some_dict[5] = “Python” is executed, the existing value “JavaScript” is overwritten by “Python” because Python recognizes 5 and 5.0 as the same key for some_dict.

  • This StackOverflow answer beautifully explains the rationale behind this.



> Deep down, we’re all the same

class WTF:

pass

Output:

>>> WTF() == WTF() # Two different objects should not be equal

False

>>> WTF() is WTF() # also different

False

>>> Hash (WTF()) == hash(WTF()) # hash should also be different

True

>>> id(WTF()) == id(WTF())

True

💡 description:

  • When the id function is called, Python creates an object of the WTF class and passes it to the ID function. The ID function then gets its ID value (that is, the memory address) and discards the object. The object is destroyed.

  • When we do this twice in a row, Python assigns the same memory address to the second object. Because (in CPython) the ID function uses the object’s memory address as the object’s ID value, the ID value for both objects is the same.

  • To sum up, an object’s ID value is unique only for the lifetime of the object. Other objects can have the same ID value after the object is destroyed or before it is created.

  • Why is the result of the is operation False? Let’s look at this code.

    class WTF(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 in which objects are destroyed is what makes all the difference.

> is is not what it is! / Unexpected is!

Here is an example that is very famous 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 ==

  • The IS operator checks whether two operands refer to the same object (that is, it checks whether they are the same).

  • The == operator compares the value of two operands.

  • So is means same references, and == means equal values. This is best illustrated by the following example,

    = = > > > [] []

    True

    >>> [] is [] # These two empty lists are located at different memory addresses.

    False

256 is an existing object, 257 is not

When you start Python, objects with values ranging from -5 to 256 are already assigned. These numbers are prepared in advance because they are often used.

Python creates a pool of small integers in this way to avoid the frequent requisition and destruction of memory space by small integers.

Quote from https://docs.python.org/3/c-api/long.html

The current implementation reserves an array of integer objects for all integers between -5 and 256. When you create an integer in that range, you only need to return a reference to the existing object. So it’s possible to change the value of 1. I suspect this behavior is undefined 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

Here the interpreter is not smart enough to realize that we have created an integer 257 when we execute y = 257, so it creates another object in memory.

When a and B are initialized on the same line with the same value, they 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

  • When a and b are set to 257 on the same line, the Python interpreter creates a new object and references the second variable at the same time. If you do it on a different line, it doesn’t “know” that a 257 object already exists.

  • This is a compiler optimization made specifically for interactive environments. When you type two lines into the live interpreter, they compile separately and are therefore optimized separately. If you try this example in a.py file, you won’t see the same behavior because the file is compiled once.

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

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

Output:

>>> a

{5: ({… 5)}},

💡 description:

  • According to the Python language reference, the form of the assignment statement is as follows

    (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, which returns a tuple) and assigns a single result object from left to right to each item in the target list.

  • The + in (target_list “=”)+ means there can be one or more target lists. In this example, the target list is A, B, and a[b] (note that there can only be one expression list, in our case {}, 5).

  • After the expression list is evaluated, its value is automatically unpacked and assigned to the target list from left to right. So, in our example, we first take the {}, 5-tuple and assign it to a, b, and then we get a = {} and b = 5.

  • The {} to which a is assigned is mutable.

  • The second target list is a[b] (you might expect an error here, because a and b were not defined in the previous statement. But remember, we just assigned a {} and b to 5).

  • We will now create a circular reference by setting the value of key 5 in the dictionary to a tuple ({}, 5) ({… } refers to the same object as a). Here is a simpler example of a circular reference

    >>> 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 such a case (a[b][0] is the same object as A)

  • So to summarize, you can also split the example into

    a, b = {}, 5

    a[b] = a, b

    A circular reference can be proved by the fact that a[b][0] is the same object as A

    >>> a[b][0] is a

    True


In this paper, a large number of content comes from: https://github.com/leisurelicht/wtfpython-cn


Remember to follow the wechat public account for support