2009-09-18 12 views
8

Se distribuye un generic function en función del tipo de todos sus argumentos. El programador define varias implementaciones de una función. El correcto se elige en el tiempo de llamada en función de los tipos de sus argumentos. Esto es útil para la adaptación de objetos, entre otras cosas. Python tiene algunas funciones genéricas que incluyen len().¿Cuál es la implementación de funciones genéricas mejor mantenida para Python?

Estos paquetes tienden a permitir que el código que se parece a esto:

@when(int) 
def dumbexample(a): 
    return a * 2 

@when(list) 
def dumbexample(a): 
    return [("%s" % i) for i in a] 

dumbexample(1) # calls first implementation 
dumbexample([1,2,3]) # calls second implementation 

Un ejemplo menos tonto que he estado pensando últimamente sería un componente web que requiere un usuario. En lugar de requerir un marco web en particular, el integrador sería sólo tiene que escribir algo como:

class WebComponentUserAdapter(object): 
    def __init__(self, guest): 
     self.guest = guest 
    def canDoSomething(self): 
     return guest.member_of("something_group") 

@when(my.webframework.User) 
componentNeedsAUser(user): 
    return WebComponentUserAdapter(user) 

Python tiene algunas funciones implementaciones genéricas. ¿Por qué elegiría uno sobre los demás? ¿Cómo se usa esa implementación en las aplicaciones?

Estoy familiarizado con zope.component.queryAdapter(object, ISomething) de Zope. El programador registra un adaptador invocable que toma una clase particular de objeto como argumento y devuelve algo compatible con la interfaz. Es una forma útil de permitir complementos. A diferencia del parche de mono, funciona incluso si un objeto necesita adaptarse a múltiples interfaces con los mismos nombres de método.

+1

La función 'len' simplemente llama al método' __len__' de un objeto. ¿Es esto lo que quieres decir con función "genérica"? Si es así, hay relativamente pocas funciones que se comporten así. ¿Tienes algo más en mente? Si es así, elija un mejor ejemplo. –

+0

¿Estás hablando de polimorfismo? ¿O estás hablando de parches de mono para agregar polimorfismos? –

+0

parece que (basado en el enlace? Y el wikipedia proporcionado) joeforker está buscando implementar funciones sobrecargadas. Creo que se está refiriendo a "genérico" en el sentido de que la función se llama por 1 nombre pero ejecuta un código diferente en función de los parámetros pasados. Por lo tanto : mi_funcion (a) -> ejecutar funcion_a mi_funcion (b) -> ejecutar funcion_b Es una aplicación más elegante de analizar las ** kwargs para conseguir que codifican para ejecutar – Mark

Respuesta

7

Recomendaría la biblioteca PEAK-Rules de P. Eby. Por el mismo autor (aunque obsoleto) es el paquete RuleDispatch (el predecesor de PEAK-Rules). Este último ya no se mantiene IIRC.

PEAK-Rules tiene muchas características agradables, una de ellas es que (bueno, no es fácil, pero) extensible. Además del despacho "clásico" en los tipos ony, presenta el envío en expresiones arbitrarias como "guardianes".

La función len() no es una verdadera función genérica (al menos en el sentido de los paquetes mencionados anteriormente, y también en el sentido, este término se utiliza en idiomas como el Common Lisp, Dylan o Cecil), ya que es simplemente una sintaxis conveniente para una llamada de método a nombrado especialmente (pero por lo demás normal):

len(s) == s.__len__() 

también tenga en cuenta, que este es un solo despacho única, es decir, el receptor real (s en el código anterior) determina la implementación del método llamado. E incluso un hipotético

def call_special(receiver, *args, **keys): 
    return receiver.__call_special__(*args, **keys) 

es todavía una función de un solo envío, ya que sólo el receptor se utiliza cuando se resuelve el método a ser llamado. Los argumentos restantes simplemente se transmiten, pero no afectan la selección del método.

Esto es diferente de despacho múltiple, donde no hay un receptor dedicado, y todos los argumentos se utilizan para encontrar la implementación del método real para llamar. Esto es, lo que realmente hace que todo valga la pena. Si fuera solo un tipo extraño de azúcar sintáctico, nadie se molestaría en usarlo, en mi humilde opinión.

from peak.rules import abstract, when 

@abstract 
def serialize_object(object, target): 
    pass 

@when(serialize_object, (MyStuff, BinaryStream)) 
def serialize_object(object, target): 
    target.writeUInt32(object.identifier) 
    target.writeString(object.payload) 

@when(serialize_object, (MyStuff, XMLStream)) 
def serialize_object(object, target): 
    target.openElement("my-stuff") 
    target.writeAttribute("id", str(object.identifier)) 
    target.writeText(object.payload) 
    target.closeElement() 

En este ejemplo, una llamada como

serialize_object(MyStuff(10, "hello world"), XMLStream()) 

considera ambos argumentos con el fin de decidir, método que en realidad debe ser llamado.

Para un buen escenario de uso de funciones genéricas en Python, recomiendo leer el código refactorizado de peak.security que proporciona una solución muy elegante para acceder a la comprobación de permisos mediante funciones genéricas (usando RuleDispatch).

+0

¿Qué pasa http: // PyPI .python.org/pypi/simplegeneric también por PJE? – joeforker

+0

AFAIK es una implementación bastante simple, que solo hace un despacho basado en tipos. Realmente no lo he investigado, así que no puedo decir nada al respecto. Mis experiencias son principalmente con 'RuleDispatch' y su sucesor. – Dirk

+1

simplegeneric es mucho más simple, pero, creo, no tan activamente mantenido como el PEAK más complejo y rico. –

0

Se puede utilizar una construcción como esta:

def my_func(*args, **kwargs): 
    pass 

En este caso args será una lista de argumentos sin nombre, y kwargs habrá un diccionario de los nombrados. Desde aquí puede detectar sus tipos y actuar según corresponda.

+1

Esto no tiene sentido en el contexto de la pregunta revisada. –

+0

Ya, parecía la respuesta correcta en ese momento. – defrex

0

No puedo ver el punto en estas funciones "genéricas". Suena como un polimorfismo simple.

Sus características "genéricas" pueden implementarse así sin recurrir a ninguna identificación de tipo de tiempo de ejecución.

class intWithDumbExample(int): 
    def dumbexample(self): 
     return self*2 

class listWithDumbExmaple(list): 
    def dumpexample(self): 
     return [("%s" % i) for i in self] 

def dumbexample(a): 
    return a.dumbexample() 
+1

Su enfoque no permite llamadas a 'dumbexample (1)' de acuerdo con las especificaciones del OP (y como permiten todos los paquetes de funciones genéricas) - necesita engorroso ajuste en cada llamada, generalmente con conmutación basada en tipos (eep). Pero el polimorfismo basado en UN tipo sigue siendo bastante correcto: cuando el OOP clásico se desmorona por completo y las funciones genéricas (como en Common Lisp, Dylan, ...) realmente brillan en el despacho basado en tipos de argumentos MÚLTIPLES (evitando klutzes como Visitor, __radd__ vs __add__, y un billón de otros kludges OOP necesita allí). –

+0

@Alex Martelli: Y, presumiblemente, evitar los problemas de coerción y los problemas de doble despacho. La pregunta de OP, y ejemplo, no tocó nada de esto. Todavía no estoy seguro de la pregunta * real *. O tal vez la pregunta era simplemente hipotética. –

Cuestiones relacionadas