Рубрики
Без рубрики

Python Dispalls – ожидая неожиданного

Независимо от того, какой язык программирования вы кодируете, вы, вероятно, столкнулись с хорошим куском … Теги с Python, WebDev, учебником.

Независимо от того, на каком языке программирования вы кодируете, вы, вероятно, столкнулись с хорошим куском странных и, казалось бы, необъяснимых вопросов, которые оказались действительно глупыми ошибками или причудами этого конкретного языка. Python направлен на то, чтобы быть чистым и простым языком, но он также имеет свою часть Gotchas и quirks, которые могут удивить как начинающих, так и опытных разработчиков программного обеспечения. Итак, чтобы избежать ненужной ярости и разочарования по какой-то странной проблеме в вашем любимом языке программирования, здесь следует список общих подводных камней Python, что вы должны пытаться избежать во всех расходах.

Музные аргументы по умолчанию – плохая идея

Настройка аргументов по умолчанию для функции очень распространено и полезно для определения дополнительных аргументов или аргументов, которые обычно могут использовать то же самое, предварительно определенное значение. Настройка аргумента по умолчанию на смежное значение, такое как Список или Дикт Может, однако, вызвать неожиданное поведение:

def some_func(args=[]):
    args.append("data")
    ...

some_func()
print(some_func.__defaults__)
# (['data'],)
some_func()
print(some_func.__defaults__)
# (['data', 'data'],)
some_func()
print(some_func.__defaults__)
# (['data', 'data', 'data'],)

# Better solution:
def some_func(args=None):
    if args is None:
        args = []
    ...

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

Хотя это может показаться неприятностью и проблемой, это предполагаемое поведение, и это также может быть эксплуатируется Чтобы сделать кэширование функций, которые могут использовать постоянный мультипнутый аргумент по умолчанию в качестве кеша:

def some_func(var, cache={}):
    if var in cache:
        return cache[var]
    # Do stuff...
    cache[var] = result
    return result

Подобное поведение до аргументов по умолчанию, также можно увидеть с помощью dict.setdefault (ключ, значение) Отказ В следующем коде мы можем увидеть некоторые удивительные результаты:

data = {}
key = 'some_key'
val = []  # Mutable

data.setdefault(key, val)

print('Before:', data)
# Before: {'some_key': []}
val.append('some_data')
print('After:', data)
# After: {'some_key': ['some_data']}

Хотя мы не трогали данные Словарь выше, он был изменен путем добавления к значению по умолчанию валь Отказ Это потому, что значение по умолчанию передается SetDefault . назначается непосредственно в словаре, когда ключ отсутствует вместо того, чтобы быть скопированным из оригинала. Чтобы избежать этой проблемы, убедитесь, что вы никогда не используете значения при использовании SetDefault . Отказ

Нан (не-) Рефлексивность

Работа с плавающими и не целочисленными числами часто может быть трудным и раздражающим, но он становится особенно странным, когда вы попадаете в Не – номер и Бесконечность Территория. Итак, давайте продемонстрируем это, сделав несколько сравнений с этими ценностями:

x = float("NaN")  # Define "Not a Number" (the string is case-insensitive), equivalent of math.nan
y = float("inf")  # Define "Infinity"
z = float("inf")  # Define more "Infinity"

x == 10  # False; Makes sense

y == z   # True; Kinda makes sense

x == x   # False... NaN != NaN

Приведенный выше код показывает нерефлексию Нан Отказ Нан В Python никогда не сравним, как равное даже по сравнению с собой. Итак, если вам нужно проверить на Нан или инф тогда вы должны использовать Math.isnan () и math.isinf () Отказ Также будьте осторожны с любой другой арифметической операцией при работе с кодом, который может производить Нан , поскольку он будет распространяться через все операции без повышения исключения.

Python обычно умный и вообще не вернет Нан Из математических функций, например math.exp (1000.0) вернется Overflowerror: Ошибка диапазона математики и оба math.sqrt (-1.0) и math.log (0,0) вернется ValueError: Ошибка математического домена , но вы можете столкнуться с этим с Numpy или Панда И если вы это сделаете, не забудьте не пытаться сравнивать Нанс для равенства.

Поздние замыкания связывания

Есть цитата несколько готов, подводных камней и сюрпризов, окружающих область и замыкание в Python. Самый распространенный – я бы сказал – это поздние обязательства в закрытиях. Начнем с примера:

funcs = []

for i in range(3):
    def some_func(n):
        return i * n
    funcs.append(some_func)

for f in funcs:
    print(f(2))

# 4
# 4
# 4

Код выше показывает определение функции внутри цикла, которая затем добавляется в список. С каждой итерацией Я Переменные приращения и так и Я Переменная в определенной функции, верно? Неправильно.

Поздние обязательства Заставляет все функции принимать значение 2 (от последней итерации). Это происходит из-за закрытия, в котором все функции определены в – глобальный. Из-за этого все они относятся к тому же Я который мутируется в цикле.

Есть более одного способа исправить это, но самый чистый, на мой взгляд, это использовать functools.partial :

from functools import partial

funcs = []

for i in range(3):
    def some_func(i, n):
        return i * n
    funcs.append(partial(some_func, i))

for f in funcs:
    print(f(2))

# 0
# 2
# 4

С частичный Мы можем создать новый Callable объект с предопределенными Я заставить переменную быть связанным немедленно который исправляет проблему. Затем мы можем поставить оставшийся оригинальный параметр n Когда мы хотим на самом деле позвонить в функции.

Переназначение глобальных переменных

Использование множества глобальных переменных, как правило, обескуражено и рассматривается как плохая практика. Однако есть действительная причина использовать некоторые глобальные переменные – например, чтобы определить различные Флаги , который можно использовать для установки уровня журнала функции.

Но что, если вы решите Flip (переназначить) этот флаг? Ну, это может вызвать массивную головную боль:

flag = False

def some_func():
    flag = True

def some_other_func():
    if flag:
        ...  # Do something when flag is set to True

some_func()
some_other_func()

Глядя на код выше, можно ожидать стоимости Global Флаг переменная для изменения в Истинный После исполнения uvere_func () , но это не так. que_func объявляет Новый местный Переменная Флаг , устанавливает это на Истинный И тогда исчезает после окончания функционального тела. глобальный Переменная никогда не трогана.

Однако есть простое решение. Нам нужно сначала объявить в функциональном теле, который мы хотим обратиться к глобальной переменной вместо того, чтобы использовать локальный. Мы делаем это с Global – В этом случае Глобальный флаг :

flag = False

def some_func():
    global flag
    flag = True

...

Другой “весело” Вопросы с переменными, к которым вы можете столкнуться – что, к счастью, намного легче отлаживать и исправить – это модификация вне охвата Переменная. Аналогично предыдущему Gotcha, это вызвано манипулированием переменной, которая была определена во внешнем объеме:

var = 1
def some_func():
    return var

def another_func():
    var += 1
    return var

print(some_func())
# 1
print(another_func())
# UnboundLocalError: local variable 'var' referenced before assignment

Здесь мы пытаемся увеличить переменную var Внутри функциональной массы, предполагая, что он изменит глобальный. Но опять же, это неправильно.

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

Это снова может быть исправлено, используя Global В случае глобальных переменных. Это так называемое Scoping Bug Может также возникать внутри вложенных функций, где вы будете использовать нелокальный Вместо этого, чтобы обратиться к переменной в ближайшем внешнем объеме:

def outer():
    flag = False     # Scope: 'outer'

    def inner():
        nonlocal flag
        flag = True  # Scope: 'inner'

    inner()
    print(flag)      # True; Did change
    return ...

outer()

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

Одно представление о том, что в значительной степени каждый разработчик Python укоренился в том, что кортежи определяются окружающими скобками. В отличие от iTerables, как обдумывать или задавать , Python кортеж определяется запятой, отделяющей его элементы.

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

x = ("value")  # Not a tuple.
type(x)
# 

x = ("value",)  # This is a tuple.
type(x)
# 

x = "value",  # No need for parenthesis.
type(x)
# 

x = ("some" "value")  # Don't forget the comma, otherwise strings will be implicitly concatenated.
print(x)
'somevalue'

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

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

Индексационные байтовые значения вместо строки байтов

При работе с файлами и данными в них мы в основном просто используем строки ASCII или UTF-8. Время от времени, однако, вам, возможно, придется прочитать и писать несколько двоичных данных, и вы можете быть удивлены результатами индексации и итерации их:

text = "Some data"

for c in text:
    print(c)

# S
# o
# m
# e
# ...

text = b"Some data"  # Binary String!

for c in text:
    print(c)

# 83
# 111
# 109
# 101
# ...

При индексации в двоичную строку вместо приема байтовой строки мы получаем целочисленное значение байта или другими словами – порядковая ценность индексированного символа. Чтобы избежать этого – особенно при чтении двоичного файла – лучше всего использовать Text.DeCode («UTF-8») чтобы получить правильную строку. Если вы хотите сохранить исходные данные в качестве двоичной строки, то вы можете вместо этого использовать Chr (C) Чтобы преобразовать отдельные символы в строковое представление.

Индексирование с отрицательной переменной

Нарезка и диска – одна из самых удобных особенностей Python, включая возможность указывать негативные индексы, но если вы не осторожны с тем, вы можете получить неожиданные результаты:

x = "Some data"
print(x[-4:])
# data

print(x[-0:])  # Equivalent to x[:]
# Some data

Если мы нарежьте последовательность с любым отрицательным значением (переменной), отличной от -0 Мы получим ожидаемые ценности, но если мы случайно случайно используем [-0:] Мы получим в результате копию целой последовательности, поскольку она эквивалентна [:] .

Почему это возвращает ничего!?

Я оставил свой “Фаворит” Готча то за последний. Легко забыть, возвращает функцию нового значения или изменяет оригинал на месте Отказ Особенно, когда есть вообще 2 типа методов – Список Методы, которые изменяют аргумент и возврат Нет и Строка Методы, которые изменяют аргумент на месте Отказ

# List methods
some_list.sort()  # Sorts in-place, returns None
some_list = some_list.sort()  # Wrong! -> some_list == None

some_list.reverse()
some_list.extend(["another"])
some_list.clear()
some_list.append("value")

# String methods
some_string = "Some data"

some_string = some_string.split()
some_string = some_string.strip()
some_string = some_string.capitalize()
some_string = some_string.upper()
some_string = some_string.encode()

Я виновен в том, чтобы сделать эту ошибку слишком много раз. Легко забывать поведение одного из многочисленных слоев или списка методов, и он может привести к часам отладки. Итак, если вы получите Нет Там, где должна быть целая строка или список, то дважды проверьте, чтобы убедиться, что вы используете все вышеперечисленные методы правильно.

Заключение

Это неизбежно, что вы столкнетесь с этими или другими похожими гоштабами и подводными камнями, которые приведут много ярости и разочарования. Чаще всего, чем нет, лучший способ решить любую из этих вопросов – просто вернуться на мгновение. Идти гулять. Go сделать чашку кофе. Или, по крайней мере, сделайте глубокий вдох. Большую часть времени все необходимое для решения такой проблемы, – это покинуть его и вернуться позже.

Если это не поможет, может быть, пришло время для отладки резиновой утки или принести в другую пару глаза (коллега сидит рядом с вами). Часто, когда вы начинаете объяснять проблему кому-то еще, вы немедленно поймете, где проблема на самом деле.

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

Оригинал: “https://dev.to/martinheinz/python-pitfalls-expecting-the-unexpected-4o45”