Tengo curiosidad por saber si hay una manera en Python de forzar (desde la clase Parent) para que se llame a un método padre desde una clase child cuando se anula.

Ejemplo:

class Parent(object):

    def __init__(self):
        self.someValue=1

    def start(self):
        ''' Force method to be called'''
        self.someValue+=1

La implementación correcta de lo que quería que hiciera mi clase Child child sería:

class ChildCorrect(Parent):

    def start(self):
        Parent.start(self)
        self.someValue+=1

Sin embargo, ¿hay alguna manera de obligar a los desarrolladores que desarrollan clases Child a llamar específicamente (no solo a anular) el método 'start' definido en la clase Parent en caso de que olviden llamarlo:

class ChildIncorrect(Parent):

    def start(self):
        '''Parent method not called, but correctly overridden'''
        self.someValue+=1

Además, si esto no se considera la mejor práctica, ¿cuál sería una alternativa?

29
Spektor 11 dic. 2015 a las 16:40

6 respuestas

Muy poco en Python se trata de obligar a otros programadores a codificar de cierta manera. Incluso en la biblioteca estándar encontrará advertencias como esta:

Si la subclase anula al constructor, debe asegurarse de invocar el constructor de la clase base antes de hacer cualquier otra cosa al hilo.

Si bien sería posible usar una metaclase para modificar el comportamiento de las subclases, lo mejor que podría hacer de manera confiable en este caso sería reemplazar los nuevos métodos con otro método que se encargaría de llamar tanto al método reemplazado como al método original. Sin embargo, eso tendría problemas si el programador entonces llamaba al método padre en el nuevo código, además conduciría a malos hábitos de no llamar a los métodos padres cuando uno debería.

En otras palabras, Python no está construido de esa manera.

2
Ethan Furman 11 dic. 2015 a las 14:17

Esto no se relaciona directamente con Python, sería el caso para la mayoría de los lenguajes OO. Debe definir un método abstracto en su clase base, hacerlo no público (pero disponible para ser anulado por subclases) y también crear un método de plantilla pública en la clase principal, que llamará a este método abstracto (definido en su subclase). El código del cliente no podrá llamar al método en cuestión directamente, pero podrá llamar al método de plantilla que, a su vez, se asegurará de que el método en cuestión se llame en el orden / contexto correcto.

En otras palabras, su sistema no debe depender de las subclases que llaman a la implementación de los padres. Si eso es obligatorio, entonces debería revisar su implementación y descomponer el método en cuestión (por ejemplo, usar el patrón de método de plantilla).

Además, algunos lenguajes (como Java) llaman implícitamente al constructor del padre, si el constructor de la subclase no lo hace explícitamente (aunque no funciona para métodos simples).

En cualquier caso, nunca confíes en "hacks". esa no es la forma correcta de resolver problemas.

0
DennisP 25 sep. 2019 a las 18:04

Sin embargo, ¿hay alguna manera de obligar a los desarrolladores que desarrollan clases Child a llamar específicamente (no solo a anular) el método 'start' definido en la clase Parent en caso de que olviden llamarlo?

En Python no puedes controlar cómo otros anulan tus funciones.

1
Ali Nikneshan 11 dic. 2015 a las 13:57

Con algunos hacks de metaclases, puede detectar en tiempo de ejecución si se ha llamado o no al inicio principal. Sin embargo, ciertamente NO recomendaré usar este código en situaciones reales, probablemente sea defectuoso de muchas maneras y no es pitónico imponer tales restricciones.

class ParentMetaClass(type):

    parent_called = False

    def __new__(cls, name, bases, attrs):
        print cls, name, bases, attrs
        original_start = attrs.get('start')
        if original_start:
            if name == 'Parent':
                def patched_start(*args, **kwargs):
                    original_start(*args, **kwargs)
                    cls.parent_called = True

            else:
                def patched_start(*args, **kwargs):
                    original_start(*args, **kwargs)
                    if not cls.parent_called:
                        raise ValueError('Parent start not called')
                    cls.parent_called = False  

            attrs['start'] = patched_start

        return super(ParentMetaClass, cls).__new__(cls, name, bases, attrs)


class Parent(object):
    __metaclass__ = ParentMetaClass

    def start(self):
        print 'Parent start called.'


class GoodChild(Parent):
    def start(self):
        super(GoodChild, self).start()
        print 'I am a good child.'


class BadChild(Parent):
    def start(self):
        print 'I am a bad child, I will raise a ValueError.'
3
Sébastien Deprez 11 dic. 2015 a las 14:21

Si la jerarquía de clases está bajo su control, puede usar lo que la cuadrilla de cuatro (Gamma, et al) libro de Patrones de diseño llama el Patrón de método de plantilla:

class MyBase:
   def MyMethod(self):
      # place any code that must always be called here...
      print "Base class pre-code"

      # call an internal method that contains the subclass-specific code
      self._DoMyMethod()

      # ...and then any additional code that should always be performed
      # here.
      print "base class post-code!"

   def _DoMyMethod(self):
      print "BASE!"


class MyDerived(MyBase):
   def _DoMyMethod(self):
      print "DERIVED!"


b = MyBase()
d = MyDerived()

b.MyMethod()
d.MyMethod()

Salidas:

Base class pre-code
BASE!
base class post-code!
Base class pre-code
DERIVED!
base class post-code!
10
bgporter 11 dic. 2015 a las 14:34

Todo lo que necesita es super.

Con él, su código podría verse así:

class Parent(object):
    def __init__(self):
        self.someValue = 1

    def start(self):
        self.someValue += 1


class Child(Parent):
    def start(self):
        # put code here
        super().start()
        # or here
-1
andrzej3393 11 dic. 2015 a las 14:04
34224896