• Python is a clear and concise language. If you don’t know some details, you will fall into those deep “pits”. Here, I will summarize some common pits in Python.

List creation and reference

Creating nested lists

  • Use the * sign to create a nested list:

        li = [[]] * 3
        print(li)
        # Out: [[], [], []]
  • Through this method, you can get a nested list with three lists. Let’s add an element to the first list:

        li[0].append(1)
        print(li)
        # Out: [[1], [1], [1]]
  • From the output, we can see that at first, we only add one element to the first element, and one element to all three lists. This is because [[]] * 3 does not create three different lists, but three objects pointing to the same list. Therefore, when we operate on the first element, the contents of the other two elements will also change. The effect is the same as the following code:

        li = []
        element = [[]]
        li = element + element + element
        print(li)
        # Out: [[], [], []]
        element.append(1)
        print(li)
        # Out: [[1], [1], [1]]
  • We can print out the memory address of the element

        li = [[]] * 3
        print([id(inner_list) for inner_list in li])
        # Out: [6830760, 6830760, 6830760]
  • We can understand why. How did that work out? You can do this:

        li = [[] for _ in range(3)]
  • So we create three different list objects

        print([id(inner_list) for inner_list in li])
        # Out: [6331048, 6331528, 6331488]

    References to list elements

  • Do not use the index method to traverse the list, for example:

        for i in range(len(tab)):
            print(tab[i])

    A better way is to:

        for elem in tab:
        print(elem)

    The for statement automatically generates an iterator. If you need index locations and elements, use the enumerate function:

        for i, elem in enumerate(tab):
            print((i, elem))

    Note = = use of symbols

    if (var == True):
            #If condition holds when VaR is true, 1, 1.0, 1L
        if (var != True):
            #If condition holds when VaR is not true and 1
        if (var == False):
            #When VaR is false or 0 (or 0.0, 0l, 0j) if condition is true
    
        if (var == None):
            #VaR is none if
    
        if var:
            #When the if condition such as string / list / dictionary / tuple, non-0 and so on, which is a non empty (none or size 0) object of VaR, is true
    
        if not var:
            #When the if conditions such as string / list / dictionary / tuple, non-0 and so on are true
    
        if var is True:
            #Only when VaR is true, the if condition is 1
    
        if var is False:
            #Only if the if condition is set to 0 when VaR is false
    
        if var is None:
        #Consistent with VaR = = none

    Catch exception due to early check

  • Less elegant code:

        if os.path.isfile(file_path):
            file = open(file_path)
        else:
            # do something

    Good practice:

        try:
            file = open(file_path)
        except OSError as e:
            # do something

    In python2.6 +, it can be simpler:

        with open(file_path) as file:

    The reason for this is that it is more general. For example, if file path sends you a none, you will be blind, and you have to judge whether it is none. If you don’t judge, you have to catch exceptions. If you judge, you have to write a lot of code.

Class variable initialization

  • Not in the object’sinitThere are two main problems in initializing class properties outside functions

    • If the class property changes, the initial value changes.
    • If you set mutable objects to the default, you get the same objects shared across instances.
Error demonstration (unless you want static variables)

```
    class Car(object):
        color = "red"
        wheels = [Wheel(), Wheel(), Wheel(), Wheel()]
```

The right approach:

```
    class Car(object):
        def __init__(self):
            self.color = "red"
            self.wheels = [Wheel(), Wheel(), Wheel(), Wheel()]
```

**Function default parameters**

```
def foo(li=[]):
    li.append(1)
    print(li)

foo([2])
# Out: [2, 1]
foo([3])
# Out: [3, 1]
```

The code behaves as expected, but what if we don't pass parameters?

```
foo()
# Out: [1] As expected...

foo()
# Out: [1, 1]  Not as expected...
```

This is because the function parameter type is defined to confirm rather than run-time, so Li points to the same list object in two function calls. To solve this problem, you can:

```
def foo(li=None):
    if not li:
        li = []
    li.append(1)
    print(li)

foo()
# Out: [1]

foo()
# Out: [1]
```

Although this solves the above problems, the output of other objects, such as zero length strings, is not what we want.

```
x = []
foo(li=x)
# Out: [1]

foo(li="")
# Out: [1]

foo(li=0) 
# Out: [1]
```

The most common way is to check whether the parameter is none.

```
def foo(li=None):
    if li is None:
        li = []
    li.append(1)
    print(li)

foo()
# Out: [1]
```

**Modify during traversal**
  • For statement generates an iterator when traversing an object. If you modify an object during traversal, unexpected results will be generated:

        alist = [0, 1, 2]
        for index, value in enumerate(alist):
            alist.pop(index)
        print(alist)
        # Out: [1]
  • The second element was not removed because the iteration iterates through the indexes in order. The above loop is traversed twice, and the results are as follows:

        # Iteration #1
        index = 0
        alist = [0, 1, 2]
        alist.pop(0) # removes '0'
    
        # Iteration #2
        index = 1
        alist = [1, 2]
        alist.pop(1) # removes '2'
    
        # loop terminates, but alist is not empty:
        alist = [1]
  • If you avoid this problem, you can create another list

        alist = [1,2,3,4,5,6,7]
        for index, item in reversed(list(enumerate(alist))):
            # delete all even items
            if item % 2 == 0:
                alist.pop(index)
        print(alist)
        # Out: [1, 3, 5, 7]

    Integer and string definitions

  • Python caches an interval integer in advance to reduce the memory operation, but it is also true. Sometimes there are very strange errors, such as:

        >>> -8 is (-7 - 1)
        False
        >>> -3 is (-2 - 1)
        True
  • Another example

        >>> (255 + 1) is (255 + 1)
        True
        >>> (256 + 1) is (256 + 1)
        False
  • Through continuous testing, we can find that the integers in (- 3256) range return true, some even (- 8257). By default, [- 5256] will be created and cached the first time the interpreter starts, so the above strange behavior will occur. This is a very common but easily overlooked pit. The solution is to always use the equality (= =) operator instead of the identity (is) operator to compare values.
  • Python also retains references to commonly used strings, and can produce similar obfuscation when comparing the identity (i.e., use) of is strings.

    >>> 'python' is 'py' + 'thon'
    True       
  • Python strings are cached. All Python strings are references to this object. For uncommon strings, even if the strings are equal, the identity comparison fails.

    >>> 'this is not a common string' is 'this is not' + ' a common string'
    False
    >>> 'this is not a common string' == 'this is not' + ' a common string'
    True
  • So, just like the integer rule, always use the equal (= =) operator instead of the identity (is) operator to compare string values.

List derivation and variable leakage in loops

  • Here’s an example:

        i = 0
        a = [i for i in range(3)]
        print(i) # Outputs 2

    List derivation in python2 changes the value of I variable, and python3 fixes this problem:

        i = 0
        a = [i for i in range(3)]
        print(i) # Outputs 0

    Similarly, for loops have no private scope for their iteration variables

        i = 0
        for i in range(3):
            pass
        print(i) # Outputs 2

    This behavior occurs in Python 2 and python 3.

    To avoid the problem of leaking variables, use new variables in list derivation and for loops.

Or operator

  • for example

        if a == 3 or b == 3 or c == 3:

    This is very simple, but look at another one:

        if a or b or c == 3: # Wrong

    This is because the priority of or is lower than = =, so the expression will be evaluated as if (a) or (b) or (C = = 3):. The correct way is to clearly check all conditions:

    if a == 3 or b == 3 or c == 3:  # Right Way

    Alternatively, you can use the built-in function any() instead of the linker operator:

    if any([a == 3, b == 3, c == 3]): # Right

    Or, to make it more efficient:
    if any(x == 3 for x in (a, b, c)): # Right
    In a shorter way:
    if 3 in (a, b, c): # Right