Existe uma maneira fácil em Python de verificar se o valor de um parâmetro opcional vem de seu valor padrão ou porque o usuário o definiu explicitamente na chamada de função?
python
function
optional-parameters
Matthias
fonte
fonte
None
como padrão e verificar isso. Se você realmente pudesse configurar esse teste, também excluiria qualquer possibilidade de o usuário passar explicitamente o valor que invoca o comportamento padrão.Class My(): def __init__(self, _p=None, a=True, b=True, c=False)
usuário a chama comx=My(b=False)
. Um método de classe poderia chamar a si mesmo comx=My(_p=self, c=True)
se as funções pudessem detectar que b não está explicitamente definido e que as variáveis não definidas devem ser transmitidas do nível superior. Mas se eles não podem, as chamadas recursivas tem que passar todas as variáveis explicitamente:x=My(a=self.a, b=self.b, c=True, d=self.d, ...)
.x=My()
ex=My(a=True)
. Seu cenário envolve a atribuição de parâmetros opcionais a um valor diferente de seu valor padrão.Respostas:
Muitas respostas contêm pequenos pedaços de informações completas, então eu gostaria de juntar tudo com meu (s) padrão (ões) favorito (s).
o valor padrão é um
mutable
tipoSe o valor padrão for um objeto mutável, você tem sorte: você pode explorar o fato de que os argumentos padrão do Python são avaliados uma vez quando a função é definida (mais informações sobre isso no final da resposta na última seção)
Isso significa que você pode comparar facilmente um valor mutável padrão usando
is
para ver se ele foi passado como um argumento ou deixado como padrão, como nos exemplos a seguir como função ou método:def f(value={}): if value is f.__defaults__[0]: print('default') else: print('passed in the call')
e
class A: def f(self, value={}): if value is self.f.__defaults__[0]: print('default') else: print('passed in the call')
Argumentos padrão imutáveis
Agora, é um pouco menos elegante se for esperado que seu padrão seja um
immutable
valor (e lembre-se de que até mesmo strings são imutáveis!) Porque você não pode explorar o truque como ele é, mas ainda há algo que você pode fazer, ainda explorando mutável tipo; basicamente, você coloca um padrão "falso" mutável na assinatura da função e o valor padrão "real" desejado no corpo da função.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)
É particularmente engraçado se o seu padrão real for
None
, masNone
é imutável, então ... você ainda precisa usar explicitamente um mutável como o parâmetro padrão da função e alternar para Nenhum no código.Usando um
Default
classe para padrões imutáveisou, semelhante à sugestão do @cz, se os documentos do python não forem suficientes :-), você pode adicionar um objeto no meio para tornar a API mais explícita (sem ler os documentos); a instância da classe used_proxy_ Default é mutável e conterá o valor padrão real que você deseja usar.
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))
agora:
>>> f() default Default Value: 1 (<class 'int'>) argument is: 1 >>> f(2) passed in the call argument is: 2
O acima também funciona bem para
Default(None)
.Outros padrões
Obviamente, os padrões acima parecem mais feios do que deveriam por causa de todos os
print
que existem apenas para mostrar como funcionam. Caso contrário, acho que são concisos e repetíveis o suficiente.Você poderia escrever um decorador para adicionar o
__call__
padrão sugerido por @dmg de uma forma mais simplificada, mas isso ainda obrigará o uso de truques estranhos na própria definição da função - você precisaria separarvalue
evalue_default
se seu código precisar distingui-los, Não vejo muita vantagem e não vou escrever o exemplo :-)Tipos mutáveis como valores padrão em Python
Um pouco mais sobre # 1 python gotcha! , abusado para seu próprio prazer acima. Você pode ver o que acontece devido à avaliação na definição , fazendo:
def testme(default=[]): print(id(default))
Você pode executar
testme()
quantas vezes quiser, você sempre verá uma referência à mesma instância padrão (então, basicamente, seu padrão é imutável :-)).Lembre-se que em Python existem apenas 3 mutável built-in tipos :
set
,list
,dict
; tudo o mais - até cordas! - é imutável.fonte
1
, que deve ser imutável ...def f(value={})
.Na verdade não. A forma padrão é usar um valor padrão que o usuário não deveria passar, por exemplo, uma
object
instância:DEFAULT = object() def foo(param=DEFAULT): if param is DEFAULT: ...
Normalmente, você pode usar apenas
None
como o valor padrão, se não fizer sentido como um valor que o usuário deseja passar.A alternativa é usar
kwargs
:def foo(**kwargs): if 'param' in kwargs: param = kwargs['param'] else: ...
No entanto, isso é muito detalhado e torna sua função mais difícil de usar, pois a documentação não inclui o
param
parâmetro automaticamente .fonte
Ellipsis
singleton como um padrão, que foi explicitamente projetado para ser usado como um salto desse valor....
é um apelido paraEllipsis
, então os usuários que desejam usar argumentos posicionais podem apenas chamar, oyour_function(p1, ..., p3)
que torna a leitura óbvia e agradável.However this is overly verbose and makes your function more difficult to use as its documentation will not automatically include the param parameter.
Na verdade, isso não é verdade, pois você pode definir a descrição de uma função e de seus parâmetros usando oinspect
módulo. Depende do seu IDE se vai funcionar ou não.O seguinte decorador de função,,
explicit_checker
cria um conjunto de nomes de parâmetro de todos os parâmetros fornecidos explicitamente. Ele adiciona o resultado como um parâmetro extra (explicit_params
) à função. Basta fazer'a' in explicit_params
para verificar se o parâmetroa
é fornecido explicitamente.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)
fonte
Às vezes, uso uma string universalmente exclusiva (como um UUID).
import uuid DEFAULT = uuid.uuid4() def foo(arg=DEFAULT): if arg is DEFAULT: # it was not passed in else: # it was passed in
Dessa forma, nenhum usuário poderia adivinhar o padrão se tentasse, então posso ter certeza de que, quando vejo esse valor para
arg
, ele não foi transmitido.fonte
object()
vez deuuid4()
- ainda é uma instância única , que é o queis
verificaEu já vi esse padrão algumas vezes (ou seja, bibliotecas
unittest
,py-flags
,jinja
):class Default: def __repr__( self ): return "DEFAULT" DEFAULT = Default()
... ou equivalente de uma linha ...:
DEFAULT = type( 'Default', (), { '__repr__': lambda x: 'DEFAULT' } )()
Ao contrário
DEFAULT = object()
, isso auxilia na verificação de tipo e fornece informações quando ocorrem erros - frequentemente, a representação da string ("DEFAULT"
) ou o nome da classe ("Default"
) são usados em mensagens de erro.fonte
A resposta de @Ellioh funciona em python 2. Em python 3, o seguinte código deve funcionar:
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
Este método pode manter os nomes dos argumentos e valores padrão (em vez de ** kwargs) com melhor legibilidade.
fonte
Você pode verificar em
foo.__defaults__
efoo.__kwdefaults__
veja um exemplo simples abaixo
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
então usando o operador
=
eis
você pode compará-lose para alguns casos o código abaixo é suficiente
Por exemplo, você precisa evitar alterar o valor padrão, então você pode verificar a igualdade e, em seguida, copiar se assim for
def update_and_show(data=Example): if data is Example: data = copy.deepcopy(data) update_inplace(data) #some operation print(data)
Além disso, é bastante conveniente usar
getcallargs
frominspect
, pois retorna argumentos reais com os quais a função será chamada. Você passa uma função e args e kwargs para ele (inspect.getcallargs(func, /, *args, **kwds)
), ele retornará os argumentos do método real usados para invocação, levando em consideração os valores padrão e outras coisas. Dê uma olhada no exemplo abaixo.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
fonte
Eu concordo com o comentário do Volatility. Mas você pode verificar da seguinte maneira:
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
fonte
def func(optional=value)
não**kwargs
**kwargs
é um pouco diferente. PS sem problemas é sobre -1 :) E meu -1 para você foi acidental :)Esta é uma variação da resposta de Stefano, mas acho um pouco mais legível:
not_specified = {} def foo(x=not_specified): if x is not_specified: print("not specified") else: print("specified")
fonte
Uma abordagem um pouco bizarra seria:
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()
Quais saídas:
passed default z passed different b not passed z
Agora, isso, como mencionei, é bastante estranho, mas faz o trabalho. No entanto, este é bastante ilegível e à semelhança do ecatmur 's sugestão não será documentado automaticamente.
fonte
check_f('z')
, que também é, como você diz, bizarro.