Czy w Pythonie istnieje łatwy sposób sprawdzenia, czy wartość parametru opcjonalnego pochodzi z jego wartości domyślnej, czy też dlatego, że użytkownik ustawił ją jawnie przy wywołaniu funkcji?
python
function
optional-parameters
Matthias
źródło
źródło
None
jako domyślnego i sprawdź to. Gdybyś naprawdę mógł skonfigurować ten test, wykluczyłbyś również jakąkolwiek możliwość jawnego przekazania przez użytkownika wartości, która wywołuje domyślne zachowanie.Class My(): def __init__(self, _p=None, a=True, b=True, c=False)
użytkownik wywołuje ją za pomocąx=My(b=False)
. Metoda klasy mogłaby wywołać samą siebie z,x=My(_p=self, c=True)
gdyby funkcje mogły wykryć, że b nie jest jawnie ustawione i że nieustawione zmienne mają być przekazywane z najwyższego poziomu. Ale jeśli nie, wywołania rekurencyjne muszą przejść każdą zmienną wyraźnie:x=My(a=self.a, b=self.b, c=True, d=self.d, ...)
.x=My()
ix=My(a=True)
. Twój scenariusz obejmuje przypisanie opcjonalnym parametrom wartości innej niż ich wartość domyślna.Odpowiedzi:
Wiele odpowiedzi zawiera małe fragmenty pełnych informacji, więc chciałbym zebrać to wszystko razem z moimi ulubionymi wzorami.
wartością domyślną jest
mutable
typJeśli wartością domyślną jest obiekt zmienny, masz szczęście: możesz wykorzystać fakt, że domyślne argumenty Pythona są oceniane tylko raz, gdy funkcja jest zdefiniowana (więcej na ten temat na końcu odpowiedzi w ostatniej sekcji)
Oznacza to, że możesz łatwo porównać domyślną zmienną wartość za pomocą,
is
aby sprawdzić, czy została przekazana jako argument, czy pozostawiona domyślnie, jak w poniższych przykładach jako funkcja lub metoda:def f(value={}): if value is f.__defaults__[0]: print('default') else: print('passed in the call')
i
class A: def f(self, value={}): if value is self.f.__defaults__[0]: print('default') else: print('passed in the call')
Niezmienne argumenty domyślne
Teraz jest trochę mniej elegancko, jeśli domyślna wartość ma być
immutable
wartością (i pamiętaj, że nawet łańcuchy są niezmienne!), Ponieważ nie możesz wykorzystać tej sztuczki w obecnej postaci, ale wciąż jest coś, co możesz zrobić, wciąż wykorzystując zmienne rodzaj; zasadniczo umieszczasz zmienną „fałszywą” wartość domyślną w sygnaturze funkcji, a żądaną „rzeczywistą” wartość domyślną w treści funkcji.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)
Wydaje się to szczególnie zabawne, jeśli prawdziwa wartość domyślna jest
None
, aleNone
jest niezmienna, więc ... nadal musisz jawnie użyć mutable jako domyślnego parametru funkcji i przełączyć się na None w kodzie.Używanie
Default
klasy dla niezmiennych wartości domyślnychlub, podobnie jak w przypadku sugestii @cz, jeśli dokumentacja Pythona nie wystarcza :-), możesz dodać obiekt pomiędzy nimi, aby API było bardziej przejrzyste (bez czytania dokumentacji); instancja klasy used_proxy_ Default jest zmienna i będzie zawierała rzeczywistą wartość domyślną, której chcesz użyć.
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))
teraz:
>>> f() default Default Value: 1 (<class 'int'>) argument is: 1 >>> f(2) passed in the call argument is: 2
Powyższe działa ładnie również w przypadku
Default(None)
.Inne wzory
Oczywiście powyższe wzorce wyglądają brzydiej niż powinny, ponieważ wszystkie
print
są tylko po to, aby pokazać, jak działają. W przeciwnym razie uważam je za zwięzłe i wystarczająco powtarzalne.Mógłbyś napisać dekorator, który doda
__call__
wzorzec sugerowany przez @dmg w bardziej uproszczony sposób, ale nadal będzie to obligowało do stosowania dziwnych sztuczek w samej definicji funkcji - musiałbyś się rozdzielićvalue
ivalue_default
jeśli twój kod musi je rozróżnić, więc Nie widzę dużej przewagi i nie będę pisać przykładu :-)Zmienne typy jako wartości domyślne w Pythonie
Trochę więcej o # 1 python gotcha! , nadużywane powyżej dla własnej przyjemności. Możesz zobaczyć, co dzieje się w wyniku oceny na definicji , wykonując:
def testme(default=[]): print(id(default))
Możesz uruchomić
testme()
tyle razy, ile chcesz, zawsze zobaczysz odniesienie do tej samej domyślnej instancji (więc w zasadzie twoja domyślna jest niezmienna :-)).Pamiętaj, że w Pythonie istnieją tylko 3 zmienne wbudowane typy :
set
,list
,dict
; wszystko inne - nawet struny! - jest niezmienna.źródło
1
, która powinna być niezmienna ...def f(value={})
.1
; przepraszam, jeśli nie jest to jasne w wyjaśnieniu, ale celem tej części odpowiedzi jest możliwość posiadania niezmiennej wartości domyślnej (1
). Jeśli spojrzysz na przykład, zobaczysz, że brzmi:print('default'); value = 1
nievalue={}
Nie całkiem. Standardowym sposobem jest użycie wartości domyślnej, której użytkownik nie powinien podać, np.
object
Instancja:DEFAULT = object() def foo(param=DEFAULT): if param is DEFAULT: ...
Zwykle możesz po prostu użyć
None
wartości domyślnej, jeśli nie ma to sensu jako wartości, którą użytkownik chciałby przekazać.Alternatywą jest użycie
kwargs
:def foo(**kwargs): if 'param' in kwargs: param = kwargs['param'] else: ...
Jest to jednak zbyt rozwlekłe i utrudnia korzystanie z funkcji, ponieważ jej dokumentacja nie będzie automatycznie zawierać
param
parametru.źródło
Ellipsis
singletona jako domyślnego, który został jawnie zaprojektowany do pominięcia tej wartości....
jest aliasem dlaEllipsis
, więc użytkownicy, którzy chcą używać argumentów pozycyjnych, mogą po prostu wywołać,your_function(p1, ..., p3)
co sprawia, że jest to oczywiste i przyjemne do czytania.However this is overly verbose and makes your function more difficult to use as its documentation will not automatically include the param parameter.
To faktycznie nieprawda, ponieważ opis funkcji i jej parametrów można ustawić za pomocąinspect
modułu. To zależy od twojego IDE, czy zadziała, czy nie.Poniższy dekorator funkcji,,
explicit_checker
tworzy zestaw nazw parametrów wszystkich parametrów podanych jawnie. Dodaje wynik jako dodatkowy parametr (explicit_params
) do funkcji. Po prostu'a' in explicit_params
sprawdź, czy parametra
jest podany jawnie.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)
źródło
Czasami używam uniwersalnego, unikalnego ciągu (takiego jak UUID).
import uuid DEFAULT = uuid.uuid4() def foo(arg=DEFAULT): if arg is DEFAULT: # it was not passed in else: # it was passed in
W ten sposób żaden użytkownik nie mógł nawet odgadnąć wartości domyślnej, gdyby spróbował, więc mogę być bardzo pewien, że gdy widzę tę wartość
arg
, nie została ona przekazana.źródło
object()
zamiast nichuuid4()
- to wciąż wyjątkowa instancja , cois
sprawdzaWidziałem tego wzorca kilka razy (na przykład biblioteka
unittest
,py-flags
,jinja
):class Default: def __repr__( self ): return "DEFAULT" DEFAULT = Default()
... lub równoważny jednolinijkowy ...:
DEFAULT = type( 'Default', (), { '__repr__': lambda x: 'DEFAULT' } )()
W przeciwieństwie do
DEFAULT = object()
tego pomaga to w sprawdzaniu typu i dostarcza informacji, gdy wystąpią błędy - często w komunikatach o błędach używane są reprezentacja ciągu ("DEFAULT"
) lub nazwa klasy ("Default"
).źródło
Odpowiedź @ Ellioh działa w Pythonie 2. W Pythonie 3 następujący kod powinien działać:
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
Ta metoda może zachować nazwy argumentów i wartości domyślne (zamiast ** kwargs) z lepszą czytelnością.
źródło
Możesz to sprawdzić pod adresem
foo.__defaults__
ifoo.__kwdefaults__
zobacz prosty przykład poniżej
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
następnie używając operatora
=
iis
możesz je porównaćaw niektórych przypadkach kod poniżej jest wystarczający
Na przykład, musisz unikać zmiany wartości domyślnej, wtedy możesz sprawdzić równość, a następnie skopiować, jeśli tak
def update_and_show(data=Example): if data is Example: data = copy.deepcopy(data) update_inplace(data) #some operation print(data)
Jest również dość wygodny w użyciu
getcallargs
from,inspect
ponieważ zwraca rzeczywiste argumenty, z którymi funkcja zostanie wywołana. Przekazujesz do niej funkcję oraz argumenty i kwargi (inspect.getcallargs(func, /, *args, **kwds)
), zwróci ona prawdziwe argumenty metody użyte do wywołania, biorąc pod uwagę wartości domyślne i inne rzeczy. Spójrz na poniższy przykład.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
źródło
Zgadzam się z komentarzem Volatility. Ale możesz sprawdzić w następujący sposób:
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
źródło
def func(optional=value)
nie**kwargs
**kwargs
jest trochę inne. PS nie ma problemu z -1 :) A moje -1 dla Ciebie było przypadkowe :)To jest wariacja na temat odpowiedzi Stefana, ale uważam, że jest trochę bardziej czytelna:
not_specified = {} def foo(x=not_specified): if x is not_specified: print("not specified") else: print("specified")
źródło
Trochę dziwacznym podejściem byłoby:
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()
Które wyjścia:
passed default z passed different b not passed z
Jak już wspomniałem, jest to dość dziwaczne, ale spełnia swoje zadanie. Jednak jest to dość nieczytelny i podobnie do ecatmur „s propozycja nie zostanie automatycznie udokumentowane.
źródło
check_f('z')
, które jest również, jak mówisz, dziwaczne.