python - propias - ¿Cómo llamar a una función con un diccionario que contiene más elementos de los que la función tiene parámetros?
lista de funciones de python (6)
Estoy buscando la mejor manera de combinar una función con un diccionario que contenga más elementos que las entradas de la función
El desempaque de kwarg básico ** falla en este caso:
def foo(a,b):
return a + b
d = {''a'':1,
''b'':2,
''c'':3}
foo(**d)
--> TypeError: foo() got an unexpected keyword argument ''c''
Después de algunas investigaciones se me ocurrió el siguiente enfoque:
import inspect
# utilities
def get_input_names(function):
''''''get arguments names from function''''''
return inspect.getargspec(function)[0]
def filter_dict(dict_,keys):
return {k:dict_[k] for k in keys}
def combine(function,dict_):
''''''combine a function with a dictionary that may contain more items than the function''s inputs ''''''
filtered_dict = filter_dict(dict_,get_input_names(function))
return function(**filtered_dict)
# examples
def foo(a,b):
return a + b
d = {''a'':1,
''b'':2,
''c'':3}
print combine(foo,d)
--> 3
Mi pregunta es: ¿es esta una buena manera de lidiar con este problema, o hay una mejor práctica o existe un mecanismo en el lenguaje que quizás falto?
¿Qué tal hacer un decorator que filtraría solo los argumentos de palabras clave permitidos ?
import inspect
def get_input_names(function):
''''''get arguments names from function''''''
return inspect.getargspec(function)[0]
def filter_dict(dict_,keys):
return {k:dict_[k] for k in keys}
def filter_kwargs(func):
def func_wrapper(**kwargs):
return func(**filter_dict(kwargs, get_input_names(func)))
return func_wrapper
@filter_kwargs
def foo(a,b):
return a + b
d = {''a'':1,
''b'':2,
''c'':3}
print(foo(**d))
Lo bueno de este decorador es que es genérico y reutilizable. Y no necesitaría cambiar la forma en que llama y usa sus funciones de destino.
Esto todavía está modificando la función original, pero puede crear un bitbucket kwargs al final de la lista de argumentos:
def foo(a, b, **kwargs):
return a + b
foo(**{
''a'': 5,
''b'': 8,
''c'': ''🐘''
}) # 13
Haría algo como esto:
def combine(function, dictionary):
return function(**{key:value for key, value in dictionary.items()
if key in inspect.getargspec(function)[0]}
)
Utilizar:
>>> def this(a, b, c=5):
... print(a, b, c)
...
>>> combine(this, {''a'': 4, ''b'': 6, ''c'': 6, ''d'': 8})
4 6 6
>>> combine(this, {''a'': 6, ''b'': 5, ''d'': 8})
6 5 5
Su problema radica en la forma en que definió su función, debería definirse así:
def foo(**kwargs):
Y luego, dentro de la función, puede iterar sobre el número de argumentos enviados a la función como tal:
if kwargs is not None:
for key, value in kwargs.iteritems():
do something
Puede encontrar más información sobre el uso de ** kwargs en este post - http://pythontips.com/2013/08/04/args-and-kwargs-in-python-explained/
También puede usar una función decoradora para filtrar los argumentos de palabras clave que no están permitidos en su función. De usted utiliza la función de signature
nueva en 3.3 para devolver su función Signature
from inspect import signature
from functools import wraps
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
sig = signature(func)
result = func(*[kwargs[param] for param in sig.parameters])
return result
return wrapper
Desde Python 3.0 puede usar getargspec
que está en desuso desde la versión 3.0
import inspect
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
argspec = inspect.getargspec(func).args
result = func(*[kwargs[param] for param in argspec])
return result
return wrapper
Para aplicar su decoración a una función existente, debe pasar su función como argumento a su decorador:
Manifestación:
>>> def foo(a, b):
... return a + b
...
>>> foo = decorator(foo)
>>> d = {''a'': 1, ''b'': 2, ''c'': 3}
>>> foo(**d)
3
Para aplicar su decorador a una nueva función simplemente use @
>>> @decorator
... def foo(a, b):
... return a + b
...
>>> foo(**d)
3
También puede definir su función utilizando argumentos de palabras clave arbitrarias **kwargs
.
>>> def foo(**kwargs):
... if ''a'' in kwargs and ''b'' in kwargs:
... return kwargs[''a''] + kwargs[''b'']
...
>>> d = {''a'': 1, ''b'': 2, ''c'': 3}
>>> foo(**d)
3
Todas estas respuestas son incorrectas.
No es posible hacer lo que está pidiendo, porque la función podría declararse así:
def foo(**kwargs):
a = kwargs.pop(''a'')
b = kwargs.pop(''b'')
if kwargs:
raise TypeError(''Unexpected arguments: %r'' % kwargs)
Ahora, ¿por qué alguien escribiría eso?
Porque no conocen todos los argumentos de antemano. Aquí hay un caso más realista:
def __init__(self, **kwargs):
for name in self.known_arguments():
value = kwargs.pop(name, default)
self.do_something(name, value)
super().__init__(**kwargs) # The superclass does not take any arguments
Y here hay un código del mundo real que realmente hace esto.
Puede preguntar por qué necesitamos la última línea. ¿Por qué pasar argumentos a una superclase que no toma ninguno? Cooperativa de herencia múltiple . Si mi clase obtiene un argumento que no reconoce, no debería tragarlo, ni debería cometer un error. Debería pasar el argumento a lo largo de la cadena para que otra clase que no conozco pueda manejarlo. Y si nadie lo maneja, entonces el object.__init__()
proporcionará un mensaje de error apropiado. Desafortunadamente, las otras respuestas no lo manejarán con gracia. Verán **kwargs
y no pasarán ningún argumento o pasarán todos, lo que es incorrecto.
El resultado final : no hay una forma general de descubrir si una llamada de función es legal sin hacerla realmente. inspect
es una aproximación cruda, y se desmorona por completo frente a las funciones variad. Variadic no significa "pasa lo que quieras"; significa "las reglas son demasiado complejas para expresar en una firma". Como resultado, mientras que en muchos casos es posible hacer lo que usted está tratando de hacer, siempre habrá situaciones en las que no hay una respuesta correcta.