Есть ли в Python простой способ проверить, исходит ли значение необязательного параметра из его значения по умолчанию или потому, что пользователь явно установил его при вызове функции?
93
Есть ли в Python простой способ проверить, исходит ли значение необязательного параметра из его значения по умолчанию или потому, что пользователь явно установил его при вызове функции?
None
по умолчанию и проверьте это. Если бы вы действительно могли настроить этот тест, вы также исключили бы любую возможность для пользователя явно передать значение, которое вызывает поведение по умолчанию.Class My(): def __init__(self, _p=None, a=True, b=True, c=False)
пользователь вызывает его с помощьюx=My(b=False)
. Метод класса мог бы вызывать сам себя,x=My(_p=self, c=True)
если бы функции могли обнаружить, что b не установлен явно и что неустановленные переменные должны быть переданы вниз с верхнего уровня. Но если они не могут, рекурсивные вызовы должны проходить каждую переменную в явном виде:x=My(a=self.a, b=self.b, c=True, d=self.d, ...)
.x=My()
иx=My(a=True)
. В вашем сценарии необязательным параметрам присваивается значение, отличное от значения по умолчанию.Ответы:
Многие ответы содержат небольшие фрагменты полной информации, поэтому я хотел бы собрать все это вместе с моими любимыми шаблонами.
значение по умолчанию - это
mutable
типЕсли значением по умолчанию является изменяемый объект, вам повезло: вы можете воспользоваться тем фактом, что аргументы Python по умолчанию оцениваются один раз при определении функции (подробнее об этом в конце ответа в последнем разделе)
Это означает, что вы можете легко сравнить изменяемое значение по умолчанию, используя,
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
оно неизменяемо, поэтому ... вам все равно нужно явно использовать изменяемый параметр в качестве параметра функции по умолчанию и переключиться на None в коде.Использование
Default
класса для неизменяемых значений по умолчаниюили, аналогично предложению @cz, если документов python недостаточно :-), вы можете добавить объект между ними, чтобы сделать API более явным (без чтения документов); Экземпляр класса used_proxy_ Default является изменяемым и будет содержать реальное значение по умолчанию, которое вы хотите использовать.
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
если ваш код должен различать их, поэтому Особого преимущества не вижу и пример писать не буду :-)Изменяемые типы как значения по умолчанию в Python
Еще немного о питоне # 1! , оскорбляли для собственного удовольствия выше. Вы можете увидеть, что происходит из-за оценки при определении , выполнив:
def testme(default=[]): print(id(default))
Вы можете запускать
testme()
столько раз, сколько хотите, вы всегда будете видеть ссылку на один и тот же экземпляр по умолчанию (так что в основном ваше значение по умолчанию является неизменным :-)).Помните , что в Python есть только 3 изменяемые встроенных типов :
set
,list
,dict
; все остальное - даже струны! - неизменен.источник
1
, которое должно быть неизменным ...def f(value={})
.На самом деле, нет. Стандартный способ - использовать значение по умолчанию, которое пользователь не может передать, например
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
параметр.источник
Ellipsis
синглтон по умолчанию, который был явно разработан для использования в качестве пропуска этого значения....
- это псевдоним дляEllipsis
, поэтому пользователи, которые хотят использовать позиционные аргументы, могут просто вызвать,your_function(p1, ..., p3)
что делает его очевидным и приятным для чтения.However this is overly verbose and makes your function more difficult to use as its documentation will not automatically include the param parameter.
На самом деле это неправда, поскольку вы можете задать описание функции и ее параметров с помощьюinspect
модуля. Будет ли она работать, зависит от вашей IDE.Следующий декоратор функции
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)
источник
Иногда я использую универсально уникальную строку (например, 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
, оно не было передано.источник
object()
вместо нихuuid4()
- это все еще уникальный экземпляр , что иis
проверяетЯ видел эту картину несколько раз (например , библиотека
unittest
,py-flags
,jinja
):class Default: def __repr__( self ): return "DEFAULT" DEFAULT = Default()
... или эквивалентный однострочный ...:
DEFAULT = type( 'Default', (), { '__repr__': lambda x: 'DEFAULT' } )()
В отличие от
DEFAULT = object()
этого, это помогает при проверке типов и предоставляет информацию при возникновении ошибок - часто в сообщениях об ошибках используется строковое представление ("DEFAULT"
) или имя класса ("Default"
).источник
Ответ @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) с лучшей читабельностью.
источник
Вы можете проверить это из
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)
Кроме того , это очень удобно использовать
getcallargs
вinspect
качестве возвращает реальные аргументы , с помощью которого функция будет вызываться. Вы передаете ей функцию, args и kwargs (inspect.getcallargs(func, /, *args, **kwds)
), она вернет аргументы реального метода, используемые для вызова, с учетом значений по умолчанию и прочего. Взгляните на пример ниже.from inspect import getcallargs # we have a function with such signature def show_params(first, second, third=3): pass # if you wanted to invoke it with such params (you could get them from a decorator as example) args = [1, 2, 5] kwargs = {} print(getcallargs(show_params, *args, **kwargs)) #{'first': 1, 'second': 2, 'third': 5} # here we didn't specify value for d args = [1, 2, 3, 4] kwargs = {} # ---------------------------------------------------------- # but d has default value =7 def show_params1(first, *second, d = 7): pass print(getcallargs(show_params1, *args, **kwargs)) # it will consider b to be equal to default value 7 as it is in real method invocation # {'first': 1, 'second': (2, 3, 4), 'd': 7} # ---------------------------------------------------------- args = [1] kwargs = {"d": 4} def show_params2(first, d=3): pass print(getcallargs(show_params2, *args, **kwargs)) #{'first': 1, 'd': 4}
https://docs.python.org/3/library/inspect.html
источник
Я согласен с комментарием 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
источник
def func(optional=value)
нет»**kwargs
**kwargs
немного другое. PS без проблем про -1 :) А мой -1 для вас был случайным :)Это вариант ответа Стефано, но я считаю его более читаемым:
not_specified = {} def foo(x=not_specified): if x is not_specified: print("not specified") else: print("specified")
источник
Немного причудливый подход:
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 «s предложение не будет автоматически документированы.
источник
check_f('z')
, которое также, как вы говорите, странно.