...

воскресенье, 8 сентября 2013 г.

Python. Неочевидное поведение некоторых конструкций

Рассмотрены примеры таких конструкций + некоторые очевидные, но не менее опасные конструкции, которых в коде желательно избегать. Статья рассчитана на python программистов с опытом 0 — 1,5 года. Опытные разработчики могут в коментах покритиковать или дополнить своими примерами.



1. Lambda.

переменная pw в lambda — ссылка на переменную, а не на значение. К моменту вызова функции переменная pw равна 5






Проблемный код

to_pow = {}
for pw in xrange(5):
to_pow[pw] = lambda x: x ** pw
print to_pow[2](10) # 10000 ???



Решение: Передавать все переменные в lambda явно

to_pow = {}
for pw in xrange(5):
to_pow[pw] = lambda x, pw=pw: x ** pw
print to_pow[2](10) # 100




2. Отличный порядок поиска атрибутов при ромбоидальном наследовании в классах классического и нового стиля











class A():
def field(self):
return 'a'

class B(A):
pass

class C(A):
def field(self):
return 'c'

class Entity(B, C):
pass

print Entity().field() # a !!!





class A():
def field(self):
return 'a'

class B(A):
pass

class C(A, object): # New style class
def field(self):
return 'c'

class Entity(B, C):
pass

print Entity().field() # c !!!




3. Изменяемые объекты в качестве значений по умолчанию







Магия:

def get_data(val=[]):

val.append(1)
return val

print get_data() # [1]
print get_data() # [1, 1] ???
print get_data() # [1, 1, 1] ???



Решение:

def get_data(val=None):
val = val or []
val.append(1)
return val

print get_data() # [1]
print get_data() # [1]




4. Значения по умолчанию инициализируются единожды



import random
def get_random_id(rand_id=random.randint(1, 100)):
return rand_id

print get_random_id() # 53
print get_random_id() # 53 ???
print get_random_id() # 53 ???



5. Не учтена иерархия исключений. Если вы не держите в голове подобные списки docs.python.org/2/library/exceptions.html#exception-hierarchy + списки исключений встроенных модулей + иерархию исключений вашего приложения, а также не используете PyLint. То можно написать следующее:


KeyError никогда не отработает



try:
d = {}
d['xxx']
except LookupError:
print '1'
except KeyError:
print '2'



6. Кэширование интерпретатором коротких строк и чисел



str1 = 'x' * 100
str2 = 'x' * 100
print str1 is str2 # False

str1 = 'x' * 10
str2 = 'x' * 10
print str1 is str2 # True ???


7. Неявная конкатенация.

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



tpl = (
'1_3_dfsf_sdfsf',
'3_11_sdfd_jfg',
'7_17_1dsd12asf sa321fs afsfffsdfs'
'11_19_dfgdfg211123sdg sdgsdgf dsfg',
'13_7_dsfgs dgfdsgdg',
'24_12_dasdsfgs dgfdsgdg',
)


8. Природа булевого типа.

True и False это самые настоящие 1 и 0, для которых придумали специальные название для большей выразительности языка.



try:
print True + 10 / False * 3
except Exception as e:
print e # integer division or modulo by zero


7. Устаревшая конструкция. Опасна тем что потеря слэша, либо перенос только первой части выражения не приводит в очевидным ошибкам. Как решение — использовать круглые скобки.



x = 1 + 2 + 3 \
+ 4


9. Операторы сравнения and, or в отличии например от PHP не возвращают True или False, а возвращают один из элементов сравнения, в этом примере current_lang будет присвоен первый положительный элемент



current_lang = from_GET or from_Session or from_DB or DEFAULT_LANG


Такое выражение как альтернатива тернарному оператору так же будет работать, но стоит обратить внимание, что если первый элемент списка окажется '', 0, False, None, то будет возвращён последний элемент в сравнении, а нет первый элемент списка.



a = ['one', 'two', 'three']
print a and a[0] or None # one


10. Перехватить все исключения и при этом никак их не обработать. В этом примере ничего необычного не происходит, но такая конструкция таит в себе опасность и весьма популярна среди начинающих. Так писать не стоит, даже если вы уверены на 100% что исключение можно никак не обрабатывать, так как это не гарантирует что другой разработчик не допишет в блок try-except строку, исключение от которой хотелось бы всё таки зафиксировать. Решение: пишите в лог, конкретизируйте перехватываемый тип исключений, обрамляйте в try-except лишь минимально необходимый кусок кода.



try:
# Много кода, чем больше тем хуже
except Exception:
pass


11. Переопределение объектов из bulit-in. В данном случае переопределяется объекты list, id, type. Использовать классические id, type в функции и класс list в модуле привычным образом не получится. Как решение — установить PyLint в свою IDE и следить что он подсказывает.



def list(id=DEFAULT_ID, type=TYPES.ANY_TYPE):
"""
W0622 Redefining built-in "id" [pylint]
"""

return Item.objects.filter(id=id, item_type=type)


12. Работающий код, без сюрпризов… для вас… А вот другой разработчик, использующий mod2.py весьма удивится, заметив, что один из атрибутов модуля вдруг неожиданно изменился. Как решение стараться избегать таких действий, или хотяб вводить в mod2.py функцию для переопределения атрибута. Тогда изучая mod2.py можно будет хотя б понять, что один из атрибутом модуля может меняться.



# mod1.py
import mod2
mod2.any_attr = '123'


PS: Критика, замечания, дополнения приветствуются.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



Комментариев нет:

Отправить комментарий