2012-02-24 15 views
16

Tengo una clase que es una superclase para muchas otras clases. Me gustaría saber (en el init() de mi super-clase si la subclase ha anulado un método específico¿Cómo detectar la sobrecarga de métodos en subclases en python?

Traté de lograr esto con un método de clase, pero los resultados equivocaste.

class Super: 
    def __init__(self): 
     if self.method == Super.method: 
     print 'same' 
     else: 
     print 'different' 

    @classmethod 
    def method(cls): 
     pass 

class Sub1(Super): 
    def method(self): 
     print 'hi' 

class Sub2(Super): 
    pass 

Super() # should be same 
Sub1() # should be different 
Sub2() # should be same 

>>> same 
>>> different 
>>> different 

¿hay alguna forma para una super-clase de saber si una sub-clase ha anulado un método?

+1

Podría dices algunas palabras sobre * por qué * querrías hacer eso? – NPE

+0

No se le ocurre nada inmediatamente, pero si incluye documentos para sus métodos, se sobrescribirán cuando se anule el método. Por lo tanto, debe poder seguirlo con 'MyClass.methodname .__ doc__'.Pero creo que esta solución es muy pirata, por lo que no la publico como una respuesta – inspectorG4dget

+0

Básicamente quiero que la superclase tenga estos métodos definidos como solo 'pasar' y tener un método separado (en realidad, __init__) llama a estas funciones Me gustaría que init ponga en las declaraciones impresas que dicen que estamos comenzando o terminando la función, pero si esa función sigue siendo nula, estas declaraciones se ven fuera de lugar, y me gustaría terminar con ellas. – Brian

Respuesta

7

Puede usar su propio decorador. Pero esto es un truco y solo funcionará en las clases donde controlas la implementación.

def override(method): 
    method.is_overridden = True 
    return method 

class Super: 
    def __init__(self): 
     if hasattr(self.method, 'is_overridden'): 
     print 'different' 
     else: 
     print 'same' 
    @classmethod 
    def method(cls): 
     pass 

class Sub1(Super): 
    @override 
    def method(self): 
     print 'hi' 

class Sub2(Super): 
    pass 

Super() # should be same 
Sub1() # should be different 
Sub2() # should be same 

>>> same 
>>> different 
>>> same 
+4

Fui en una dirección ligeramente diferente, decorando mi método con @native, y suponiendo que las subclases no lo harán. Creo que lo haré de esta manera ya que no tengo mucho control sobre las subclases. ¡Funciona bien, gracias por su ayuda! – Brian

+0

O incluso más simple poner 'Super.method._original = True' es el cuerpo de la clase' Super() ', y luego comprobar si' hasattr (self.method, '_original') '. –

3

es posible comparar lo que está en __dict__ de la clase con la función dentro del método se puede recuperar de la objeto - la función "detect_overriden" functionbel low hace eso - el truco es pasar la "clase padre" por su nombre, tal como se hace en una llamada a "super" - , de lo contrario no es fácil recuperar los atributos de la clase padre en lugar de los de la subclase:

# -*- coding: utf-8 -*- 
from types import FunctionType 

def detect_overriden(cls, obj): 
    res = [] 
    for key, value in cls.__dict__.items(): 
     if isinstance(value, classmethod): 
      value = getattr(cls, key).im_func 
     if isinstance(value, (FunctionType, classmethod)): 
      meth = getattr(obj, key) 
      if not meth.im_func is value: 
       res.append(key) 
    return res 


# Test and example 
class A(object): 
    def __init__(self): 
     print detect_overriden(A, self) 

    def a(self): pass 
    @classmethod 
    def b(self): pass 
    def c(self): pass 

class B(A): 
    def a(self): pass 
    #@classmethod 
    def b(self): pass 

edición código modificado para trabajar bien con classmethods así: si detecta un classmethod en la clase padre, extrae la función subyacente antes de continuar.

- Otra forma de hacer esto, sin tener que codificar el nombre de la clase, habría que seguir la clase de la instancia (self.__class__) para la resolución de métodos (dado por el atributo __mro__) y la búsqueda de duplicados de los métodos y atributos definidos en cada clase a lo largo de la cadena de herencia.

+1

En Python 3 es el atributo '__func__'. –

4

Parece más simple y suficiente para hacer esto comparando el subconjunto común de los diccionarios de una instancia y la propia clase base, por ejemplo:

def detect_overridden(cls, obj): 
    common = cls.__dict__.keys() & obj.__class__.__dict__.keys() 
    diff = [m for m in common if cls.__dict__[m] != obj.__class__.__dict__[m]] 
    print(diff) 

def f1(self): 
    pass 

class Foo: 
    def __init__(self): 
    detect_overridden(Foo, self) 
    def method1(self): 
    print("Hello foo") 
    method2=f1 

class Bar(Foo): 
    def method1(self): 
    print("Hello bar") 
    method2=f1 # This is pointless but not an override 
# def method2(self): 
# pass 

b=Bar() 
f=Foo() 

ejecuta y da:

['method1'] 
[] 
4

En respuesta a la respuesta https://stackoverflow.com/a/9437273/1258307, como todavía no tengo suficientes créditos para comentar, no funcionará en python 3 a menos que reemplace im_func con __func__ y tampoco funcionará en Python 3.4 (y muy probablemente en adelante) ya que las funciones ya no tienen el atributo __func__, solo métodos enlazados.

EDIT: Aquí está la solución a la pregunta original (que trabajó en 2.7 y 3.4, y asumo toda otra versión en el medio):

class Super: 
     def __init__(self): 
      if self.method.__code__ is Super.method.__code__: 
       print('same') 
      else: 
       print('different') 

     @classmethod 
     def method(cls): 
      pass 

    class Sub1(Super): 
     def method(self): 
      print('hi') 

    class Sub2(Super): 
     pass 

    Super() # should be same 
    Sub1() # should be different 
    Sub2() # should be same 

Y aquí está la salida:

same 
different 
same 
+0

Para tener en cuenta: en cython (al menos en python 3.4), 'self.method .__ code__' está ausente, pero funciona en Python limpio. Por alguna razón, '__code__' falta en' dir (self.method) ' – socketpair

Cuestiones relacionadas