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

в 21:24, , рубрики: Без рубрики

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

Python. Неочевидное поведение некоторых конструкций
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
>>> 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'.

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: Критика, замечания, дополнения приветствуются.

Автор: niko83

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js