2009-09-11 64 views
17

Supongamos que tengo una función genérica f. Quiero programmatically crear una función f2 que se comporta igual que f, pero tiene una firma personalizada.Establecer firma de función en Python

Más detalles

Dada una lista ly y un diccionario d Quiero ser capaz de:

  • Establecer los argumentos que no sean de palabras clave de f2 para las cuerdas en l
  • Conjunto los argumentos de palabra clave de f2 a las claves en d y los valores predeterminados a los valores de d

es decir. Supongamos que tenemos

l=["x", "y"] 
d={"opt":None} 

def f(*args, **kwargs): 
    #My code 

Entonces me gustaría que una función con la firma:

def f2(x, y, opt=None): 
    #My code 

un uso específico caso

Esto es sólo una versión simplificada de mi caso de uso específico. Estoy dando esto como un ejemplo solamente.

Mi caso de uso real (simplificado) es el siguiente. Tenemos una función de iniciación genérica:

def generic_init(self,*args,**kwargs): 
    """Function to initiate a generic object""" 
    for name, arg in zip(self.__init_args__,args): 
     setattr(self, name, arg) 
    for name, default in self.__init_kw_args__.items(): 
     if name in kwargs: 
      setattr(self, name, kwargs[name]) 
     else: 
      setattr(self, name, default) 

Queremos utilizar esta función en varias clases. En particular, queremos crear una función init que se comporta como generic_init, pero tiene la firma definida por algunas variables de clase en hora de creación:

class my_class: 
    __init_args__=["x", "y"] 
    __kw_init_args__={"my_opt": None} 

__init__=create_initiation_function(my_class, generic_init) 
setattr(myclass, "__init__", __init__) 

Queremos create_initiation_function para crear una nueva función con la firma definido usando init_args y kw_init_args. ¿Es posible escribir create_initiation_function?

Tenga en cuenta:

  • Si sólo quería mejorar la ayuda, pude establecer doc.
  • Queremos establecer la firma de la función en la creación. Después de eso, no necesita ser cambiado.
  • En lugar de crear una función como generic_init, pero con una firma diferente, podríamos crear una nueva función con la firma deseada que simplemente llama a generic_init
  • Queremos definir create_initiation_function. ¡No queremos especificar manualmente la nueva función!

relacionada

+0

Obviamente, desea saber algo completamente diferente de lo que está preguntando. ¿Tal vez deberías explicar cuál es el problema que estás teniendo? Eso podría decirnos lo que en realidad estás tratando de preguntar. –

+1

¿Estás preguntando cómo crear programáticamente esa función?De todos modos, creo que debes darnos un contexto. ¿Cómo quieres usarlo? ¿Cómo quieres especificar el cuerpo de la función? ¿Podrías mostrarnos cómo te gustaría usarlo? –

+0

¿Está más claro ahora que lo he editado de nuevo? – Casebash

Respuesta

13

Para su caso de uso, tener un docstring en la clase/la función debería funcionar, eso aparecerá en la ayuda() está bien, y puede establecerse mediante programación (func .__ doc__ = "cosas").

No veo ninguna forma de establecer la firma real. Pensé que el functools module lo habría hecho si fuera factible, pero no lo es, al menos en py2.5 y py2.6.

También puede generar una excepción TypeError si obtiene una entrada incorrecta.

Hmm, si no te importa ser realmente malo, puedes usar compile()/eval() para hacerlo. Si su firma deseada se especifica arglist = [ "foo", "barra", "baz"], y su función real es F (* args, ** kwargs), puede administrar:

argstr = ", ".join(arglist) 
fakefunc = "def func(%s):\n return real_func(%s)\n" % (argstr, argstr) 
fakefunc_code = compile(fakefunc, "fakesource", "exec") 
fakeglobals = {} 
eval(fakefunc_code, {"real_func": f}, fakeglobals) 
f_with_good_sig = fakeglobals["func"] 

help(f)    # f(*args, **kwargs) 
help(f_with_good_sig) # func(foo, bar, baz) 

Cambio docstring y func_name deberían ofrecerle una solución completa. Pero, uh, eww ...

+2

Debería haber pensado en eval. Cualquier cosa es posible con eso. – Casebash

+7

Este es el patrón utilizado por el paquete de decorador - http://pypi.python.org/pypi/decorator/ –

+1

completamente vil? esto es exactamente lo que 'namedtuple' hace. –

-4

Edición 1: Responder a nueva pregunta:

Te preguntas cómo se puede crear una función con esta firma:

def fun(a, b, opt=None): 
    pass 

El forma correcta de hacerlo en Python es así:

def fun(a, b, opt=None): 
    pass 

Edición 2: Respondiendo explicación:

"Supongamos que tengo una función genérica f. Quiero crear mediante programación una función f2 que comporta del mismo modo M, pero tiene una firma personalizada "

def f(*args, **kw): 
    pass 

OK, entonces f2 se ve así:.

def f2(a, b, opt=None): 
    f(a, b, opt=opt) 

Una vez más, la respuesta a su la pregunta es tan trivial, que obviamente quiere saber algo diferente de lo que está preguntando. Realmente necesita dejar de hacer preguntas abstractas y explicar su problema concreto.

+2

No responde la pregunta. Lo siento por no estar claro, lo he aclarado ahora. – Casebash

+0

Respondió la pregunta. También actualicé la respuesta para reflejar sus cambios. De nuevo responde la pregunta. Probablemente no le diga lo que quiere saber, pero primero tiene que decirnos lo que quiere saber. –

+0

Estaba bastante seguro de que el significado estaba claro después de editarlo, pero lo he editado de nuevo. – Casebash

0

No puede hacer esto con el código en vivo.

Es decir, que parecen estar querer tomar una función real, en vivo que tiene este aspecto:

def f(*args, **kwargs): 
    print args[0] 

y cambiarlo a uno como este:

def f(a): 
    print a 

La razón de que esto puede Que se haga, al menos sin modificar el bytecode de Python real, es porque compilan de forma diferente.

El primero da como resultado una función que recibe dos parámetros: una lista y un dict, y el código que está escribiendo opera en esa lista y dict. El segundo da como resultado una función que recibe un parámetro y al que se accede directamente como una variable local. Si ha cambiado la función de "firma", por así decirlo, que sería el resultado de una función como esta:

def f(a): 
    print a[0] 

que obviamente no iba a funcionar.

Si desea más detalles (aunque realmente no lo ayudan), una función que toma un * args o * kwargs tiene uno o dos bits establecidos en f.func_code.co_flags; usted puede examinar esto usted mismo. La función que toma un parámetro regular tiene f.func_code.co_argcount establecido en 1; la * versión de args es 0. Esto es lo que Python usa para descubrir cómo configurar el marco de pila de la función cuando se llama, para verificar los parámetros, etc.

Si quieres jugar modificando la función directamente-- si solo para convencerse de que no funcionará, consulte this answer para saber cómo crear un objeto de código y una función en vivo a partir de una existente para modificar bits de la misma. (Esto está documentado en alguna parte, pero no puedo encontrarlo, no está en ninguna parte en los documentos del módulo de tipos ...)

Dicho esto, puede cambiar dinámicamente la cadena de documentos de una función. Simplemente asigne al func.__doc__. Asegúrese de hacerlo solo en el momento de la carga (desde el contexto global o, lo más probable, un decorador); si lo haces más adelante, las herramientas que cargan el módulo para examinar las cadenas de documentos nunca lo verán.

+0

No necesito cambiar dinámicamente la firma de una función, solo para poder crear una función que haga lo mismo que otra función y tenga una firma diferente – Casebash

+0

Esa es la ... misma cosa. –

+0

No del todo. Como señaló Towns, podríamos simplemente crear una nueva función utilizando eval que pueda llamar a la función genérica – Casebash

1

Queremos create_initiation_function cambiar la firma

Por favor, no hagas esto.

queremos utilizar esta función en una serie de clases

Utilice la herencia común.

No hay ningún valor en tener la firma "modificada" en tiempo de ejecución.

Está creando una pesadilla de mantenimiento. Nadie más se molestará en descubrir lo que estás haciendo. Simplemente lo arrancarán y lo reemplazarán con herencia.

Haga esto en su lugar. Es simple y obvio, y hace que tu init genérico esté disponible en todas las subclases de una manera evidente, simple y pitonica.

class Super(object): 
    def __init__(self, *args, **kwargs): 
     # the generic __init__ that we want every subclass to use 

class SomeSubClass(Super): 
    def __init__(self, this, that, **kwdefaults): 
     super(SomeSubClass, self).__init__(this, that, **kwdefaults) 

class AnotherSubClass(Super): 
    def __init__(self, x, y, **kwdefaults): 
     super(AnotherSubClass, self).__init__(x, y, **kwdefaults) 
0

Tal vez no entendían bien el problema, pero si se trata de mantener el mismo comportamiento mientras se cambia la firma de la función, entonces usted puede hacer algo como:

# define a function 
def my_func(name, age) : 
    print "I am %s and I am %s" % (name, age) 

# label the function with a backup name 
save_func = my_func 

# rewrite the function with a different signature 
def my_func(age, name) : 
    # use the backup name to use the old function and keep the old behavior 
    save_func(name, age) 

# you can use the new signature 
my_func(35, "Bob") 

Este salidas:

I am Bob and I am 35 
+0

. Necesita ser programáticamente. Es difícil de explicar, pero supongo que una última edición no me mataría – Casebash

11

de PEP-0362, en realidad parece haber una manera de establecer la firma en py3.3 +, utilizando el atributo fn.__signature__:

def shared_vars(*shared_args): 
    """Decorator factory that defines shared variables that are 
     passed to every invocation of the function""" 

    def decorator(f): 
     @wraps(f) 
     def wrapper(*args, **kwargs): 
      full_args = shared_args + args 
      return f(*full_args, **kwargs) 

     # Override signature 
     sig = signature(f) 
     sig = sig.replace(parameters=tuple(sig.parameters.values())[1:]) 
     wrapper.__signature__ = sig 

     return wrapper 
    return decorator 

continuación:

>>> @shared_vars({"myvar": "myval"}) 
>>> def example(_state, a, b, c): 
>>>  return _state, a, b, c 
>>> example(1,2,3) 
({'myvar': 'myval'}, 1, 2, 3) 
>>> str(signature(example)) 
'(a, b, c)' 

Nota: el PPE no es exactamente correcto; Signature.replace movió los parámetros de un arg posicional a un arg solo-kw.

Cuestiones relacionadas