Является ли использование одноразового связывания аргументов функции плохой идеей?

Новые пользователи 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)
Другие вопросы по тегам