(Estoy desarrollando en Python 3.1, así que si hay alguna nueva característica brillante 3.x que debería conocer para esto, ¡hágamelo saber!)

Tengo una clase (la llamaremos "Paquete") que sirve como padre para un montón de clases secundarias que representan cada una de las docenas de tipos de paquetes en un protocolo heredado cliente-servidor sobre el que no tengo control. (Los paquetes a menudo tienen un comportamiento muy diferente, así que les di a cada uno su propia clase para hacerme la vida más fácil).

Al recibir un paquete, tendré una simple función de "despacho" que verifica el encabezado del paquete para determinar el tipo, luego lo entrega a la clase que sabe cómo manejarlo.

no quiero mantener la tabla de búsqueda a mano, es poco elegante y solo me pide problemas. En cambio, me gustaría construir la tabla en tiempo de ejecución examinando todas las subclases de Packet, que tendrán variables de clase que especifican a qué tipo de paquete corresponden, por ejemplo:

class Login(Packet):
    type_id = 0x01

Pensé, por supuesto, en iterar a través de object.__subclasses__(), pero he encontrado puntos de vista ligeramente diferentes sobre la seguridad y la propiedad de usarlo para cosas como esta, incluidas las implicaciones de que es específico de la implementación y puede no funcionar en lugares aparte de CPython, y que puede desaparecer de CPython en el futuro. ¿Estoy siendo demasiado paranoico? ¿Se considera __subclassess__ una parte "real" del lenguaje ahora?

Si no, ¿cuál es una buena forma "pitónica" de abordar esto?

0
Nicholas Knight 20 oct. 2009 a las 06:00

4 respuestas

La mejor respuesta

__subclasses__ ES parte del lenguaje Python, e implementado por IronPython y Jython, así como por CPython (no hay pypy a mano para probar, en este momento, ¡pero me sorprendería si lo han roto! -). ¿Qué le dio la impresión de que __subclasses__ fue problemático ?! Veo un comentario de @gnibbler en la misma línea y me gustaría cuestionarlo: ¿puede publicar URL sobre __subclasses__ que no es una parte crucial del lenguaje Python ?!

3
Alex Martelli 20 oct. 2009 a las 03:02

Supongo que puedes usar las metaclases de Python (Python ≥ 2.2) para compartir dicha información entre clases, eso sería bastante pitónico. Eche un vistazo a la implementación de los Búfers de protocolo de Google. Aquí está el tutorial que muestra metaclases en el trabajo. Por cierto, el dominio de Protocol Buffers es similar al tuyo.

1
Andrey Vlasovskikh 20 oct. 2009 a las 02:11

>>> class PacketMeta(type):
...     def __init__(self,*args,**kw):
...         if self.type_id is not None:
...             self.subclasses[self.type_id]=self
...         return type.__init__(self,*args,**kw)
... 
>>> class Packet(object):
...     __metaclass__=PacketMeta
...     subclasses={}
...     type_id = None
... 
>>> class Login(Packet):
...     type_id = 0x01
... 
>>> class Logout(Packet):
...     type_id = 0x02
... 
>>> 
>>> Packet.subclasses
{1: <class '__main__.Login'>, 2: <class '__main__.Logout'>}
>>> 

Si prefiere usar el __subclasses__(), puede hacer algo como esto

>>> class Packet(object):
...     pass
... 
>>> class Login(Packet):
...     type_id = 0x01
... 
>>> class Logout(Packet):
...     type_id = 0x02
... 
def packetfactory(packet_id):
    for x in Packet.__subclasses__():
        if x.type_id==packet_id:
            return x
... 
>>> packetfactory(0x01)
<class '__main__.Login'>
>>> packetfactory(0x02)
<class '__main__.Logout'>
2
John La Rooy 20 oct. 2009 a las 04:54

"Me gustaría construir la tabla en tiempo de ejecución examinando todas las subclases de Packet".

Esto garantiza causar problemas sin fin. Este tipo de cosas pone una restricción extraña en sus subclases. No puedes usar ninguna superclase abstracta para simplificar las cosas.

Aquí hay un ejemplo específico de lo que no funcionará si descubre "automáticamente" las subclases.

class MessagePacket( object ):
    """superclass.  Ignore me, I'm abstract."""
class Type_01( MessagePacket ):
    type_id = 0x01
class Type_023( MessagePacket ):
    """superclass with common features for type 2 and 3.  Ignore me, I'm abstract."""
class Type_02( Type_023 ):
    type_id = 0x02
class Type_03( Type_023 ):
    type_id = 0x03
class Type_04( MessagePacket ):
    """superclass for subtypes of 4.  Ignore me, I'm abstract."""
    type_id = 0x04
class Type_04A( Type_04 ):
    discriminator = lambda x: x[23] == 'a' or x[35] == 42
class Type_04B( Type_04 ):
    discriminator = lambda x: True

Eso debería ser suficiente para mostrar que el "descubrimiento automático" está condenado desde el principio.

La solución correcta es tener una clase Factory que incorpore la jerarquía de subclase correcta, explotando todas las características basadas en un diseño manual.

class Factory( object ):
    def __init__( self, *subclass_list ):
        self.subclass = dict( (s.type_id,s) for s in subclass_list )
    def parse( self, packet ):
        if packet.type_id == 04:
            # special subclass logic
        return self.subclass[packet.type_id]( packet )

No parece una carga demasiado onerosa incluir lo siguiente:

factory= Factory( Subclass1, Subclass2, ... SubclassN )

Y cuando agregue subclases, agréguelo a la lista de subclases que se están utilizando en realidad .

3
S.Lott 20 oct. 2009 a las 10:17