Pull to refresh

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

Reading time 4 min
Views 34K
Рассмотрены примеры таких конструкций + некоторые очевидные, но не менее опасные конструкции, которых в коде желательно избегать. Статья рассчитана на 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]


val = val or [] выглядит короче и вполне приемлем, но если на входе в функцию не передаются 0, пустая строка, False и т.д. Тогда надо делать проверку is None, как и описано в gogle-style google-styleguide.googlecode.com/svn/trunk/pyguide.html?showone=Default_Argument_Values#Default_Argument_Values

(Кто не читал этот документ — советую обязательно заглянуть.)

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


>>> type(True).__mro__
(<type 'bool'>, <type 'int'>, <type 'object'>)


docs.python.org/release/2.3.5/whatsnew/section-bool.html
Python's Booleans were added with the primary goal of making code clearer.

To sum up True and False in a sentence: they're alternative ways to spell the integer values 1 and 0, with the single difference that str() and repr() return the strings 'True' and 'False' instead of '1' and '0'.


8*. Устаревшая конструкция. Опасна тем что потеря слэша, либо перенос только первой части выражения не приводит в очевидным ошибкам. Как решение — использовать круглые скобки.
x = 1 + 2 + 3 \
+ 4

ps: номер пункта 8* обусловлен тем что изначально в статье присутствовало по ошибке два 7-ых пункта. Чтоб не сбивать нумерацию (так как на неё уже есть ссылки в коментах) пришлось ввести такое обозначение.

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. Переопределение объектов из built-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)  


Если уж переписать код никак нельзя, то обращаться к built-in функциям придётся так:
def list(id=None):
    print(__builtins__.id(list))


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

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


UPD: Полезная ссылка по теме от lega: Hidden features of Python

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

Articles