Pregunta

Por favor, considere el siguiente código implementar un MixIn simple:

class Story(object):
    def __init__(self, name, content):  
     self.name = name
     self.content = content    

class StoryHTMLMixin(object):
    def render(self):
     return ("<html><title>%s</title>"
         "<body>%s</body></html>"
         % (self.name, self.content))

def MixIn(TargetClass, MixInClass):
    if MixInClass not in TargetClass.__bases__:
     TargetClass.__bases__ += (MixInClass,)

if __name__ == "__main__":
    my_story = Story("My Life", "<p>Is good.</p>")
    # plug-in the MixIn here
    MixIn(Story, StoryHTMLMixin)
    # now I can render the story as HTML
    print my_story.render()

Ejecución de cables main al error siguiente:

TypeError: Cannot create a consistent method resolution
order (MRO) for bases object, StoryHTMLMixin

El problema es que tanto Story y StoryHTMLMixin se derivan de object, y surge el problema diamante .

La solución es simplemente hacer StoryHTMLMixin un clase de estilo antiguo , es decir, eliminar la herencia de object, por lo tanto, el cambio de la definición de la clase StoryHTMLMixin a:

class StoryHTMLMixin:
    def render(self):
     return ("<html><title>%s</title>"
         "<body>%s</body></html>"
         % (self.name, self.content))

conduce al siguiente resultado cuando se ejecuta main:

<html><title>My Life</title><body><p>Is good.</p></body></html>

No me gusta tener que utilizar clases de estilo antiguo , por lo que mi pregunta es:

¿Es esta la forma correcta de manejar este problema en Python, o hay una mejor manera?

Editar:

veo que el UserDict clase en el última fuente de Python define un MixIn recurrir a las clases de estilo antiguo (tal como se presenta en mi ejemplo).

Según lo recomendado por todos, que puede recurrir a la redefinición de la funcionalidad que quiero alcanzar (es decir, la unión de los métodos en tiempo de ejecución) sin necesidad de utilizar mixins. Sin embargo, el punto sigue siendo - este es el único caso donde el uso de jugar con el MRO no tiene solución sin recurrir a la reimplementación o caer de nuevo a las clases de estilo antiguo

¿Fue útil?

Solución

Es más fácil ver lo que está sucediendo si eliminamos la magia __bases__ y escribimos nuestras las clases va a crear de forma explícita:

class StoryHTMLMixin(object):
    def render(self):
        return ("<html><title>%s</title>"
            "<body>%s</body></html>"
            % (self.name, self.content))

class Story(object, StoryHTMLMixin):
    def __init__(self, name, content):
        self.name = name
        self.content = content

Ese es el resultado final de lo que está haciendo - o lo que sería si tenía éxito. El resultado es el mismo error.

Tenga en cuenta que esto no es en realidad la herencia de diamante. Esto implica cuatro clases, en las dos clases de base de cada uno hereda una cuarta clase común; múltiples ofertas de herencia de Python con eso.

Aquí tiene sólo tres clases, lo que lleva a la herencia que tiene este aspecto:

StoryHTMLMixin <--- Story
            |   _____/
            |  |
            v  v
           object

Python no sabe cómo resolver esto.

No sé acerca de una solución para este. En principio, la solución sería eliminar object desde las bases de Story al mismo tiempo se agrega StoryHTMLMixin a ella, pero que no está permitido por razones internas, un tanto opacos (TypeError: __bases__ assignment: 'StoryHTMLMixin' deallocator differs from 'object').

Nunca he encontrado ninguna, el uso práctico del mundo real para modificar las clases como esto de todos modos. Sólo parece ofuscado y confuso -. Si desea una clase que deriva de esas dos clases, basta con crear una clase normalmente

Ed:

Este es un enfoque que hace algo similar a la suya, pero sin modificar las clases en el lugar. Tenga en cuenta la forma en que devuelve una nueva clase, que se deriva de forma dinámica a partir de los argumentos de la función. Esto es mucho más claro -. No puede modificar inadvertidamente objetos que ya se crean instancias, por ejemplo

class Story(object):
    def __init__(self, name, content):
        self.name = name
        self.content = content

class StoryHTMLMixin(object):
    def render(self):
        return ("<html><title>%s</title>"
            "<body>%s</body></html>"
            % (self.name, self.content))

def MixIn(TargetClass, MixInClass, name=None):
    if name is None:
        name = "mixed_%s_with_%s" % (TargetClass.__name__, MixInClass.__name__)

    class CombinedClass(TargetClass, MixInClass):
        pass

    CombinedClass.__name__ = name
    return CombinedClass

if __name__ == "__main__":
    MixedStory = MixIn(Story, StoryHTMLMixin, "MixedStory")
    my_story = MixedStory("My Life", "<p>Is good.</p>")
    print my_story.render()

Otros consejos

¿Por qué no utilizar mixins directamente en lugar de cortar alrededor de la MRO?

class Story(object):
    def __init__(self, name, content):  
     self.name = name
     self.content = content    

class StoryHTMLMixin(object):
    def render(self):
     return ("<html><title>%s</title>"
         "<body>%s</body></html>"
         % (self.name, self.content))

class StoryHTML(Story, StoryHTMLMixin):
    pass


print StoryHTML('asd','asdf').render() # works just fine

Si realmente, realmente, realmente quiere pegar métodos adicionales en una clase, que no es un gran problema. Bueno, excepto que es el mal y malas prácticas. De todas formas, puede cambiar una clase en cualquier momento:

# first, the story
x = Story('asd','asdf')

# changes a class object
def stick_methods_to_class( cls, *methods):
    for m in methods:
        setattr(cls, m.__name__, m)

# a bare method to glue on
def render(self):
 return ("<html><title>%s</title>"
     "<body>%s</body></html>"
     % (self.name, self.content))

# change the class object
stick_methods_to_class(Story, render)

print x.render()

Pero al final, la pregunta sigue siendo: ¿Por qué las clases crecer repentinamente métodos adicionales? Ese es el horror cosas películas están hechas de; -)

Edit: Mi mal, el error es un tema aparte (gracias @Glenn Maynard). Este truco todavía funciona, siempre y cuando sus hereda directamente de mixin object.

class Text(object): pass
class Story(Text):
    ....

Sin embargo, creo que mixins no son la solución más elegante a su problema. Ambos otras soluciones aportadas (un decorador de clase y subclase ordinaria) marcan claramente la clase Story como renderable, mientras que su solución oculta este hecho. Es decir, la existencia del método render en las otras soluciones es explícita , mientras que en su solución se oculta. Creo que esto causará confusión más tarde, sobre todo si se llega a depender más de la metodología mixin.

En lo personal me gusta el decorador de clase.

Sin embargo, el punto sigue siendo - se este caso el único uso donde jugar con el MRO es insoluble sin recurrir a la reimplementación o cayendo de nuevo a las clases de estilo antiguo?

mejores soluciones

Otros han sugerido - como la construcción explícita de la clase deseada - sino para responder a su pregunta editada, es posible para definir el mixin sin recurrir a las clases de estilo antiguo:

class Base(object): pass

class Story(Base):
    def __init__(self, name, content):  
     self.name = name
     self.content = content    

class StoryHTMLMixin(Base):
    def render(self):
     return ("<html><title>%s</title>"
         "<body>%s</body></html>"
         % (self.name, self.content))

def MixIn(TargetClass, MixInClass):
    if MixInClass not in TargetClass.__bases__:
        TargetClass.__bases__ = (MixInClass,)+TargetClass.__bases__

if __name__ == "__main__":
    my_story = Story("My Life", "<p>Is good.</p>")
    # plug-in the MixIn here
    MixIn(Story, StoryHTMLMixin)
    # now I can render the story as HTML
    print my_story.render()

rendimientos

<html><title>My Life</title><body><p>Is good.</p></body></html>

Se podría tratar de usar un decorador para agregar la funcionalidad en su lugar:

def render(obj):
  return ("<html><title>%s</title>"
    "<body>%s</body></html>"
    % (obj.name, obj.content))

def renderable(cls):
  cls.render = render
  return cls

@renderable
class Story(object):
  ...

Además de todo lo dicho por las respuestas anteriores (y siendo malos y una mala idea), el problema es:

  1. Las bases deben ser ordenados a la inversa

    TargetClass.__bases__ = (MixInClass,) + TargetClass.__bases__

  2. http://bugs.python.org/issue672115 - La solución más fácil es hereda todo de una clase definida por el usuario y no objeto, como

    class MyBase(object): pass
    
    
    class Story(MyBase):
         ...
    
    
    class StoryHTMLMixin(MyBase):
         ...
    
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top