Общие подводные камни в Python

Возможный дубликат:
Python 2.x gotcha & rsquo; s и наземные мины

Сегодня я снова был укушен изменчивыми аргументами по умолчанию через много лет. Обычно я не использую изменчивые аргументы по умолчанию, если это необходимо, но со временем я забыл об этом. Сегодня в приложении я добавил tocElements = [] в список аргументов функции генерации PDF, и теперь "Оглавление" становится все длиннее и длиннее после каждого вызова "generate pdf".:)

Что еще я должен добавить в список вещей, которые ДОЛЖНЫ избежать?

  • Всегда импортируйте модули таким же образом, например. from y import x и import x рассматриваются как разные модули.

  • Не используйте диапазон вместо списков, потому что range() станет итератором в любом случае, следующее:

    myIndexList = [0, 1, 3]
    isListSorted = myIndexList == range(3)  # will fail in 3.0
    isListSorted = myIndexList == list(range(3))  # will not
    

    То же самое можно ошибочно сделать с помощью xrange:

    myIndexList == xrange(3)
    
  • Будьте внимательны при поиске нескольких типов исключений:

    try:
        raise KeyError("hmm bug")
    except KeyError, TypeError:
        print TypeError
    

    Это печатает "hmm bug", хотя это не ошибка; похоже, мы ловим исключения обоих типов, но вместо этого мы ловим KeyError только как переменную TypeError, используйте это вместо:

    try:
        raise KeyError("hmm bug")
    except (KeyError, TypeError):
        print TypeError
    

Ответ 1

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

Не выполнять:

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

Do:

for elem in tab :
    print elem

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

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

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

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

Pythonistas обычно говорят: "Легче просить прощения, кроме разрешения".

Не выполнять:

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

Do:

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

Или даже лучше с python 2.6/3:

with open(file_path) as file :

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

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

Python динамически типизируется, поэтому проверка типа позволяет вам потерять гибкость. Вместо этого используйте утиную печать, проверяя поведение. E.G, вы ожидаете строку в функции, а затем используйте 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)

Do:

def foo(name) :
    print str(name).lower()

def bar(listing) :
    l = list(listing)
    l.extend((1, 2, 3))
    return ", ".join(l)

Используя последний способ, foo примет любой объект. Бар будет принимать строки, кортежи, наборы, списки и многое другое. Дешевые DRY: -)

Не смешивайте пространства и вкладки

Просто не надо. Вы плачете.

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

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

Не выполнять:

class Father :
    pass

class Child(Father) :
    pass

Do:

class Father(object) :
    pass


class Child(Father) :
    pass

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

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

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

Выполнение этого будет определять атрибуты класса (статические атрибуты), а затем, когда вы попытаетесь получить атрибут объекта, он даст вам значение, если оно не будет пустым. В этом случае он вернет атрибуты класса.

Это подразумевает две большие опасности:

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

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

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

Do:

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

Ответ 2

Если вам нужна группа массивов, у вас может возникнуть соблазн ввести что-то вроде этого:

>>> a=[[1,2,3,4,5]]*4

И, конечно же, это даст вам то, что вы ожидаете, когда посмотрите на него.

>>> from pprint import pprint
>>> pprint(a)

[[1, 2, 3, 4, 5],
 [1, 2, 3, 4, 5],
 [1, 2, 3, 4, 5],
 [1, 2, 3, 4, 5]]

Но не ожидайте, что элементы вашего населения будут отдельными объектами:

>>> a[0][0] = 2
>>> pprint(a)

[[2, 2, 3, 4, 5],
 [2, 2, 3, 4, 5],
 [2, 2, 3, 4, 5],
 [2, 2, 3, 4, 5]]

Если это не то, что вам нужно...

Следует упомянуть обходное решение:

a = [[1,2,3,4,5] for _ in range(4)]

Ответ 3

Язык Python Gotchas - вещи, которые терпят неудачу очень неясными способами

  • Использование изменяемых аргументов по умолчанию.

  • Ведущие нули означают восьмеричные. 09 - очень неясная синтаксическая ошибка в Python 2.x

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

Python Design Gotchas

  • Время ожидания при интроспекции (например, попытка автоматического определения типов или суперкласса или других вещей). Во-первых, это очевидно из чтения источника. Что еще более важно, время, затрачиваемое на странную интроспекцию Python, обычно указывает на фундаментальное нарушение понимания полиморфизма. 80% вопросов интроспекции Python на SO - неспособность получить полиморфизм.

  • Время ожидания на гольф-поле. Просто потому, что ваша ментальная модель вашего приложения - это четыре ключевых слова ( "делать", "что", "я", "означает" ), не означает, что вы должны создать гиперкомплексную интроспективную структуру, ориентированную на декоратора, для этого. Python позволяет принимать DRY до уровня, который является глупостью. Остальные вопросы интроспекции Python на SO пытаются уменьшить сложные проблемы, чтобы кодировать упражнения для гольфа.

  • Monkeypatching.

  • Невозможно фактически прочитать стандартную библиотеку и изобретать колесо.

  • Конфигурирование интерактивного типа - вы идете на Python с соответствующей программой. Пока вы вводите текст в интерактивном режиме, вы можете потерять отслеживание переменной и использовать globals(). Кроме того, пока вы печатаете, почти все глобально. В правильных программах вы никогда не "потеряете" переменную, и ничто не будет глобальным.

Ответ 4

Мутирование аргумента по умолчанию:

def foo(bar=[]):
    bar.append('baz')
    return bar

Значение по умолчанию оценивается только один раз, а не каждый раз, когда вызывается функция. Повторные вызовы foo() вернут ['baz'], ['baz', 'baz'], ['baz', 'baz', 'baz'],...

Если вы хотите, чтобы mutate bar сделал что-то вроде этого:

def foo(bar=None):
    if bar is None:
        bar = []

    bar.append('baz')
    return bar

Или, если вы хотите, чтобы аргументы были окончательными:

def foo(bar=[]):
    not_bar = bar[:]

    not_bar.append('baz')
    return not_bar

Ответ 5

Я не знаю, является ли это распространенной ошибкой, но пока у Python нет операторов приращения и декремента, допускаются двойные знаки, поэтому

++i

и

--i

является синтаксически правильным кодом, но не делает ничего полезного или что вы можете ожидать.

Ответ 6

Перемещение собственного кода перед тем, как смотреть в стандартную библиотеку. Например, написав это:

def repeat_list(items):
    while True:
        for item in items:
            yield item

Когда вы можете просто использовать это:

from itertools import cycle

Примеры часто забываемых модулей (кроме itertools) включают:

  • optparse для создания парсеров командной строки
  • ConfigParser для стандартного чтения файлов конфигурации
  • tempfile для создания и управления временными файлами
  • shelve для хранения объектов Python на диск, удобно, когда полная база данных переполнена

Ответ 7

Избегайте использования ключевых слов в качестве собственных идентификаторов.

Кроме того, всегда полезно не использовать from somemodule import *.

Ответ 8

Если вы пришли из С++, убедитесь, что переменные, объявленные в определении класса, являются статическими. Вы можете инициализировать нестатические члены в методе init.

Пример:

class MyClass:
  static_member = 1

  def __init__(self):
    self.non_static_member = random()

Ответ 9

Удивлен, что никто этого не сказал:

Смешайте вкладку и пробелы при отступы.

Действительно, это убийца. Поверь мне. В частности, если он работает.

Ответ 10

Не использовать функциональные инструменты. Это не просто ошибка с точки зрения стиля, это ошибка с точки зрения скорости, потому что многие функциональные инструменты оптимизированы на C.

Это наиболее распространенный пример:

temporary = []
for item in itemlist:
    temporary.append(somefunction(item))
itemlist = temporary

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

itemlist = map(somefunction, itemlist)

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

itemlist = [somefunction(x) for x in itemlist]

И если вам нужны только обработанные элементы, доступные по одному, а не все одновременно, вы можете сохранить память и повысить скорость с помощью итеративных эквивалентов

# itertools-based iterator
itemiter = itertools.imap(somefunction, itemlist)
# generator expression-based iterator
itemiter = (somefunction(x) for x in itemlist)

Ответ 12

Импортировать re и использовать полный подход к регулярному выражению для сопоставления строк/преобразования, когда для любой общей операции существуют хорошие строковые методы (например, капитализация, простое сопоставление/поиск).

Ответ 13

Последняя ссылка является исходной, этот вопрос SO является дубликатом.

Ответ 14

Нормальное копирование (присвоение) выполняется по ссылке, поэтому заполнение контейнера путем адаптации одного и того же объекта и вставки заканчивается контейнером со ссылками на последний добавленный объект.

Используйте copy.deepcopy вместо этого.

Ответ 15

Использование форматера %s в сообщениях об ошибках. Почти во всех обстоятельствах следует использовать %r.

Например, представьте код следующим образом:

try:
    get_person(person)
except NoSuchPerson:
    logger.error("Person %s not found." %(person))

Отпечатано эта ошибка:

ERROR: Person wolever not found.

Невозможно определить, является ли переменная person строкой "wolever", строкой unicode u"wolever" или экземпляром класса person (который имеет __str__, определенный как def __str__(self): return self.name). Принимая во внимание, что если использовалось %r, было бы три разных сообщения об ошибках:

...
logger.error("Person %r not found." %(person))

Произведет гораздо более полезные ошибки:

ERROR: Person 'wolever' not found.
ERROR: Person u'wolever' not found.
ERROR: Person  not found.

Еще одна веская причина в том, что пути намного проще копировать/вставлять. Представьте себе:

try:
    stuff = open(path).read()
except IOError:
    logger.error("Could not open %s" %(path))

Если path - some path/with 'strange' "characters", сообщение об ошибке будет:

ERROR: Could not open some path/with 'strange' "characters"

Что трудно визуально разобрать и трудно скопировать/вставить в оболочку.

В то время как если используется %r, ошибка будет:

ERROR: Could not open 'some path/with \'strange\' "characters"'

Легко визуально разобрать, легко скопировать-вставить, все вокруг лучше.

Ответ 16

Я бы прекратил использовать устаревшие методы в версии 2.6, так что ваше приложение или script будет готово и проще конвертировать в Python 3.

Ответ 17

Плохая привычка, из-за которой я должен был тренироваться, использовал X and Y or Z для встроенной логики.

Если вы не можете 100% всегда гарантировать, что Y будет истинным значением, даже если ваш код изменится через 18 месяцев, вы настроитесь на неожиданное поведение.

К счастью, в более поздних версиях вы можете использовать Y if X else Z.

Ответ 18

Некоторые личные мнения, но я считаю лучше не:

  • использовать устаревшие модули (используйте для них предупреждения)

  • чрезмерно использовать классы и наследование (типично для старых статических языков)

  • явно использовать декларативные алгоритмы (как итерация с for против использования itertools)

  • переопределить функции из стандартного lib, "потому что мне не нужны все эти функции"

  • используя для этого функции (уменьшая совместимость со старыми версиями Python)

  • использование метаклассов, когда вам действительно не нужно, и, в общем, делать вещи слишком "волшебными"

  • избегать использования генераторов

  • (более личный) попробуйте микро-оптимизировать CPython-код на низкоуровневой основе. Лучше тратьте время на алгоритмы, а затем оптимизируйте, создав небольшой C-общий lib, вызываемый ctypes (его так легко получить 5-кратное повышение на внутреннем контуре)

  • используйте ненужные списки, когда итераторов хватит

  • запрограммируйте проект непосредственно для 3.x до того, как все доступные вам библиотеки будут доступны (этот момент может быть немного противоречивым сейчас!)

Ответ 19

import this    

Красивая лучше, чем уродливая.
Явный лучше, чем неявный.
Простой лучше, чем сложный.
Комплекс лучше, чем сложный.
Плоский лучше, чем вложенный.
Редкий лучше, чем плотный. Показатели удобочитаемости.
Особые случаи не являются достаточно сложными, чтобы нарушать правила.
Хотя практичность превосходит чистоту.
Ошибки никогда не должны проходить молча.
Если явно не отключен.
Перед лицом двусмысленности откажитесь от соблазна угадать.
Должен быть один - и желательно только один - простой способ сделать это.
Хотя этот путь может быть не очевидным, если вы не голландский. Теперь лучше, чем никогда. Хотя никогда не бывает лучше, чем сейчас. Если внедрение трудно объяснить, это плохая идея.
Если внедрение легко объяснить, это может быть хорошей идеей.
Пространства имен - одна хорошая идея - пусть больше таких!

import not_this

Напишите уродливый код.
Напишите неявный код.
Напишите сложный код.
Впишите вложенный код.
Напишите плотный код.
Написать нечитаемый код.
Напишите специальные случаи. Стремитесь к чистоте.
Игнорировать ошибки и исключения.
Напишите оптимальный код перед выпуском.
Для каждой реализации требуется блок-схема.
Не используйте пространства имен.

Ответ 20

Я начал изучать Python, и одна из самых больших ошибок, которые я сделал, постоянно использует С++/С# с индексом для цикла. Python для цикла типа (i; я < length; я ++) и по уважительной причине - в большинстве случаев есть лучшие способы сделать то же самое.

Пример: У меня был метод, который повторялся по списку и возвращал индексы выбранных элементов:

for i in range(len(myList)):
    if myList[i].selected:
        retVal.append(i)

Вместо этого у Python есть понимание по спискам, которое решает одну и ту же задачу более элегантно и легко читается:

retVal = [index for index, item in enumerate(myList) if item.selected]

Ответ 21

Никогда не предполагайте, что наличие многопоточного приложения Python и машины, поддерживающей SMP (например, одна из которых оснащена многоядерным процессором), даст вам преимущество в представлении истинного parallelism в ваше приложение. Скорее всего, это не из-за GIL (Global Interpreter Lock), который синхронизирует ваше приложение на уровне интерпретатора байтового кода.

Есть некоторые обходные пути, например, использование SMP путем помещения параллельного кода в вызовы API C или использование нескольких процессов (вместо потоков) через обертки (например, как один из доступных в http://www.parallelpython.org), но если для Python нужна истинная многопоточность, нужно смотреть на такие вещи, как Jython, IronPython и т.д. (GIL - это функция интерпретатора CPython, поэтому другие реализации не затрагиваются).

В соответствии с Python 3000 часто задаваемых вопросов (доступно в Artima) выше все еще стоит даже для последних версий Python.

Ответ 22

++n и --n могут работать не так, как ожидалось, людьми из C или Java.

++n положительно положительное число, которое просто n.

--n отрицателен от отрицательного числа, что просто n.

Ответ 23

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

def func1(toc=None):
    if not toc:
        toc = []
    toc.append('bar')

def func2(toc=None):
    if toc is None:
        toc = []
    toc.append('bar')

def demo(toc, func):
    print func.__name__
    print '  before:', toc
    func(toc)
    print '  after:', toc

demo([], func1)
demo([], func2)

Здесь вывод:

func1
  before: []
  after: []
func2
  before: []
  after: ['bar']

Ответ 24

Самая первая ошибка, перед которой вы даже начинаете: не бойтесь пробелов.

Когда вы показываете кому-то фрагмент кода Python, они впечатлены, пока вы не скажете им, что они должны иметь отступ правильно. По некоторым причинам большинство людей считают, что язык не должен навязывать им определенный стиль, в то время как все они будут отступать от кода тем не менее.

Ответ 25

my_variable = <something>
...
my_varaible = f(my_variable)
...
use my_variable and thinking it contains the result from f, and not the initial value

Python никоим образом не предупредит вас, что при втором назначении вы ошибочно написали имя переменной и создали новую.

Ответ 26

Вы упомянули аргументы по умолчанию... То, что почти так же плохо, как изменяемые аргументы по умолчанию: значения по умолчанию не являются None.

Рассмотрим функцию, которая будет готовить пищу:

def cook(breakfast="spam"):
    arrange_ingredients_for(breakfast)
    heat_ingredients_for(breakfast)
    serve(breakfast)

Поскольку он задает значение по умолчанию для breakfast, для какой-либо другой функции невозможно сказать "приготовить по умолчанию завтрак" без специального случая:

def order(breakfast=None):
    if breakfast is None:
        cook()
    else:
        cook(breakfast)

Однако этого можно избежать, если cook используется None как значение по умолчанию:

def cook(breakfast=None):
    if breakfast is None:
        breakfast = "spam"

def order(breakfast=None):
    cook(breakfast)

Хорошим примером этого является ошибка Django # 6988. В модуле кэширования Django была функция "сохранить в кеш", которая выглядела так:

def set(key, value, timeout=0):
    if timeout == 0:
        timeout = settings.DEFAULT_TIMEOUT
    _caching_backend.set(key, value, timeout)

Но для memcached-бэкенда тайм-аут 0 означает "никогда не тайм-аут"... Который, как вы можете видеть, было бы невозможно указать.

Ответ 27

Не изменяйте список, повторяя его.

odd = lambda x : bool(x % 2)
numbers = range(10)
for i in range(len(numbers)):
    if odd(numbers[i]):
        del numbers[i]

Одно общее предложение для решения этой проблемы состоит в том, чтобы перебирать список в обратном порядке:

for i in range(len(numbers)-1,0,-1):
    if odd(numbers[i]):
        del numbers[i]

Но еще лучше использовать понимание списка для создания нового списка для замены старого:

numbers[:] = [n for n in numbers if not odd(n)]

Ответ 28

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

def x(a, l=[]):
    l.append(a)
    return l

print x(1)
print x(2)

печатает:

[1]
[1, 2]

то есть. вы всегда получаете тот же список.

Ответ 29

Подобно mutable аргументам по умолчанию является атрибут mutable class.

>>> class Classy:
...    foo = []
...    def add(self, value):
...        self.foo.append(value)
... 
>>> instance1 = Classy()
>>> instance2 = Classy()
>>> instance1.add("Foo!")
>>> instance2.foo
['Foo!']

Не то, что вы ожидаете.

Ответ 30

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