Подтвердить что ты не робот

Python: как проверить, установлен ли дополнительный параметр функции

Есть ли в Python простой способ проверить, не зависит ли значение необязательного параметра от его значения по умолчанию, или потому, что пользователь явно указал его на вызов функции?

4b9b3361

Ответ 1

Многие ответы содержат небольшие фрагменты полной информации, поэтому я хотел бы объединить их со своими любимыми шаблонами.

значение по умолчанию - тип mutable

Если значение по умолчанию является изменяемым объектом, вам повезло: вы можете использовать тот факт, что аргументы Pythons по умолчанию оцениваются один раз, когда определена функция (подробнее об этом в конце ответа)

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

def f(value={}):
    if value is f.__defaults__[0]:
        print('default')
    else:
        print('passed in the call')

и

class A:
    def f(self, value={}):
        if value is self.f.__defaults__[0]:
            print('default')
        else:
            print('passed in the call')

Неизменяемые аргументы по умолчанию

Теперь, это немного менее элегантно, если по умолчанию ожидается значение immutable (и помните, что даже строки неизменяемы!), Потому что вы не можете использовать трюк как есть, но вы все равно можете сделать что-то вроде:

def f(value={}):
    """
    my function
    :param value: value for my function; default is 1
    """
    if value is f.__defaults__[0]:
        print('default')
        value = 1
    else:
        print('passed in the call')
    # whatever I want to do with the value
    print(value)

Это особенно забавно, если вы по умолчанию используете None, но None неизменен, так что...

Использование класса Default для неизменных значений по умолчанию

или, аналогично предложению @c-z, если документов на python недостаточно :-), вы можете добавить промежуточный объект, чтобы сделать API более явным, не читая документы:

class Default:
    def __repr__(self):
        return "Default Value: {} ({})".format(self.value, type(self.value))

    def __init__(self, value):
        self.value = value

def f(default=Default(1)):
    if default is f.__defaults__[0]:
        print('default')
        print(default)
        default = default.value
    else:
        print('passed in the call')
    print("argument is: {}".format(default))

Сейчас:

>>> f()
default
Default Value: 1 (<class 'int'>)
argument is: 1

>>> f(2)
passed in the call
argument is: 2

Вышеописанное прекрасно работает и для Default(None).

Другие шаблоны

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

Вы могли бы написать декоратор, чтобы добавить шаблон __call__, предложенный @dmg, более упорядоченным способом, но это все равно заставит использовать странные трюки в самом определении функции - вам нужно будет разделить value и value_default если ваш код должен их различать, я не вижу большого преимущества и не напишу пример :-)

Изменяемые типы в качестве значений по умолчанию

Немного больше о # 1 питонской ошибке!, над которой издевались ради вашего собственного удовольствия. Вы можете увидеть, что происходит из-за оценки при определении, выполнив:

def testme(default=[]):
    print(id(default))

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

Помните, что в Python есть 3 изменяемых встроенных типа: set, list, dict, но все остальное - даже строки! - неизменен.

Ответ 2

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

DEFAULT = object()
def foo(param=DEFAULT):
    if param is DEFAULT:
        ...

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

Альтернативой является использование kwargs:

def foo(**kwargs):
    if 'param' in kwargs:
        param = kwargs['param']
    else:
        ...

Однако это слишком многословно и делает вашу функцию более трудной для использования, поскольку ее документация не будет автоматически включать параметр param.

Ответ 3

Следующий декоратор функций explicit_checker, делает набор имен параметров всех указанных параметров явно. Он добавляет результат в качестве дополнительного параметра (explicit_params) к функции. Просто выполните 'a' in explicit_params, чтобы проверить, задан ли параметр a явно.

def explicit_checker(f):
    varnames = f.func_code.co_varnames
    def wrapper(*a, **kw):
        kw['explicit_params'] = set(list(varnames[:len(a)]) + kw.keys())
        return f(*a, **kw)
    return wrapper

@explicit_checker
def my_function(a, b=0, c=1, explicit_params=None):
    print a, b, c, explicit_params
    if 'b' in explicit_params:
        pass # Do whatever you want


my_function(1)
my_function(1, 0)
my_function(1, c=1)

Ответ 4

Я иногда использую универсально уникальную строку (например, UUID).

import uuid
DEFAULT = uuid.uuid4()
def foo(arg=DEFAULT):
  if arg is DEFAULT:
    # it was not passed in
  else:
    # it was passed in

Таким образом, ни один пользователь не может даже догадаться о по умолчанию, если они попытаются, поэтому я могу быть очень уверенным, что, когда я вижу это значение для arg, он не был передан.

Ответ 5

Я согласен с комментарием Volatility. Но вы можете проверить следующее:

def function(arg1,...,**optional):
    if 'optional_arg' in optional:
        # user has set 'optional_arg'
    else:
        # user has not set 'optional_arg'
        optional['optional_arg'] = optional_arg_default_value # set default

Ответ 6

Я видел этот шаблон несколько раз (например, библиотека unittest, py-flags, jinja):

class Default:
    def __repr__( self ):
        return "DEFAULT"

DEFAULT = Default()

... или эквивалентный однострочный...:

DEFAULT = type( 'Default', (), { '__repr__': lambda x: 'DEFAULT' } )()

В отличие от DEFAULT = object(), это помогает проверять тип и предоставляет информацию при возникновении ошибок - часто в сообщениях об ошибках используется либо строковое представление ("DEFAULT"), либо имя класса ("DEFAULT").

Ответ 7

Вы можете проверить это из foo.__defaults__ и foo.__kwdefaults__

см. простой пример ниже

def foo(a, b, c=123, d=456, *, e=789, f=100):
    print(foo.__defaults__)
    # (123, 456) 
    print(foo.__kwdefaults__)
    # {'e': 789, 'f': 100}
    print(a, b, c, d, e, f)

#and these variables are also accessible out of function body
print(foo.__defaults__)    
# (123, 456)  
print(foo.__kwdefaults__)  
# {'e': 789, 'f': 100}

foo.__kwdefaults__['e'] = 100500

foo(1, 2) 
#(123, 456)
#{'f': 100, 'e': 100500}
#1 2 123 456 100500 100

затем с помощью оператора = и is вы можете сравнить их

и для некоторых случаев достаточно кода ниже

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

    def update_and_show(data=Example):
        if data is Example:
            data = copy.deepcopy(data)
        update_inplace(data) #some operation
        print(data)

Ответ 8

Немного причудливый подход:

class CheckerFunction(object):
    def __init__(self, function, **defaults):
        self.function = function
        self.defaults = defaults

    def __call__(self, **kwargs):
        for key in self.defaults:
            if(key in kwargs):
                if(kwargs[key] == self.defaults[key]):
                    print 'passed default'
                else:
                    print 'passed different'
            else:
                print 'not passed'
                kwargs[key] = self.defaults[key]

        return self.function(**kwargs)

def f(a):
    print a

check_f = CheckerFunction(f, a='z')
check_f(a='z')
check_f(a='b')
check_f()

Какие выходы:

passed default
z
passed different
b
not passed
z

Теперь это, как я уже упоминал, довольно причудливо, но он выполняет эту работу. Однако это довольно непроницаемо и аналогично ecatmur не будет автоматически задокументировано.

Ответ 9

Ответ @Ellioh работает в Python 2. В Python 3 должен работать следующий код:

import inspect
def explicit_checker(f):
    varnames = inspect.getfullargspec(f)[0]
    def wrapper(*a, **kw):
        kw['explicit_params'] = set(list(varnames[:len(a)]) + list(kw.keys()))
        return f(*a, **kw)
    return wrapper

@explicit_checker
def my_function(a, b=0, c=1, explicit_params=None):
    print a, b, c, explicit_params
    if 'b' in explicit_params:
        pass # Do whatever you want

Этот метод может сохранять имена аргументов и значения по умолчанию (вместо ** kwargs) с лучшей читаемостью.