Komme søkeord argumentene faktisk gått til en Python metode

stemmer
21

Jeg drømmer om en Python metode med eksplisitte søkeord args:

def func(a=None, b=None, c=None):
    for arg, val in magic_arg_dict.items():   # Where do I get the magic?
        print '%s: %s' % (arg, val)

Jeg ønsker å få en ordbok med bare de argumentene innringeren faktisk gått inn i metoden, akkurat som **kwargs, men jeg ønsker ikke den som ringer å kunne passere noen gamle tilfeldige args, i motsetning til **kwargs.

>>> func(b=2)
b: 2
>>> func(a=3, c=5)
a: 3
c: 5

Så: er det slik en besvergelse? I mitt tilfelle, jeg tilfeldigvis kunne sammenligne hvert argument mot sin standard for å finne de som er annerledes, men dette er slags uelegant og får kjedelig når du har ni argumenter. For bonuspoeng, gi en besvergelse som kan fortelle meg selv når den som ringer passerer inn et søkeord argument tildelt sin standardverdi:

>>> func(a=None)
a: None

Tricksy!

Edit: Den (leksikalsk) funksjon signatur må forbli intakt. Det er en del av et offentlig API, og den primære verdien av de eksplisitte søkeord args ligger i deres dokumentar verdi. Bare for å gjøre ting interessant. :)

Publisert på 11/09/2009 klokken 03:21
kilden bruker
På andre språk...                            


8 svar

stemmer
5

En mulighet:

def f(**kw):
  acceptable_names = set('a', 'b', 'c')
  if not (set(kw) <= acceptable_names):
    raise WhateverYouWantException(whatever)
  ...proceed...

IOW, er det veldig lett å sjekke at de gikk inn navnene er innenfor det akseptable sett og ellers heve hva du ønsker Python å heve (Typeerror, antar jeg ;-). Ganske enkelt å slå inn en dekoratør, btw.

En annen mulighet:

_sentinel = object():
def f(a=_sentinel, b=_sentinel, c=_sentinel):
   ...proceed with checks `is _sentinel`...

ved å lage et unikt objekt _sentineldu fjerner risikoen for at den som ringer kan være et uhell passerer None(eller andre ikke-unike standardverdier innringeren kunne passere). Dette er alt object()som er bra for, btw: en ekstremt lett, unik sentinel som ikke kan muligens være et uhell forveksles med en annen gjenstand (når du sjekke med isoperatøren).

Enten løsning er å foretrekke for litt ulike problemer.

Svarte 11/09/2009 kl. 03:31
kilden bruker

stemmer
2

Hva med å bruke en dekoratør å validere innkommende kwargs?

def validate_kwargs(*keys):
    def entangle(f):
        def inner(*args, **kwargs):
            for key in kwargs:
                if not key in keys:
                    raise ValueError("Received bad kwarg: '%s', expected: %s" % (key, keys))
            return f(*args, **kwargs)
        return inner
    return entangle

###

@validate_kwargs('a', 'b', 'c')
def func(**kwargs):
   for arg,val in kwargs.items():
       print arg, "->", val

func(b=2)
print '----'
func(a=3, c=5)
print '----'
func(d='not gonna work')

Gir denne produksjonen:

b -> 2
----
a -> 3
c -> 5
----
Traceback (most recent call last):
  File "kwargs.py", line 20, in <module>
    func(d='not gonna work')
  File "kwargs.py", line 6, in inner
    raise ValueError("Received bad kwarg: '%s', expected: %s" % (key, keys))
ValueError: Received bad kwarg: 'd', expected: ('a', 'b', 'c')
Svarte 11/09/2009 kl. 03:34
kilden bruker

stemmer
0

Det er nok bedre måter å gjøre dette på, men her er min ta:

def CompareArgs(argdict, **kwargs):
    if not set(argdict.keys()) <= set(kwargs.keys()):
        # not <= may seem weird, but comparing sets sometimes gives weird results.
        # set1 <= set2 means that all items in set 1 are present in set 2
        raise ValueError("invalid args")

def foo(**kwargs):
    # we declare foo's "standard" args to be a, b, c
    CompareArgs(kwargs, a=None, b=None, c=None)
    print "Inside foo"


if __name__ == "__main__":
    foo(a=1)
    foo(a=1, b=3)
    foo(a=1, b=3, c=5)
    foo(c=10)
    foo(bar=6)

og dens utgang:

inne foo
inne foo
inne foo
inne foo
Tilbakesporings (siste samtale sist):
  Fil "a.py", linje 18, 
    foo (bar = 6)
  Fil "a.py", linje 9, foo
    CompareArgs (kwargs, en = Ingen, b = Ingen, c = Ingen)
  Fil "a.py", linje 5, i CompareArgs
    heve ValueError ( "ugyldige args")
ValueError: ugyldige args

Dette kan trolig bli konvertert til en dekoratør, men mine dekoratører trenger arbeid. Igjen som en øvelse til leseren: P

Svarte 11/09/2009 kl. 03:36
kilden bruker

stemmer
0

Kanskje heve en feil hvis de passerer noen * args?

def func(*args, **kwargs):
  if args:
    raise TypeError("no positional args allowed")
  arg1 = kwargs.pop("arg1", "default")
  if kwargs:
    raise TypeError("unknown args " + str(kwargs.keys()))

Det ville være enkelt å faktor det til å ta en liste over varnames eller et generisk parsing funksjonen skal bruke. Det ville ikke være for vanskelig å gjøre dette til en dekoratør (python 3.1), også:

def OnlyKwargs(func):
  allowed = func.__code__.co_varnames
  def wrap(*args, **kwargs):
    assert not args
    # or whatever logic you need wrt required args
    assert sorted(allowed) == sorted(kwargs)
    return func(**kwargs)

Merk: Jeg er ikke sikker på hvor godt dette ville omgå allerede pakket funksjoner eller funksjoner som har *argseller **kwargsallerede.

Svarte 11/09/2009 kl. 03:37
kilden bruker

stemmer
12

Her er den enkleste og enkleste måten:

def func(a=None, b=None, c=None):
    args = locals().copy()
    print args

func(2, "egg")

Dette gir output: {'a': 2, 'c': None, 'b': 'egg'}. Årsaken argsskal være en kopi av localsordboken er at ordbøkene er foranderlig, så hvis du opprettet noen lokale variabler i denne funksjonen argsvil inneholde alle de lokale variablene og deres verdier, ikke bare argumenter.

Mer dokumentasjon på den innebygde localsfunksjon her .

Svarte 11/09/2009 kl. 03:37
kilden bruker

stemmer
24

Jeg ble inspirert av fraværs teori er dekoratør godhet, og etter å ha spilt rundt med den for litt kom opp med dette:

def actual_kwargs():
    """
    Decorator that provides the wrapped function with an attribute 'actual_kwargs'
    containing just those keyword arguments actually passed in to the function.
    """
    def decorator(function):
        def inner(*args, **kwargs):
            inner.actual_kwargs = kwargs
            return function(*args, **kwargs)
        return inner
    return decorator


if __name__ == "__main__":

    @actual_kwargs()
    def func(msg, a=None, b=False, c='', d=0):
        print msg
        for arg, val in sorted(func.actual_kwargs.iteritems()):
            print '  %s: %s' % (arg, val)

    func("I'm only passing a", a='a')
    func("Here's b and c", b=True, c='c')
    func("All defaults", a=None, b=False, c='', d=0)
    func("Nothin'")
    try:
        func("Invalid kwarg", e="bogon")
    except TypeError, err:
        print 'Invalid kwarg\n  %s' % err

Som skriver dette:

Jeg bare passerer en
  a: en
Her er b og c
  b: Sann
  c: c
alle mislighold
  a: Ingen
  b: False
  c: 
  d: 0
Ingenting'
Ugyldig kwarg
  funk () fikk en uventet ordet argument 'e'

Jeg er fornøyd med dette. En mer fleksibel tilnærming er å passere navnet på attributtet du vil bruke til å dekoratør, i stedet for hardkode det å 'actual_kwargs', men dette er den enkleste tilnærmingen som illustrerer løsningen.

Mmm, er Python velsmakende.

Svarte 11/09/2009 kl. 06:25
kilden bruker

stemmer
0

Magien er ikke svaret:

def funky(a=None, b=None, c=None):
    for name, value in [('a', a), ('b', b), ('c', c)]:
        print name, value
Svarte 11/09/2009 kl. 06:53
kilden bruker

stemmer
1

Dette blir lettest oppnådd med en enkelt forekomst av en vakt objekt:

# Top of module, does not need to be exposed in __all__
missing = {}

# Function prototype
def myFunc(a = missing, b = missing, c = missing):
    if a is not missing:
        # User specified argument a
    if b is missing:
        # User did not specify argument b

Det fine med denne tilnærmingen er at, siden vi bruker "er" operatør, kan den som ringer passere en tom dict som argument verdi, og vi vil likevel plukke opp at de ikke mente å passere den. Vi unngår også ekle dekoratører på denne måten, og holde vår kode litt renere.

Svarte 08/05/2012 kl. 18:46
kilden bruker

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more