Подводные камни в Python

Введение

Примеры

  • 47

    Изменение последовательности, по которой вы перебираете

    for итерации цикла по последовательности, так что изменение этой последовательности внутри цикла может привести к неожиданным результатам (особенно при добавлении или удалении элементов):

     alist = [0, 1, 2]
    for index, value in enumerate(alist):
        alist.pop(index)
    print(alist)
    # Out: [1]
    
     

    Примечание: list.pop() используется для удаления элементов из списка.

    Второй элемент не был удален, потому что итерация проходит по индексам по порядку. Вышеупомянутый цикл повторяется дважды, со следующими результатами:

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

    Эта проблема возникает из-за того, что индексы изменяются при итерации в направлении увеличения индекса. Чтобы избежать этой проблемы, вы можете перебирать через петлю обратной:

     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]
    
     

    Итерация цикла, начинающегося в конце, когда элементы удаляются (или добавляются), не влияет на индексы элементов ранее в списке. Так что этот пример будет правильно удалить все элементы, которые даже с alist .


    Аналогичная проблема возникает при вставке или добавления элементов в список , который вы Перебор, что может привести к бесконечному циклу:

     alist = [0, 1, 2]
    for index, value in enumerate(alist):
        # break to avoid infinite loop:
        if index == 20:     
            break           
        alist.insert(index, 'a')
    print(alist)
    # Out (abbreviated): ['a', 'a', ..., 'a', 'a',  0,   1,   2]
    
     

    Без break условия петля вставит 'a' до тех пор , пока компьютер не запускается из памяти , и программа будет продолжаться. В подобной ситуации обычно предпочтительнее создать новый список и добавить элементы в новый список, когда вы перебираете исходный список.


    При использовании for цикла, вы не можете изменить список элементов с переменным заполнителем:

     alist = [1,2,3,4]
    for item in alist:
        if item % 2 == 0:
            item = 'even'
    print(alist)
    # Out: [1,2,3,4]
    
     

    В приведенном выше примере, изменение item не меняет ничего в исходном списке. Вы должны использовать индекс списка ( alist[2] ), и enumerate() работает хорошо для этого:

     alist = [1,2,3,4]
    for index, item in enumerate(alist):
        if item % 2 == 0:
            alist[index] = 'even'
    print(alist)
    # Out: [1, 'even', 3, 'even']
     

    В while цикл может быть лучшим выбором в некоторых случаях:

    Если вы собираетесь удалить все элементы в списке:

     zlist = [0, 1, 2]
    while zlist:
        print(zlist[0])
        zlist.pop(0)
    print('After: zlist =', zlist)
    
    # Out: 0
    #      1
    #      2
    # After: zlist = []
    
     

    Хотя просто сброс zlist будет выполнять один и тот же результат;

     zlist = []
    
     

    Выше пример можно также комбинировать с len() , чтобы остановить после определенного момента, или удалить все , кроме x элементов в списке:

     zlist = [0, 1, 2]
    x = 1
    while len(zlist) > x:
        print(zlist[0])
        zlist.pop(0)
    print('After: zlist =', zlist)
    
    # Out: 0
    #      1
    # After: zlist = [2]
    
    
     

    Или проходной список в то время как удаление элементов , которые удовлетворяют определенное условие (в данном случае удаления всех четных элементов):

     zlist = [1,2,3,4,5]
    i = 0
    while i < len(zlist):
        if zlist[i] % 2 == 0:
            zlist.pop(i)
        else:
            i += 1
    print(zlist)
    # Out: [1, 3, 5]
    
     

    Обратите внимание на то, что вы не увеличиваете i после удаления элемента. При удалении элемента в zlist[i] , индекс следующего элемента уменьшилось на единицу, поэтому, проверив zlist[i] с тем же значением i на следующей итерации, вы будете правильно проверять следующий элемент в списке ,


    Противоположный способ думать об удалении нежелательных элементов из списка, чтобы добавить требуемые товаров в новый список. Следующий пример является альтернативой последнего в while , например петли:

     zlist = [1,2,3,4,5]
    
    z_temp = []
    for item in zlist:
        if item % 2 != 0:
            z_temp.append(item)
    zlist = z_temp
    print(zlist)
    # Out: [1, 3, 5]
    
     

    Здесь мы объединяем желаемые результаты в новый список. Затем мы можем при желании переназначить временный список исходной переменной.

    С этой тенденцией мышления, вы можете вызвать одну из самых элегантных и мощных функций языка Python, списковых, что исключает временные списки и расходится с ранее обсуждавшейся в месте списка / индекс мутации идеологии.

     zlist = [1,2,3,4,5]
    [item for item in zlist if item % 2 != 0]
    # Out: [1, 3, 5] 
  • 66

    Изменяемый аргумент по умолчанию

     def foo(li=[]):
        li.append(1)
        print(li)
    
    foo([2])
    # Out: [2, 1]
    foo([3])
    # Out: [3, 1]
    
     

    Этот код ведет себя как ожидалось, но что если мы не передадим аргумент?

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

    Это происходит потому , что по умолчанию аргументы функций и методов оцениваются во время определения , а не время выполнения. Таким образом , мы только когда - либо один экземпляр li списка.

    Чтобы обойти это, используйте только неизменяемые типы для аргументов по умолчанию:

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

    В то время как улучшение , и , хотя , if not li правильно оценивает в False , многие другие объекты , а также, такие как последовательности нулевой длины. Следующие примеры аргументов могут привести к непредвиденным результатам:

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

    Идиоматический подход непосредственно проверить аргумент против None объекта:

     def foo(li=None):
        if li is None:
            li = []
        li.append(1)
        print(li)
    
    foo()
    # Out: [1] 
  • 92

    Список умножения и общие ссылки

    Рассмотрим случай создания структуры вложенного списка путем умножения:

     li = [[]] * 3
    print(li)
    # Out: [[], [], []]
    
     

    На первый взгляд нам кажется, что у нас есть список, содержащий 3 разных вложенных списка. Давайте попробуем добавить 1 к первому:

     li[0].append(1)
    print(li)
    # Out: [[1], [1], [1]]
    
     

    1 получил добавляется ко всем спискам в li .

    Причина заключается в том, что [[]] * 3 не создает list из 3 -х различных list с. Скорее всего , он создает list держит 3 ссылки на тот же list объектов. Таким образом , когда мы добавляем к li[0] изменение видна во всех суб-элементов li . Это эквивалентно:

     li = []
    element = [[]]
    li = element + element + element
    print(li)
    # Out: [[], [], []]
    element.append(1)
    print(li)
    # Out: [[1], [1], [1]]
    
     

    Это может быть дополнительно подтверждено , если мы печатаем адрес памяти содержащегося list с помощью id :

     li = [[]] * 3
    print([id(inner_list) for inner_list in li])
    # Out: [6830760, 6830760, 6830760]
    
     

    Решение состоит в том, чтобы создать внутренние списки с помощью цикла:

     li = [[] for _ in range(3)]
    
     

    Вместо того , чтобы создать единый list , а затем сделать 3 ссылки на него, мы теперь создать 3 различные различные списки. Это, опять - таки, может быть проверено с помощью id функции:

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

    Вы также можете сделать это. Это вызывает новый пустой список , который будет создан в каждом append вызова.

     >>> li = []
    >>> li.append([])
    >>> li.append([])
    >>> li.append([])
    >>> for k in li: print(id(k))
    ... 
    4315469256
    4315564552
    4315564808
    
     

    Не используйте индекс для зацикливания последовательности.

    Не рекомендуется :

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

    Есть:

     for elem in tab:
        print(elem)
    
     

    for автоматизируют большинство операций итерации для вас.

    Используйте перечисления , если вам действительно нужно как индекс и элемент.

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

    Будьте внимательны при использовании «==» для проверки на True или False

     if (var == True):
        # this will execute if var is True or 1, 1.0, 1L
    
    if (var != True):
        # this will execute if var is neither True nor 1
    
    if (var == False):
        # this will execute if var is False or 0 (or 0.0, 0L, 0j)
    
    if (var == None):
        # only execute if var is None
    
    if var:
        # execute if var is a non-empty string/list/dictionary/tuple, non-0, etc
    
    if not var:
        # execute if var is "", {}, [], (), 0, None, etc.
    
    if var is True:
        # only execute if var is boolean True, not 1
    
    if var is False:
        # only execute if var is boolean False, not 0
    
    if var is None:
        # same as var == None
    
     

    Не проверяйте, можете ли вы, просто сделайте это и исправьте ошибку

    Питонисты обычно говорят: «Проще просить прощения, чем разрешения».

    Не рекомендуется :

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

    Делать:

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

    Или еще лучше с Python 2.6+ :

     with open(file_path) as file:
    
     

    Это намного лучше, потому что это гораздо более общий характер. Вы можете применить try/except за try/except почти ничего. Вам не нужно заботиться о том, что делать, чтобы предотвратить это, просто заботьтесь об ошибке, которой вы рискуете.

    Не проверять против типа

    Python динамически типизирован, поэтому проверка типа приводит к потере гибкости. Вместо этого используйте утка ввод проверяя поведение. Если вы ожидаете строку в функции, а затем использовать str() , чтобы преобразовать любой объект в строку. Если вы ожидаете список, используйте list() , чтобы преобразовать любую итерацию в список.

    Не рекомендуется :

     def foo(name):
        if isinstance(name, str):
            print(name.lower())
    
    def bar(listing):
        if isinstance(listing, list):
            listing.extend((1, 2, 3))
            return ", ".join(listing)
    
     

    Делать:

     def foo(name) :
        print(str(name).lower())
    
    def bar(listing) :
        l = list(listing)
        l.extend((1, 2, 3))
        return ", ".join(l)
    
     

    Используя последний путь, foo будет принимать любой объект. bar принимает строки, кортежи, наборы, списки и многие другие. Дешевые СУХОЙ.

    Не смешивайте пробелы и табуляции

    Использование объекта в качестве первого родителя

    Это сложно, но это будет кусать вас по мере роста вашей программы. Есть старые и новые классы в Python 2.x . Старые, ну, старые. Им не хватает некоторых функций, и они могут вести себя неловко с наследованием. Чтобы быть пригодным для использования, любой ваш класс должен иметь «новый стиль». Чтобы сделать это, сделать его наследовать от object .

    Не рекомендуется :

     class Father:
        pass
    
    class Child(Father):
        pass
    
     

    Делать:

     class Father(object):
        pass
    
    
    class Child(Father):
        pass
    
     

    В Python 3.x все классы новый стиль , так что вам не нужно делать это.

    Не инициализировать класс атрибутов за пределами метода инициализации

    Люди из других языков находят это заманчивым, потому что это то, что вы делаете на Java или PHP. Вы пишете имя класса, затем перечисляете свои атрибуты и даете им значение по умолчанию. Кажется, это работает в Python, однако, это не работает так, как вы думаете. При этом будут настроены атрибуты класса (статические атрибуты), а затем, когда вы попытаетесь получить атрибут объекта, он даст вам его значение, если оно не пустое. В этом случае он вернет атрибуты класса. Это подразумевает две большие опасности:

    • Если атрибут класса изменен, то начальное значение изменяется.

    • Если вы установите изменяемый объект в качестве значения по умолчанию, вы получите один и тот же объект для всех экземпляров.

    Не (если только вы не хотите статического):

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

    Делать :

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

    Целочисленная и строковая идентичность

    Python использует внутреннее кэширование для целого ряда целых чисел, чтобы уменьшить ненужные накладные расходы от их повторного создания.

    По сути, это может привести к путанице при сравнении целочисленных тождеств:

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

    и, используя другой пример:

     >>> (255 + 1) is (255 + 1)
    True
    >>> (256 + 1) is (256 + 1)
    False
    
     

    Чего ждать?

    Мы можем видеть , что операция идентичности is доходностью True для некоторых целых чисел ( -3 , 256 ) , но не для других ( -8 , 257 ).

    Чтобы быть более точным, целые числа в диапазоне [-5, 256] внутренне кэшируются при запуске и интерпретатора создаются только один раз. Таким образом , они идентичны и сравнивая их идентичность с is дают True ; целые числа вне этого диапазона (обычно) создаются на лету и их идентичности по сравнению с False .

    Это распространенная ошибка, поскольку это общий диапазон для тестирования, но достаточно часто код завершается ошибкой в ​​более позднем этапе подготовки (или, что еще хуже, в производстве) без видимой причины после безупречной работы в разработке.

    Решение состоит в том, чтобы всегда сравнивать значения с помощью равенства ( == ) оператор , а не тождество ( is оператором).


    Python также сохраняет ссылки на часто используемые строки , и может привести к аналогичным странному поведению при сравнении идентичности (т.е. используя is ) строк.

     >>> 'python' is 'py' + 'thon'
    True
    
     

    Строка 'python' обычно используется, поэтому Python имеет один объект, все ссылки на строку 'python' использования.

    Для необычных строк сравнивать идентичность не удается, даже если строки равны.

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

    Таким образом, так же , как правило , для Целые, всегда сравнивать строковые значения , используя равенство ( == ) оператор , а не тождество ( is оператором).

  • 9

    Доступ к атрибутам int литералов

    Возможно, вы слышали, что все в Python - это объект, даже литералы. Это означает , что , например, 7 представляет собой объект , а также, что означает , что имеет атрибуты. Например, один из этих атрибутов является bit_length . Он возвращает количество битов, необходимое для представления значения, для которого он вызывается.

     x = 7
    x.bit_length()
    # Out: 3
    
     

    Видя выше код работает, вы можете интуитивно думать , что 7.bit_length() будет работать так же, только чтобы узнать, что вызывает SyntaxError . Зачем? так как интерпретатор должен различать между выходом атрибута и плавающим числом (например , 7.2 или 7.bit_length() ). Не может, и поэтому возникает исключение.

    Есть несколько способов , чтобы получить доступ к int атрибутов литералов:

     # parenthesis
    (7).bit_length()
    # a space
    7 .bit_length()
    
     

    Используя две точки (как этот 7..bit_length() ) не работает в этом случае, потому что это создает float буквальным и поплавки не имеют bit_length() метод.

    Эта проблема не существует при обращении с float атрибутов литералов , поскольку interperter является „умным“ достаточно , чтобы знать , что float литерал не может содержать два . , например:

     7.2.as_integer_ratio()
    # Out: (8106479329266893, 1125899906842624) 
  • 14

    Цепочка или оператор

    При проверке любого из нескольких сравнений на равенство:

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

    заманчиво сократить это до

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

    Это не верно; or оператор имеет более низкий приоритет , чем == , так что выражение будет оценено как if (a) or (b) or (c == 3): . Правильный способ - это явная проверка всех условий:

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

    С другой стороны , встроенные в any() функции может быть использован вместо сцепленных or операторов:

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

    Или, чтобы сделать его более эффективным:

     if any(x == 3 for x in (a, b, c)): # Right
    
     

    Или, чтобы сделать его короче:

     if 3 in (a, b, c): # Right
    
     

    Здесь мы используем in операторе , чтобы проверить , если значение присутствует в кортеже , содержащем значение , которые мы хотим сравнить против.

    Точно так же неправильно писать

     if a == 1 or 2 or 3:
    
     

    который должен быть написан как

     if a in (1, 2, 3):
    
     
  • 9

    sys.argv [0] - имя исполняемого файла

    Первый элемент sys.argv[0] является именем файла питона выполняется. Остальные элементы являются аргументами скрипта.

     # script.py
    import sys
    
    print(sys.argv[0])
    print(sys.argv)
    
     

    #

     $ python script.py
    => script.py
    => ['script.py']
    
    $ python script.py fizz
    => script.py
    => ['script.py', 'fizz']
    
    $ python script.py fizz buzz
    => script.py
    => ['script.py', 'fizz', 'buzz'] 
  • 23

    Словари неупорядочены

    Можно было бы ожидать словарь Python , чтобы быть отсортированы по клавишам , как, например, в C ++ std::map , но это не так:

     myDict = {'first': 1, 'second': 2, 'third': 3}
    print(myDict)
    # Out: {'first': 1, 'second': 2, 'third': 3}
    
    print([k for k in myDict])
    # Out: ['second', 'third', 'first']
    
     

    В Python нет встроенного класса, который автоматически сортирует свои элементы по ключу.

    Однако, если сортировка не обязательно, и вы просто хотите , чтобы ваш словарь запомнить порядок введения его пар ключ / значение, вы можете использовать collections.OrderedDict :

     from collections import OrderedDict
    
    oDict = OrderedDict([('first', 1), ('second', 2), ('third', 3)])
    
    print([k for k in oDict])
    # Out: ['first', 'second', 'third']
    
     

    Имейте в виду , что инициализация OrderedDict со стандартным словарем не разберется в любом случае словарь для вас. Все , что эта структура делает это , чтобы сохранить порядок ключа вставки.

    Реализация словарей была изменена в Python 3.6 , чтобы улучшить их потребление памяти. Побочным эффектом этой новой реализации является то, что она также сохраняет порядок аргументов ключевых слов, передаваемых в функцию:

    def func (** kw): print (kw.keys ()) func (a = 1, b = 2, c = 3, d = 4, e = 5) dict_keys (['a', 'b', ' c ',' d ',' e ']) # ожидаемый порядок

    Оговорка: берегитесь , что «порядок сохраняющего аспекта этой новой реализации считается деталью реализации и не должен полагаться на» , так как это может измениться в будущем.

  • 6

    Глобальная блокировка интерпретатора (GIL) и блокировка потоков

    Много было написано о Пайтона GIL . Иногда это может привести к путанице при работе с многопоточными (не путать с многопроцессорными) приложениями.

    Вот пример:

    импортировать математику из потоков

     def calc_fact(num):
        math.factorial(num)
    
    num = 600000
    t = Thread(target=calc_fact, daemon=True, args=[num])
    print("About to calculate: {}!".format(num))
    t.start()
    print("Calculating...")
    t.join()
    print("Calculated")
    
     

    Вы ожидали бы увидеть Calculating... распечатывается сразу после того , как поток запускается, мы хотели расчет произойдет в новом потоке в конце концов! Но на самом деле, вы видите, что он напечатан после завершения расчета. Это происходит потому , что новый поток зависит от функции C ( math.factorial ) , которая будет блокировки GIL в то время как он выполняет.

    Есть несколько способов обойти это. Первый - реализовать вашу факториальную функцию на родном Python. Это позволит основному потоку захватить контроль, пока вы находитесь внутри цикла. Недостатком является то, что это решение будет намного медленнее, так как мы не используем функцию C больше.

    def calc_fact (num): "" "Медленная версия факториала в нативном Python" "" res = 1, а num> = 1: res = res * num num - = 1 return res

    Вы также можете sleep в течение периода времени , прежде чем начать выполнение. Примечание: это на самом деле не позволит вашей программе прерывать вычисления, происходящие внутри функции C, но позволит вашему основному потоку продолжить работу после spawn, чего вы и ожидаете.

    def calc_fact (num): сон (0,001) math.factorial (num)

  • 16

    Утечка переменных в списках и циклах

    Рассмотрим следующее понимание списка

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

    Это происходит только в Python 2 из - за того , что список понимание «утечку» в управляющем переменном цикле в окружающей области видимости ( источник ). Такое поведение может привести к труднодоступным найти ошибки , и это было зафиксировано в Python 3.

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

    Аналогично, циклы for не имеют закрытой области видимости для своей переменной итерации.

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

    Этот тип поведения встречается как в Python 2, так и в Python 3.

    Чтобы избежать проблем с утечкой переменных, используйте новые переменные в списках и для циклов по мере необходимости.

  • 3

    Многократный возврат

    Функция xyz возвращает два значения a и b:

     def xyz():
      return a, b
    
     

    Код, вызывающий xyz, сохраняет результат в одной переменной, предполагая, что xyz возвращает только одно значение:

     t = xyz()
    
     

    Значение t фактически кортеж (а, б) , так что любое действие на t предполагая , что это не кортеж может не глубоко в коде с неожиданной ошибкой о кортежах.

    Ошибка типа: тип кортежа не определяет ... метод

    Исправление было бы сделать:

     a, b = xyz()
    
     

    Новичкам будет трудно найти причину этого сообщения, только прочитав сообщение об ошибке кортежа!

  • 2

    Питонические ключи JSON

     my_var = 'bla';
    api_key = 'key';
    ...lots of code here...
    params = {"language": "en", my_var: api_key}
    
     

    Если вы привыкли к JavaScript, оценка переменных в словарях Python будет не такой, как вы ожидаете. Это утверждение в JavaScript приведет к params объекта следующим образом :

     {
        "language": "en",
        "my_var": "key"
    }
    
     

    В Python, однако, это приведет к следующему словарю:

     {
        "language": "en",
        "bla": "key"
    }
    
     

    my_var оценивается и его значение используется в качестве ключа.

Синтаксис

Параметры

Примечания