Gibt es in Python eine einfache Möglichkeit zu überprüfen, ob der Wert eines optionalen Parameters von seinem Standardwert stammt oder ob der Benutzer ihn beim Funktionsaufruf explizit festgelegt hat?
python
function
optional-parameters
Matthias
quelle
quelle
None
als Standard und überprüfen Sie dies. Wenn Sie diesen Test wirklich einrichten könnten, würden Sie auch jede Möglichkeit für den Benutzer ausschließen, den Wert, der das Standardverhalten aufruft, explizit zu übergeben.Class My(): def __init__(self, _p=None, a=True, b=True, c=False)
Benutzer ruft sie mit aufx=My(b=False)
. Eine Klassenmethode könnte sich selbst aufrufen,x=My(_p=self, c=True)
wenn Funktionen erkennen könnten, dass b nicht explizit gesetzt ist und dass nicht gesetzte Variablen von der obersten Ebene weitergegeben werden sollen. Wenn dies nicht möglich ist, müssen die rekursiven Aufrufe jede Variable explizit übergeben :x=My(a=self.a, b=self.b, c=True, d=self.d, ...)
.x=My()
undx=My(a=True)
. In Ihrem Szenario wird optionalen Parametern ein anderer Wert als der Standardwert zugewiesen.Antworten:
Viele Antworten enthalten kleine Teile der vollständigen Informationen, daher möchte ich alles mit meinen Lieblingsmustern zusammenbringen.
Standardwert ist ein
mutable
TypWenn der Standardwert ein veränderbares Objekt ist, haben Sie Glück: Sie können die Tatsache ausnutzen, dass die Standardargumente von Python bei der Definition der Funktion einmal ausgewertet werden (mehr dazu am Ende der Antwort im letzten Abschnitt).
Dies bedeutet, dass Sie einen veränderlichen Standardwert leicht vergleichen können, indem Sie feststellen
is
, ob er als Argument übergeben oder standardmäßig belassen wurde, wie in den folgenden Beispielen als Funktion oder Methode:def f(value={}): if value is f.__defaults__[0]: print('default') else: print('passed in the call')
und
class A: def f(self, value={}): if value is self.f.__defaults__[0]: print('default') else: print('passed in the call')
Unveränderliche Standardargumente
Jetzt ist es etwas weniger elegant, wenn erwartet wird, dass Ihre Standardeinstellung ein
immutable
Wert ist (und denken Sie daran, dass sogar Zeichenfolgen unveränderlich sind!), Weil Sie den Trick nicht so ausnutzen können, wie er ist, aber es gibt immer noch etwas, das Sie tun können, indem Sie immer noch veränderlich ausnutzen Art; Grundsätzlich setzen Sie einen veränderlichen "falschen" Standard in die Funktionssignatur und den gewünschten "echten" Standardwert in den Funktionskörper.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)
Es fühlt sich besonders lustig an, wenn Sie eine echte Standardeinstellung haben
None
, diese aberNone
unveränderlich ist. Sie müssen also weiterhin explizit eine veränderbare Variable als Standardparameter für die Funktion verwenden und im Code zu Keine wechseln.Verwenden einer
Default
Klasse für unveränderliche Standardeinstellungenoder, ähnlich wie bei einem @ cz-Vorschlag, wenn Python-Dokumente nicht ausreichen :-), können Sie ein Objekt dazwischen hinzufügen, um die API expliziter zu gestalten (ohne die Dokumente zu lesen); Die Klasseninstanz used_proxy_ Default ist veränderbar und enthält den tatsächlichen Standardwert, den Sie verwenden möchten.
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))
jetzt:
>>> f() default Default Value: 1 (<class 'int'>) argument is: 1 >>> f(2) passed in the call argument is: 2
Das obige funktioniert auch gut für
Default(None)
.Andere Muster
Offensichtlich sehen die obigen Muster hässlicher aus als sie sollten, weil all
print
diese nur dazu dienen, zu zeigen, wie sie funktionieren. Ansonsten finde ich sie knapp und wiederholbar genug.Sie könnten einen Dekorateur schreiben, um das
__call__
von @dmg vorgeschlagene Muster rationaler hinzuzufügen , aber dies zwingt immer noch dazu, seltsame Tricks in der Funktionsdefinition selbst zu verwenden - Sie müssten sich aufteilenvalue
undvalue_default
wenn Ihr Code sie unterscheiden muss, so Ich sehe keinen großen Vorteil und werde das Beispiel nicht schreiben :-)Veränderbare Typen als Standardwerte in Python
Ein bisschen mehr über # 1 Python Gotcha! , zu Ihrem eigenen Vergnügen oben missbraucht. Sie können sehen, was aufgrund der Bewertung bei der Definition passiert, indem Sie Folgendes tun:
def testme(default=[]): print(id(default))
Sie können so oft ausführen,
testme()
wie Sie möchten. Es wird immer ein Verweis auf dieselbe Standardinstanz angezeigt (Ihre Standardeinstellung ist also im Grunde unveränderlich :-)).Denken Sie daran , dass in Python gibt es nur 3 wandelbar eingebauten Typen :
set
,list
,dict
; alles andere - sogar Saiten! - ist unveränderlich.quelle
1
, der unveränderlich sein sollte ...def f(value={})
.1
; Es tut mir leid, wenn dies in der Erklärung nicht klar ist, aber der Sinn dieses Teils der Antwort besteht darin, einen unveränderlichen Standardwert (1
) zu haben. Wenn Sie das Beispiel überprüfen, werden Sie sehen, dass es sagt:print('default'); value = 1
nichtvalue={}
Nicht wirklich. Die Standardmethode besteht darin, einen Standardwert zu verwenden, den der Benutzer voraussichtlich nicht übergeben wird, z. B. eine
object
Instanz:DEFAULT = object() def foo(param=DEFAULT): if param is DEFAULT: ...
Normalerweise können Sie nur
None
als Standardwert verwenden, wenn dies als Wert, den der Benutzer übergeben möchte, keinen Sinn ergibt.Die Alternative ist zu verwenden
kwargs
:def foo(**kwargs): if 'param' in kwargs: param = kwargs['param'] else: ...
Dies ist jedoch zu ausführlich und erschwert die Verwendung Ihrer Funktion, da die Dokumentation den
param
Parameter nicht automatisch enthält .quelle
Ellipsis
Singleton als Standard verwenden, der explizit als Überspringen dieses Werts konzipiert wurde....
ist ein Alias fürEllipsis
, sodass Benutzer, die Positionsargumente verwenden möchten, einfach aufrufen können,your_function(p1, ..., p3)
was das Lesen offensichtlich und angenehm macht.However this is overly verbose and makes your function more difficult to use as its documentation will not automatically include the param parameter.
Dies ist eigentlich nicht wahr, da Sie die Beschreibung einer Funktion und ihrer Parameter mithilfe desinspect
Moduls festlegen können . Es hängt von Ihrer IDE ab, ob es funktioniert oder nicht.Der folgende Funktionsdekorator erstellt
explicit_checker
eine Reihe von Parameternamen für alle explizit angegebenen Parameter. Es fügt das Ergebnis als zusätzlichen Parameter (explicit_params
) zur Funktion hinzu.'a' in explicit_params
Überprüfen Sie einfach , ob der Parametera
explizit angegeben ist.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)
quelle
Ich verwende manchmal eine universell eindeutige Zeichenfolge (wie eine UUID).
import uuid DEFAULT = uuid.uuid4() def foo(arg=DEFAULT): if arg is DEFAULT: # it was not passed in else: # it was passed in
Auf diese Weise konnte kein Benutzer den Standard erraten, wenn er es versuchte, sodass ich sehr sicher sein kann, dass dieser Wert, wenn ich ihn sehe,
arg
nicht übergeben wurde.quelle
object()
anstattuuid4()
- es ist immer noch eine eindeutige Instanz , dieis
überprüft wirdIch habe dieses Muster ein paar Mal (zB Bibliothek gesehen
unittest
,py-flags
,jinja
):class Default: def __repr__( self ): return "DEFAULT" DEFAULT = Default()
... oder der entsprechende Einzeiler ...:
DEFAULT = type( 'Default', (), { '__repr__': lambda x: 'DEFAULT' } )()
Im Gegensatz dazu
DEFAULT = object()
unterstützt dies die Typprüfung und liefert Informationen, wenn Fehler auftreten. In Fehlermeldungen wird häufig entweder die Zeichenfolgendarstellung ("DEFAULT"
) oder der Klassenname ("Default"
) verwendet.quelle
@ Elliohs Antwort funktioniert in Python 2. In Python 3 sollte der folgende Code funktionieren:
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
Diese Methode kann die Argumentnamen und Standardwerte (anstelle von ** kwargs) mit besserer Lesbarkeit beibehalten.
quelle
Sie können es von
foo.__defaults__
und überprüfenfoo.__kwdefaults__
siehe ein einfaches Beispiel unten
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
dann mit dem Operator
=
undis
Sie können sie vergleichenund in einigen Fällen ist der folgende Code unten ausreichend
Sie müssen beispielsweise vermeiden, den Standardwert zu ändern, dann können Sie die Gleichheit überprüfen und dann kopieren, wenn dies der Fall ist
def update_and_show(data=Example): if data is Example: data = copy.deepcopy(data) update_inplace(data) #some operation print(data)
Auch ist es sehr bequem zu bedienen
getcallargs
aus ,inspect
da es gibt echte Argumente , mit denen Funktion aufgerufen wird. Wenn Sie eine Funktion und Argumente und Warnungen an it übergeben (inspect.getcallargs(func, /, *args, **kwds)
), werden die Argumente der realen Methode zurückgegeben, die für den Aufruf verwendet werden, wobei Standardwerte und andere Elemente berücksichtigt werden. Schauen Sie sich unten ein Beispiel an.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
quelle
Ich stimme dem Kommentar von Volatility zu. Sie können dies jedoch auf folgende Weise überprüfen:
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
quelle
def func(optional=value)
nicht**kwargs
**kwargs
etwas anders ist. PS kein Problem ist über -1 :) Und meine -1 für Sie war zufällig :)Dies ist eine Variation von Stefanos Antwort, aber ich finde sie etwas lesbarer:
not_specified = {} def foo(x=not_specified): if x is not_specified: print("not specified") else: print("specified")
quelle
Ein etwas verrückter Ansatz wäre:
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()
Welche Ausgänge:
passed default z passed different b not passed z
Nun, das ist, wie ich schon sagte, ziemlich verrückt, aber es macht den Job. Dies ist jedoch ziemlich unlesbar und wird ähnlich wie der Vorschlag von ecatmur nicht automatisch dokumentiert.
quelle
check_f('z')
einbeziehen, das, wie Sie sagen, auch verrückt ist.