Можно ли использовать константы модуля в качестве аргументов функции по умолчанию в Python?

В основном что-то вроде этого:

DEFAULT_TIMEOUT = 10
# or even: from my_settings import DEFAULT_TIMEOUT

def get_google(timeout=DEFAULT_TIMEOUT):
    return requests.get('google.com', timeout=timeout)

Я бы предположил, что пока константа действительно остается постоянной, это должно работать нормально. Но иногда я вижу такую ​​картину:

DEFAULT_TIMEOUT = 10

def get_google(timeout=None):
    if timeout is None:
        timeout = DEFAULT_TIMEOUT
    return requests.get('google.com', timeout=timeout)

Являются ли они эквивалентными или я должен предпочесть один другому?


person gmolau    schedule 30.12.2017    source источник
comment
Можете ли вы привести пример, где вы видели этот второй шаблон?   -  person BrenBarn    schedule 30.12.2017
comment
@BrenBarn На первый взгляд, нет, но я видел это достаточно часто, чтобы запомнить его как один из способов сделать это.   -  person gmolau    schedule 30.12.2017
comment
Второй шаблон более полезен, когда значение по умолчанию является изменяемым, например []. С неизменяемыми типами, такими как числа, вы должны быть в порядке, используя их напрямую.   -  person Paul Panzer    schedule 30.12.2017
comment
Второй пример не позволит тайм-аут 0   -  person Stephen Rauch    schedule 30.12.2017
comment
@mxgx: Как говорится в комментарии Пола Панцера, этот шаблон чаще встречается в другой ситуации, поэтому я и спрашивал. Мне интересно, видели ли вы кого-то, кто использовал функцию default-None-then-if-inside вместе с константой уровня модуля в качестве значения по умолчанию, или вы видели только что видел эти две вещи по отдельности.   -  person BrenBarn    schedule 30.12.2017
comment
@BrenBarn Я специально спрашиваю о константе модуля, обычные аргументы по умолчанию, конечно, всегда должны указываться в заголовке функции.   -  person gmolau    schedule 30.12.2017
comment
@StephenRauch Вы правы, исправил, если для этого.   -  person gmolau    schedule 30.12.2017
comment
Эти примеры теперь эквивалентны, если только во втором примере вызывающая сторона явно не передаст None, тогда они получат значение по умолчанию.   -  person Stephen Rauch    schedule 30.12.2017


Ответы (2)


Нет проблем с использованием «константы» в качестве значения по умолчанию. Как вы говорите, пока «константа» действительно постоянна, это не имеет значения. Единственное, вам нужно убедиться, что константа определена перед функцией, но обычно люди помещают все свои константы в начало файла, так что это не проблема.

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

def foo(x=None):
    if x is None:
        x = []

вместо def foo(x=[]). Вы можете найти много вопросов по этому поводу, но, по сути, это потому, что если вы не сделаете это таким образом, изменяемое значение по умолчанию будет сохраняться при нескольких вызовах функции, что обычно нежелательно.

Однако использование этого шаблона для изменяемой константы уровня модуля не решит эту проблему. Если у вас есть:

SOME_CONSTANT = []

def foo(x=None):
    if x is None:
        x = SOME_CONSTANT

. . . тогда вы все еще повторно используете одно и то же изменяемое значение для нескольких вызовов. (Конечно, определение изменяемого значения как «константы», вероятно, в любом случае не очень хорошая идея.) Вот почему я спрашивал в комментариях, видели ли вы, чтобы кто-то специально делал такие вещи с константами модуля.

Этот шаблон None-then-if также будет использоваться, если значение по умолчанию на уровне модуля на самом деле не является константой, а значением, предназначенным для изменения другим кодом. Если вы выполняете def foo(x=DEFAULT_TIMEOUT), значение x по умолчанию равно тому, каким было DEFAULT_TIMEOUT во время определения функции. Но если вы используете шаблон None-then-if, по умолчанию будет то, что DEFAULT_TIMEOUT есть в момент вызова функции. Некоторые библиотеки определяют значения уровня модуля, которые не должны быть постоянными, а скорее являются значениями конфигурации, которые могут быть изменены в ходе выполнения. Это позволяет пользователям делать такие вещи, как установка DEFAULT_TIMEOUT = 20 для изменения времени ожидания по умолчанию для всех последующих вызовов, вместо того, чтобы каждый раз передавать timeout=20. В этом случае вам понадобится проверка if внутри функции, чтобы убедиться, что каждый вызов использует «текущее» значение DEFAULT_TIMEOUT.

person BrenBarn    schedule 30.12.2017
comment
Спасибо за объяснение, мне кажется, что на самом деле есть два тонких различия: первое из них — это то, что вы описали в своем последнем абзаце, то есть шаблон «нет-тогда-если» дает пользователю возможность переопределить константу модуля для повторные вызовы этой функции. Другое отличие заключается в том, что первый шаблон позволяет пользователю выбирать между моим собственным значением по умолчанию и значением по умолчанию базовой функции запроса. Передав None в первом случае, можно получить тайм-аут request.get() по умолчанию, о котором пользователь не должен знать, но может быть о нем осведомлен. - person gmolau; 30.12.2017
comment
Не то чтобы я знал о каком-либо практическом варианте использования этого, но это помогает понять, что происходит. - person gmolau; 30.12.2017
comment
@mxgx: Да, это правда. - person BrenBarn; 30.12.2017

ОБНОВЛЕНИЕ: я настоятельно рекомендую прочитать этот пост об атрибутах экземпляра и класса, он включает рекомендации по использованию обоих типов атрибутов, а также когда один из них предпочтительнее другого.

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

class Module:
    def __init__(self):
        self.DEFAULT_TIMEOUT = 10

    def get_google(timeout=self.DEFAULT_TIMEOUT):
        return requests.get('google.com', timeout=timeout)

выдаст ошибку: NameError: name 'self' is not defined

class Module:
    def __init__(self):
        self.DEFAULT_TIMEOUT = 10

    def get_google(timeout=None):
        if timeout is None:
            timeout = self.DEFAULT_TIMEOUT
        return requests.get('google.com', timeout=timeout)

В другом вопросе проблема решается с помощью mgilson более умным способом. Предлагается создать дозорные:

Обычная идиома здесь заключается в том, чтобы установить значение по умолчанию для некоторого контрольного значения (None является типичным, хотя некоторые предложили многоточие для этой цели), что вы можете проверить.

class Example(object): #inherit from object.  It's just a good idea.  
    def __init__(self, data = None):
        self.data = self.default_data() if data is None else data

    def default_data(self):  #probably need `self` here, unless this is a @staticmethod ...
        # ....
        return something

Вы также можете увидеть экземпляр object(), используемый для часового.

SENTINEL = object()
class Example(object):
    def __init__(self, data = SENTINEL):
        self.data = self.default_data() if data is SENTINEL else data

Эта последняя версия имеет то преимущество, что вы можете передать None в свою функцию, но имеет несколько недостатков (см. комментарии @larsmans ниже). Если вы не видите необходимости передавать None в качестве значимого аргумента вашим методам, я бы рекомендовал использовать это.

person Lycopersicum    schedule 30.12.2017