Является ли использование одноразового связывания аргументов функции плохой идеей?
Новые пользователи Python часто получают путаницу по умолчанию с изменяемым аргументом. Каковы недостатки и другие проблемы использования этой "функции" специально, например, для получения настраиваемых значений по умолчанию во время выполнения, которые продолжают правильно отображаться в сигнатурах функций через help()
?
class MutableString (str):
def __init__ (self, value):
self.value = value
def __str__ (self):
return self.value
def __repr__ (self):
return "'" + self.value + "'"
defaultAnimal = MutableString('elephant')
def getAnimal (species=defaultAnimal):
'Return the given animal, or the mutable default.'
return species
И в использовании:
>>> help(getAnimal)
getAnimal(species='elephant')
Return the given animal, or the mutable default.
>>> print getAnimal()
elephant
>>> defaultAnimal.value = 'kangaroo'
>>> help(getAnimal)
getAnimal(species='kangaroo')
Return the given animal, or the mutable default.
>>> print getAnimal()
kangaroo
2 ответа
Во-первых, прочитайте Почему значения по умолчанию совместно используются объектами. Это не отвечает на ваш вопрос, но дает некоторую предысторию.
Существуют различные допустимые варианты использования этой функции, но все они имеют что-то общее: по умолчанию используется прозрачный, простой, явно изменяемый, встроенный тип. Кэши мемоизации, аккумуляторы для рекурсивных вызовов, необязательные выходные переменные и т. Д. Выглядят так. Итак, опытные разработчики Python обычно замечают один из этих вариантов использования - если они видят memocache={}
или же accum=[]
, они будут знать, чего ожидать. Но ваш код совсем не будет выглядеть как использование изменяемых значений по умолчанию, что будет вводить в заблуждение как экспертов, так и новичков.
Другая проблема в том, что ваша функция выглядит так, как будто она возвращает строку, но она врет:
>>> print getAnimal()
kangaroo
>>> print getAnimal()[0]
e
Конечно, проблема в том, что вы реализовали MutableString
не так, не то, что это невозможно реализовать… но все же, это должно показать, почему попытка "обмануть" переводчика и ваших пользователей имеет тенденцию открывать двери неожиданным ошибкам.
-
Очевидный способ справиться с этим - сохранить измененное значение по умолчанию в атрибуте экземпляра модуля, функции или (если это метод) и использовать None
в качестве значения по умолчанию. Или если None
является допустимым значением, используйте какой-нибудь другой страж:
defaultAnimal = 'elephant'
def getAnimal (species=None):
if species is None:
return defaultAnimal
return species
Обратите внимание, что это в значительной степени именно то, что предлагает FAQ. Даже если у вас изначально есть изменчивая ценность, вы должны сделать этот танец, чтобы обойти проблему. Таким образом, вам определенно не следует создавать изменяемое значение из неотъемлемо неизменного для создания проблемы.
Да это значит что help(getAnimal)
не показывает текущее значение по умолчанию. Но никто не будет этого ожидать.
Они, вероятно, ожидают, что вы скажете им, что значение по умолчанию, конечно, является ловушкой, но это работа для строки документации:
defaultAnimal = 'elephant'
def getAnimal (species=None):
"""getAnimal([species]) -> species
If the optional species parameter is left off, a default animal will be
returned. Normally this is 'elephant', but this can be configured by setting
foo.defaultAnimal to another value.
"""
if species is None:
return defaultAnimal
return species
Единственное полезное использование, которое я видел для этого, - это кеш:
def fibo(n, cache={}):
if n < 2:
return 1
else:
if n in cache:
return cache[n]
else:
fibo_n = fibo(n-1) + fibo(n-2) # you can still hit maximum recursion depth
cache[n] = fibo_n
return fibo_n
... но тогда чище использовать @lru_cache
декоратор.
@lru_cache
def fibo(n):
if n < 2:
return 1
else:
return fibo(n-1) + fibo(n-2)