Können Sie die Schlüsselwortargumente auflisten, die eine Funktion erhält?

104

Ich habe ein Diktat, das ich Schlüssel / Werte als Schlüsselwortargumente übergeben muss. Zum Beispiel.

d_args = {'kw1': 'value1', 'kw2': 'value2'}
example(**d_args)

Dies funktioniert einwandfrei , aber wenn das d_args-Diktat Werte enthält, die von der exampleFunktion nicht akzeptiert werden , stirbt es offensichtlich. Sagen wir , wenn die Beispielfunktion definiert ist alsdef example(kw2):

Dies ist ein Problem, da ich weder die Generierung der d_argsnoch die exampleFunktion steuere. Beide stammen von externen Modulen und exampleakzeptieren nur einige der Schlüsselwortargumente aus dem Diktat.

Idealerweise würde ich es einfach tun

parsed_kwargs = feedparser.parse(the_url)
valid_kwargs = get_valid_kwargs(parsed_kwargs, valid_for = PyRSS2Gen.RSS2)
PyRSS2Gen.RSS2(**valid_kwargs)

Ich werde das Diktat wahrscheinlich nur aus einer Liste gültiger Schlüsselwortargumente herausfiltern, aber ich habe mich gefragt: Gibt es eine Möglichkeit, die Schlüsselwortargumente, die eine bestimmte Funktion verwendet, programmgesteuert aufzulisten?

dbr
quelle

Antworten:

150

Ein wenig schöner als das direkte Überprüfen des Codeobjekts und das Erarbeiten der Variablen ist die Verwendung des Inspect-Moduls.

>>> import inspect
>>> def func(a,b,c=42, *args, **kwargs): pass
>>> inspect.getargspec(func)
(['a', 'b', 'c'], 'args', 'kwargs', (42,))

Wenn Sie wissen möchten, ob es mit einem bestimmten Satz von Argumenten aufrufbar ist, benötigen Sie die Argumente ohne einen bereits angegebenen Standard. Diese können erhalten werden von:

def getRequiredArgs(func):
    args, varargs, varkw, defaults = inspect.getargspec(func)
    if defaults:
        args = args[:-len(defaults)]
    return args   # *args and **kwargs are not required, so ignore them.

Dann ist eine Funktion, um festzustellen, was Ihnen in Ihrem speziellen Diktat fehlt:

def missingArgs(func, argdict):
    return set(getRequiredArgs(func)).difference(argdict)

Um nach ungültigen Argumenten zu suchen, verwenden Sie:

def invalidArgs(func, argdict):
    args, varargs, varkw, defaults = inspect.getargspec(func)
    if varkw: return set()  # All accepted
    return set(argdict) - set(args)

Ein vollständiger Test, wenn er aufrufbar ist, lautet also:

def isCallableWithArgs(func, argdict):
    return not missingArgs(func, argdict) and not invalidArgs(func, argdict)

(Dies ist nur in Bezug auf das Arg-Parsing von Python gut. Laufzeitprüfungen auf ungültige Werte in kwargs können offensichtlich nicht erkannt werden.)

Brian
quelle
Nett! Ich kannte diese Funktion nicht!
DzinX
1
Gibt es einen Vorteil, wenn ein weiteres Modul importiert werden muss, da die Methode mit Codeobjekten mehr oder weniger identisch ist ...?
jmetz
@jmets - definitiv - es ist praktisch immer besser, ein Bibliotheksmodul zu verwenden, als ein eigenes zu rollen. Außerdem sind die Attribute für das Codeobjekt interner und können geändert werden (z. B. beachten Sie, dass dies in pyhon3 in den Code verschoben wurde). Wenn Sie das Modul als Schnittstelle verwenden, sind Sie zukunftssicher, falls sich einige dieser Interna jemals ändern sollten. Es wird auch Dinge tun, an die Sie vielleicht nicht gedacht haben, wie einen entsprechenden Typfehler auf Funktionen werfen, die Sie nicht überprüfen können (z. B. C-Funktionen).
Brian
13
inspect.getargspec(f)ist seit Python 3.0 veraltet; Die moderne Methode ist inspect.signature(f).
Gerrit
Zu Ihrer Information, wenn Sie Cython und Python unterstützen möchten, funktioniert diese Methode nicht mit einer Cython-Funktion. Die co_varnamesOption funktioniert dagegen in beiden Fällen.
etwas
32

Dadurch werden die Namen aller passablen Argumente, Schlüsselwörter und Nicht-Schlüsselwörter, gedruckt:

def func(one, two="value"):
    y = one, two
    return y
print func.func_code.co_varnames[:func.func_code.co_argcount]

Dies liegt daran, dass zuerst co_varnamesimmer Parameter sind (als nächstes lokale Variablen, wie yim obigen Beispiel).

Jetzt könnten Sie also eine Funktion haben:

def getValidArgs(func, argsDict):
    '''Return dictionary without invalid function arguments.'''
    validArgs = func.func_code.co_varnames[:func.func_code.co_argcount]
    return dict((key, value) for key, value in argsDict.iteritems() 
                if key in validArgs)

Was Sie dann so verwenden könnten:

>>> func(**getValidArgs(func, args))

BEARBEITEN : Ein kleiner Zusatz: Wenn Sie wirklich nur Schlüsselwortargumente einer Funktion benötigen , können Sie diese mithilfe des func_defaultsAttributs extrahieren:

def getValidKwargs(func, argsDict):
    validArgs = func.func_code.co_varnames[:func.func_code.co_argcount]
    kwargsLen = len(func.func_defaults) # number of keyword arguments
    validKwargs = validArgs[-kwargsLen:] # because kwargs are last
    return dict((key, value) for key, value in argsDict.iteritems() 
                if key in validKwargs)

Sie können Ihre Funktion jetzt mit bekannten Argumenten aufrufen, aber kwargs extrahieren, z.

func(param1, param2, **getValidKwargs(func, kwargsDict))

Dies setzt voraus, dass funckeine *argsoder **kwargsMagie in seiner Signatur verwendet wird.

DzinX
quelle
Was ist, wenn ich nur "Schlüsselwort" -Argumente "Schlüssel" ausdrucken möchte?
Jia
7

In Python 3.0:

>>> import inspect
>>> import fileinput
>>> print(inspect.getfullargspec(fileinput.input))
FullArgSpec(args=['files', 'inplace', 'backup', 'bufsize', 'mode', 'openhook'],
varargs=None, varkw=None, defaults=(None, 0, '', 0, 'r', None), kwonlyargs=[], 
kwdefaults=None, annotations={})
jfs
quelle
7

Für eine Python 3-Lösung können Sie inspect.signaturenach den Parametern verwenden und filtern, die Sie kennen möchten.

Nehmen einer Beispielfunktion mit Positions- oder Schlüsselwort-, Nur-Schlüsselwort-, Var-Positions- und Var-Schlüsselwortparametern:

def spam(a, b=1, *args, c=2, **kwargs):
    print(a, b, args, c, kwargs)

Sie können ein Signaturobjekt dafür erstellen:

from inspect import signature
sig =  signature(spam)

und filtern Sie dann mit einem Listenverständnis, um die Details herauszufinden, die Sie benötigen:

>>> # positional or keyword
>>> [p.name for p in sig.parameters.values() if p.kind == p.POSITIONAL_OR_KEYWORD]
['a', 'b']
>>> # keyword only
>>> [p.name for p in sig.parameters.values() if p.kind == p.KEYWORD_ONLY]
['c']

und in ähnlicher Weise für var positionals using p.VAR_POSITIONALund var keyword with VAR_KEYWORD.

Darüber hinaus können Sie dem if eine Klausel hinzufügen, um zu überprüfen, ob ein Standardwert vorhanden ist, indem Sie prüfen, ob er p.defaultgleich ist p.empty.

Dimitris Fasarakis Hilliard
quelle
3

Erweiterung der Antwort von DzinX:

argnames = example.func_code.co_varnames[:func.func_code.co_argcount]
args = dict((key, val) for key,val in d_args.iteritems() if key in argnames)
example(**args)
Claudiu
quelle