Распространенные подводные камни в Python
Возможный дубликат:
Python 2.x Гоча и мины
Сегодня меня снова укусили изменчивые аргументы по умолчанию после многих лет. Я обычно не использую изменяемые аргументы по умолчанию, если в этом нет необходимости, но я думаю, что со временем я забыл об этом. Сегодня в приложении я добавил 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
Это печатает "хм ошибка", хотя это не ошибка; похоже, что мы ловим исключения обоих типов, но вместо этого мы ловим KeyError только как переменную TypeError, вместо этого используйте это:
try: raise KeyError("hmm bug") except (KeyError, TypeError): print TypeError
34 ответа
Не используйте индекс для зацикливания последовательности
Не:
for i in range(len(tab)) :
print tab[i]
Делать:
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
Не проверяйте, можете ли вы, просто сделайте это и исправьте ошибку
Питонисты обычно говорят: "Проще просить прощения, чем разрешения".
Не:
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+ / 3:
with open(file_path) as file :
Это намного лучше, потому что это намного более общее. Вы можете применить "попробовать / исключить" практически ко всему. Вам не нужно заботиться о том, что делать, чтобы предотвратить это, просто об ошибке, которой вы рискуете.
Не проверять против типа
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 примет любой объект. Бар будет принимать строки, кортежи, наборы, списки и многое другое. Дешево СУХОЙ:-)
Не смешивайте пробелы и табуляции
Просто не надо. Вы бы плакали.
Использоватьобъект в качестве первого родителя
Это сложно, но это будет кусать вас по мере роста вашей программы. В Python 2.x есть старые и новые классы. Старые, ну, старые. Им не хватает некоторых функций, и они могут вести себя неловко с наследованием. Чтобы быть пригодным для использования, любой ваш класс должен быть "нового стиля". Для этого сделайте так, чтобы он наследовал от "объекта":
Не:
class Father :
pass
class Child(Father) :
pass
Делать:
class Father(object) :
pass
class Child(Father) :
pass
В Python 3.x все классы имеют новый стиль, поэтому вы можете объявитьclass Father:
Это хорошо.
Не инициализируйте атрибуты класса за пределами__init__
метод
Люди, пришедшие с других языков, находят это заманчивым, потому что вы выполняете свою работу на 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()]
Когда вам нужна совокупность массивов, у вас может возникнуть желание набрать что-то вроде этого:
>>> 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)]
Python Language Gotchas - вещи, которые терпят неудачу очень неясными способами
Использование изменяемых аргументов по умолчанию.
Ведущие нули означают восьмеричные.
09
очень неясная синтаксическая ошибка в Python 2.xНеправильные имена переопределенных методов в суперклассе или подклассе. Ошибка неверного написания суперкласса хуже, потому что ни один из подклассов не переопределяет его правильно.
Python Design Gotchas
Тратить время на самоанализ (например, пытаться автоматически определять типы, идентичность суперкласса или другие вещи). Во-первых, это очевидно из чтения источника. Что еще более важно, время, потраченное на странную интроспекцию Python, обычно указывает на фундаментальную неспособность понять полиморфизм. 80% вопросов самоанализа Python по SO - неспособность получить полиморфизм.
Тратить время на код гольф. То, что ваша ментальная модель вашего приложения состоит из четырех ключевых слов ("делать", "что", "я", "имею в виду"), не означает, что для этого вам нужно создать гиперкомплексную интроспективную управляемую декоратором среду. Python позволяет вам сделать СУХОЙ до уровня глупости. Остальные вопросы о самоанализе Python о SO пытаются свести сложные проблемы к программированию упражнений в гольфе.
Monkeypatching.
Неспособность фактически прочитать стандартную библиотеку и заново изобрести колесо.
Объединяя интерактивный тип-как-вы идете Python с соответствующей программой. Когда вы печатаете в интерактивном режиме, вы можете потерять отслеживание переменной и вам придется использовать
globals()
, Кроме того, пока вы печатаете, почти все глобально. В правильных программах вы никогда не потеряете переменную, и ничто не будет глобальным.
Отключение аргумента по умолчанию:
def foo(bar=[]):
bar.append('baz')
return bar
Значение по умолчанию оценивается только один раз, а не каждый раз, когда вызывается функция. Повторные звонки на foo()
вернется ['baz']
, ['baz', 'baz']
, ['baz', 'baz', 'baz']
...
Если вы хотите изменить строку, сделайте что-то вроде этого:
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
Я не знаю, является ли это распространенной ошибкой, но хотя в Python нет операторов увеличения и уменьшения, допускаются двойные знаки, поэтому
++i
а также
--i
является синтаксически правильным кодом, но не делает ничего "полезного" или того, что вы, возможно, ожидаете.
Прокручивая свой собственный код, прежде чем заглядывать в стандартную библиотеку Например, написать это:
def repeat_list(items):
while True:
for item in items:
yield item
Когда вы можете просто использовать это:
from itertools import cycle
Примеры часто пропускаемых модулей (помимо itertools
) включают:
optparse
для создания парсеров командной строкиConfigParser
для чтения файлов конфигурации стандартным способомtempfile
для создания и управления временными файламиshelve
для хранения объектов Python на диск, удобно, когда полноценная база данных перегружена
Избегайте использования ключевых слов в качестве собственных идентификаторов.
Кроме того, это всегда хорошо, чтобы не использовать from somemodule import *
,
Удивлен, что никто не сказал этого:
Смешайте табуляцию и пробелы при отступе.
На самом деле, это убийца. Поверь мне. В частности, если он работает.
Не используя функциональные инструменты. Это не просто ошибка с точки зрения стиля, это ошибка с точки зрения скорости, потому что многие функциональные инструменты оптимизированы в 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)
Если вы пришли из C++, то поймите, что переменные, объявленные в определении класса, являются статическими. Вы можете инициализировать нестатические элементы в методе init.
Пример:
class MyClass:
static_member = 1
def __init__(self):
self.non_static_member = random()
Обычное копирование (присваивание) выполняется по ссылке, поэтому при заполнении контейнера путем адаптации и вставки того же объекта получается контейнер со ссылками на последний добавленный объект.
использование copy.deepcopy
вместо.
Импорт re
и использование подхода с полным регулярным выражением для сопоставления / преобразования строк, когда для каждой обычной операции существуют совершенно хорошие строковые методы (например, использование заглавных букв, простое сопоставление / поиск).
С использованием %s
Форматер в сообщениях об ошибках. Почти в любых обстоятельствах %r
должен быть использован.
Например, представьте код, подобный этому:
try:
get_person(person)
except NoSuchPerson:
logger.error("Person %s not found." %(person))
Напечатал эту ошибку:
ОШИБКА: Человек не найден.
Невозможно сказать, если person
переменная это строка "wolever"
Строка Юникода u"wolever"
или экземпляр Person
класс (который имеет __str__
определяется как def __str__(self): return self.name
). Тогда как, если %r
было бы три разных сообщения об ошибке:
...
logger.error("Person %r not found." %(person))
Будет производить гораздо более полезные ошибки:
ОШИБКА: Человек 'wolever' не найден. ОШИБКА: Человек, которого нет, не найден. ОШИБКА: человек не найден.
Еще одна веская причина для этого заключается в том, что пути намного проще копировать / вставлять. Представить:
try:
stuff = open(path).read()
except IOError:
logger.error("Could not open %s" %(path))
Если path
является some path/with 'strange' "characters"
сообщение об ошибке будет:
ОШИБКА: не удалось открыть какой-либо путь / со "странными" "символами"
Который трудно визуально разобрать и трудно скопировать / вставить в оболочку.
Тогда как, если %r
используется, ошибка будет:
ОШИБКА: не удалось открыть "какой-то путь" с "странными" "символами"
Легко визуально разбирать, легко копировать-вставлять, все вокруг лучше.
- не пишите большие выходные сообщения в стандартный вывод
- Строки являются неизменяемыми - создавайте их не используя оператор "+", а используя функцию str.join().
- прочитайте эти статьи:
Последняя ссылка является оригинальной, этот вопрос является дубликатом.
Я бы прекратил использовать устаревшие методы в 2.6, чтобы ваше приложение или скрипт были готовы и их было легче конвертировать в Python 3.
Дурная привычка, из которой я должен был тренироваться, использовала X and Y or Z
для встроенной логики.
Если вы не можете на 100% всегда гарантировать, что Y
будет истинным значением, даже если ваш код изменится через 18 месяцев, вы настроите себя на неожиданное поведение.
К счастью, в более поздних версиях вы можете использовать Y if X else Z
,
Я также начал изучать Python, и одна из самых больших ошибок, которые я допустил, это постоянное использование C++/C# с индексированным циклом for. У Python есть цикл типа (i; i Пример: у меня был метод, который перебрал список и возвратил индексы выбранных элементов: Вместо этого в Python есть списки, которые решают ту же проблему более элегантным и легким для чтения способом:for i in range(len(myList)):
if myList[i].selected:
retVal.append(i)
retVal = [index for index, item in enumerate(myList) if item.selected]
Некоторые личные мнения, но я считаю, что лучше НЕ:
использовать устаревшие модули (используйте для них предупреждения)
чрезмерное использование классов и наследование (возможно, типично для статических языков)
явно использовать декларативные алгоритмы (как итерация с
for
против использованияitertools
)переопределить функции из стандартной библиотеки, "потому что мне не нужны все эти функции"
использование возможностей ради этого (снижение совместимости со старыми версиями Python)
использование метаклассов, когда вам действительно не нужно, и, в более общем смысле, делайте вещи слишком "волшебными"
избегать использования генераторов
(более личный) попробуйте микрооптимизировать код CPython на низкоуровневой основе. Лучше потратить время на алгоритмы, а затем оптимизировать, сделав небольшую общую библиотеку C, называемую
ctypes
(так легко получить 5-кратное усиление во внутренней петле)использовать ненужные списки, когда итераторов будет достаточно
закодируйте проект непосредственно для 3.x, прежде чем все необходимые библиотеки будут доступны (этот момент может быть немного спорным!)
++n
а также --n
может работать не так, как ожидалось людьми из C или Java.
++n
является положительным положительным числом, которое просто n
,
--n
отрицательно от отрицательного числа, которое просто n
,
Никогда не думайте, что наличие многопоточного приложения Python и компьютера с поддержкой SMP (например, оснащенного многоядерным процессором) даст вам преимущество введения истинного параллелизма в ваше приложение. Скорее всего, это не из-за GIL (Global Interpreter Lock), который синхронизирует ваше приложение на уровне интерпретатора байт-кода.
Есть некоторые обходные пути, такие как использование преимущества SMP путем помещения параллельного кода в вызовы C API или использования нескольких процессов (вместо потоков) через оболочки (например, как тот, который доступен на http://www.parallelpython.org/), но если один в Python нужна настоящая многопоточность, нужно взглянуть на такие вещи, как Jython, IronPython и т. д. (GIL является функцией интерпретатора CPython, поэтому другие реализации не затрагиваются).
В соответствии с часто задаваемыми вопросами по Python 3000 (доступны в Artima), вышеприведенное относится даже к последним версиям Python.
import this
Красиво лучше, чем безобразно.
Явное лучше, чем неявное.
Простое лучше, чем сложное.
Сложный лучше, чем сложный.
Квартира лучше, чем вложенная.
Разреженный лучше, чем плотный.
Читаемость имеет значение.
Особые случаи не настолько особенные, чтобы нарушать правила.
Хотя практичность превосходит чистоту.
Ошибки никогда не должны проходить бесшумно.
Если явно не молчать.
Перед лицом двусмысленности откажитесь от соблазна гадать.
Должен быть один - и желательно только один - очевидный способ сделать это.
Хотя этот путь может быть неочевидным на первый взгляд, если вы не голландец.
Сейчас лучше, чем никогда.
Хотя никогда не бывает лучше, чем сейчас.
Если реализацию сложно объяснить, это плохая идея.
Если реализацию легко объяснить, это может быть хорошей идеей.
Пространства имен - одна из отличных идей - давайте сделаем больше!
import not_this
Напишите некрасивый код.
Написать неявный код.
Написать сложный код.
Написать вложенный код.
Написать плотный код.
Написать нечитаемый код.
Напишите особые случаи.
Стремитесь к чистоте.
Игнорировать ошибки и исключения.
Напишите оптимальный код перед выпуском.
Каждой реализации нужна блок-схема.
Не используйте пространства имен.
Не изменяйте список, перебирая его.
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)]
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 никоим образом не предупредит вас, что во втором назначении вы неправильно написали имя переменной и создали новое.
В некоторой степени связанный с изменяемым аргументом по умолчанию, как проверка "отсутствующего" случая приводит к различиям при передаче пустого списка:
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']
Вы упомянули аргументы по умолчанию... Тот, который почти так же плох, как изменяемые аргументы по умолчанию: значения по умолчанию, которые не являются 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
означает "никогда не истекает"... Что, как вы можете видеть, было бы невозможно определить.
Самая первая ошибка, прежде чем вы даже начнете: не бойтесь пробелов.
Когда вы показываете кому-то кусочек кода Python, он впечатлен, пока вы не скажете ему, что он должен делать отступ правильно. По некоторым причинам, большинство людей считают, что язык не должен навязывать им определенный стиль, в то время как все они, тем не менее, будут отступать от кода.
Общая ошибка: аргументы по умолчанию оцениваются один раз:
def x(a, l=[]):
l.append(a)
return l
print x(1)
print x(2)
печатает:
[1]
[1, 2]
т.е. вы всегда получаете один и тот же список.
Создание локального модуля с тем же именем, что и у stdlib. Это почти всегда происходит случайно (как сообщается в этом вопросе), но обычно приводит к загадочным сообщениям об ошибках.
Беспорядочная обработка исключений
Это то, что я вижу удивительное количество в производственном коде, и это заставляет меня съеживаться.
try:
do_something() # do_something can raise a lot errors e.g. files, sockets
except:
pass # who cares we'll just ignore it
Было ли исключение то, которое вы хотите подавить, или оно более серьезное? Но есть и более тонкие случаи. Это может заставить вас вырвать свои волосы, пытаясь понять.
try:
foo().bar().baz()
except AttributeError: # baz() may return None or an incompatible *duck type*
handle_no_baz()
Проблема в том, что виноваты могут быть также foo или baz. Я думаю, что это может быть более коварным, потому что это идиоматический питон, где вы проверяете свои типы на правильные методы. Но у каждого вызова метода есть шанс вернуть что-то неожиданное и подавить ошибки, которые должны вызывать исключения.
Знание того, какие исключения может вызвать метод, не всегда очевидно. Например, urllib и urllib2 используют сокет, который имеет свои собственные исключения, которые просачиваются вверх и назад по уродливой голове, когда вы меньше всего этого ожидаете.
Обработка исключений является благом производительности при обработке ошибок на языках системного уровня, таких как C. Но я обнаружил, что подавление исключений может неправильно создавать действительно таинственные сеансы отладки и лишать главное преимущество, предоставляемое интерпретируемыми языками.