2012-02-10 12 views
8
class ITestType(object): 
    """ Sample interface type """ 

    __metaclass__ = ABCMeta 

    @abstractmethod 
    def requiredCall(self): 
    return 

class TestType1(object): 
    """ Valid type? """ 
    def requiredCall(self): 
    pass 

class TestType2(ITestType): 
    """ Valid type """ 
    def requiredCall(self): 
    pass 

class TestType3(ITestType): 
    """ Invalid type """ 
    pass 

En el ejemplo anterior issubclass(TypeType*, ITestType) devolverá cierto para 2 y falso para 1 y 3.¿Cómo se verifican las interfaces de pato en python?

¿Existe una forma alternativa de utilizar issubclass, o un método alternativo para pruebas de interfaz que permitirá 1 y 2 pasar, pero rechazar 3?

Sería extremadamente útil para mí poder usar el tipo de pato en lugar de vincular explícitamente clases a tipos abstractos, pero también permitir la comprobación de objetos cuando los objetos de tipo pato pasan a través de interfaces particulares.

Sí, soy consciente de que a las personas de Python no les gustan las interfaces, y la metodología estándar es "encuéntrala cuando falla y envuelve todo en excepciones", pero también completamente irrelevante para mi pregunta. No, no puedo simplemente no usar interfaces en este proyecto.

editar:

Perfecto! Para cualquier otra persona que se encuentra esta pregunta, aquí está el ejemplo de cómo utilizar subclasshook:

class ITestType(object): 
    """ Sample interface type """ 

    __metaclass__ = ABCMeta 

    @abstractmethod 
    def requiredCall(self): 
    return 

    @classmethod 
    def __subclasshook__(cls, C): 
    required = ["requiredCall"] 
    rtn = True 
    for r in required: 
     if not any(r in B.__dict__ for B in C.__mro__): 
     rtn = NotImplemented 
    return rtn 
+1

'No, no puedo simplemente no utilizar interfaces en este proyecto. ¿Por qué? (No estoy siendo sarcástico, genuinamente curioso cuál podría ser el caso de uso) –

Respuesta

9

Mira la ABC module. Puede definir una clase base abstracta que proporcione un método __subclasshook__ que defina si una clase en particular "es una subclase" de la clase base abstracta en función de los criterios que desee, como "tiene los métodos X, Y y Z" o lo que sea . Luego puede usar issubclass() o isinstance() para detectar interfaces en clases e instancias.

1

Aquí hay una alternativa que también funciona muy bien en la práctica, sin la complicada comprobación del diccionario completo en cada creación de instancia de clase.

(AP2 y AP3 compatibles)

Uso:

class Bar(): 
    required_property_1 = '' 

    def required_method(self): 
    pass 

# Module compile time check that Foo implements Bar 
@implements(Bar) 
class Foo(UnknownBaseClassUnrelatedToBar): 
    required_property_1 

    def required_method(self): 
    pass 

# Run time check that Foo uses @implements or defines its own __implements() member 
def accepts_bar(self, anything): 
    if not has_api(anything, Bar): 
    raise Exception('Target does not implement Bar') 
    ... 

También puede hacer cosas obvias como @implements (Stream, carpetas, Bar), cuando todos ellos requieren algunos de los mismos métodos, que lo hace más útil prácticamente que la herencia.

Código:

import inspect 


def implements(*T): 
    def inner(cls): 
    cls.__implements = [] 
    for t in T: 

     # Look for required methods 
     t_methods = inspect.getmembers(t, predicate=lambda x: inspect.isfunction(x) or inspect.ismethod(x)) 
     c_methods = inspect.getmembers(cls, predicate=lambda x: inspect.isfunction(x) or inspect.ismethod(x)) 
     sig = {} 
     for i in t_methods: 
     name = 'method:%s' % i[0] 
     if not name.startswith("method:__"): 
      sig[name] = False 
     for i in c_methods: 
     name = 'method:%s' % i[0] 
     if name in sig.keys(): 
      sig[name] = True 

     # Look for required properties 
     t_props = [i for i in inspect.getmembers(t) if i not in t_methods] 
     c_props = [i for i in inspect.getmembers(cls) if i not in c_methods] 
     for i in t_props: 
     name = 'property:%s' % i[0] 
     if not name.startswith("property:__"): 
      sig[name] = False 
     for i in c_props: 
     name = 'property:%s' % i[0] 
     if name in sig.keys(): 
      sig[name] = True 

     missing = False 
     for i in sig.keys(): 
     if not sig[i]: 
      missing = True 
     if missing: 
     raise ImplementsException(cls, t, sig) 
     cls.__implements.append(t) 
    return cls 
    return inner 


def has_api(instance, T): 
    """ Runtime check for T in type identity """ 
    rtn = False 
    if instance is not None and T is not None: 
    if inspect.isclass(instance): 
     if hasattr(instance, "__implements"): 
     if T in instance.__implements: 
      rtn = True 
    else: 
     if hasattr(instance.__class__, "__implements"): 
     if T in instance.__class__.__implements: 
      rtn = True 
    return rtn 


class ImplementsException(Exception): 
    def __init__(self, cls, T, signature): 
    msg = "Invalid @implements decorator on '%s' for interface '%s': %r" % (cls.__name__, T.__name__, signature) 
    super(ImplementsException, self).__init__(msg) 
    self.signature = signature 
2

este es un par de años de retraso, pero aquí está la forma en que lo hice:

import abc 

class MetaClass(object): 
    __metaclass__ = abc.ABCMeta 

    [...] 

    @classmethod 
    def __subclasshook__(cls, C): 
     if C.__abstractmethods__: 
      print C.__abstractmethods__ 
      return False 
     else: 
      return True 

Si C es un intento de la clase de MetaClass, entonces C.__abstractmethods__ estará vacía solamente si C implementa todos los métodos abstractos.

Vea aquí los detalles: https://www.python.org/dev/peps/pep-3119/#the-abc-module-an-abc-support-framework (que está bajo "Aplicación" sino una búsqueda de __abstractmethods__ que debe llegar al punto derecha)

Cuando esto ha funcionado para mí:

puedo crear MetaClass. Luego puedo crear la subclase BaseClass y MetaClass para crear SubClass, que necesita alguna funcionalidad adicional.Pero tengo una necesidad de convertir una instancia de BaseClass en SubClass cambiando el atributo __cls__ ya que no soy el propietario de BaseClass, pero tengo instancias de ello que quiero eliminar.

Sin embargo, si inadecuadamente implemento SubClass, todavía puedo derribar a menos que utilice la anterior __subclasshook__ y simplemente añadir un cheque subclase cuando lo haga el proceso de derribar (que debería hacer de todos modos ya que quiero sólo para tratar de fundido una clase para padres abajo). Si alguien lo solicita, puedo proporcionarle un MWE para esto.

ETA: Aquí hay un MWE. Creo que lo que estaba sugiriendo antes era incorrecto, por lo que lo siguiente parece hacer lo que pretendo.

El objetivo es poder convertir un objeto BaseClass en SubClass y volver. Desde SubClass hasta BaseClass es fácil. Pero desde el BaseClass hasta el SubClass no tanto. Lo estándar para hacer es actualizar el atributo __class__ pero deja una apertura cuando SubClass no es realmente una subclase o deriva de una metaclase abstracta, pero no se implementa correctamente.

A continuación, la conversión se realiza en el método convert que BaseMetaClass implementa. Sin embargo, en esa lógica, verifico dos cosas. Uno, para convertir a la subclase, compruebo si se trata de una subclase. En segundo lugar, verifico el atributo __abstractmethods__ para ver si está vacío. Si es así, también es una metaclase implementada correctamente. El error conduce a un TypeError. De lo contrario, el objeto se convierte.

import abc 


class BaseMetaClass(object): 
    __metaclass__ = abc.ABCMeta 

    @classmethod 
    @abc.abstractmethod 
    def convert(cls, obj): 
     if issubclass(cls, type(obj)): 
      if cls.__abstractmethods__: 
       msg = (
        '{0} not a proper subclass of BaseMetaClass: ' 
        'missing method(s)\n\t' 
       ).format(
        cls.__name__ 
       ) 
       mthd_list = ',\n\t'.join(
        map(
         lambda s: cls.__name__ + '.' + s, 
         sorted(cls.__abstractmethods__) 
        ) 
       ) 
       raise TypeError(msg + mthd_list) 

      else: 
       obj.__class__ = cls 
       return obj 
     else: 
      msg = '{0} not subclass of {1}'.format(
       cls.__name__, 
       type(obj).__name__ 
      ) 
      raise TypeError(msg) 

    @abc.abstractmethod 
    def abstractmethod(self): 
     return 


class BaseClass(object): 

    def __init__(self, x): 
     self.x = x 

    def __str__(self): 
     s0 = "BaseClass:\n" 
     s1 = "x: {0}".format(self.x) 
     return s0 + s1 


class AnotherBaseClass(object): 

    def __init__(self, z): 
     self.z = z 

    def __str__(self): 
     s0 = "AnotherBaseClass:\n" 
     s1 = "z: {0}".format(self.z) 
     return s0 + s1 


class GoodSubClass(BaseMetaClass, BaseClass): 

    def __init__(self, x, y): 
     super(GoodSubClass, self).__init__(x) 
     self.y = y 

    @classmethod 
    def convert(cls, obj, y): 
     super(GoodSubClass, cls).convert(obj) 
     obj.y = y 

    def to_base(self): 
     return BaseClass(self.x) 

    def abstractmethod(self): 
     print "This is the abstract method" 

    def __str__(self): 
     s0 = "SubClass:\n" 
     s1 = "x: {0}\n".format(self.x) 
     s2 = "y: {0}".format(self.y) 
     return s0 + s1 + s2 


class BadSubClass(BaseMetaClass, BaseClass): 

    def __init__(self, x, y): 
     super(BadSubClass, self).__init__(x) 
     self.y = y 

    @classmethod 
    def convert(cls, obj, y): 
     super(BadSubClass, cls).convert(obj) 
     obj.y = y 

    def __str__(self): 
     s0 = "SubClass:\n" 
     s1 = "x: {0}\n".format(self.x) 
     s2 = "y: {0}".format(self.y) 
     return s0 + s1 + s2 


base1 = BaseClass(1) 
print "BaseClass instance" 
print base1 
print 


GoodSubClass.convert(base1, 2) 
print "Successfully casting BaseClass to GoodSubClass" 
print base1 
print 

print "Cannot cast BaseClass to BadSubClass" 
base1 = BaseClass(1) 
try: 
    BadSubClass.convert(base1, 2) 
except TypeError as e: 
    print "TypeError: {0}".format(e.message) 
    print 


print "Cannot cast AnotherBaseCelass to GoodSubClass" 
anotherbase = AnotherBaseClass(5) 
try: 
    GoodSubClass.convert(anotherbase, 2) 
except TypeError as e: 
    print "TypeError: {0}".format(e.message) 
    print 

print "Cannot cast AnotherBaseCelass to BadSubClass" 
anotherbase = AnotherBaseClass(5) 
try: 
    BadSubClass.convert(anotherbase, 2) 
except TypeError as e: 
    print "TypeError: {0}".format(e.message) 
    print 


# BaseClass instance 
# BaseClass: 
# x: 1 

# Successfully casting BaseClass to GoodSubClass 
# SubClass: 
# x: 1 
# y: 2 

# Cannot cast BaseClass to BadSubClass 
# TypeError: BadSubClass not a proper subclass of BaseMetaClass: missing method(s) 
#  BadSubClass.abstractmethod 

# Cannot cast AnotherBaseCelass to GoodSubClass 
# TypeError: GoodSubClass not subclass of AnotherBaseClass 

# Cannot cast AnotherBaseCelass to BadSubClass 
# TypeError: BadSubClass not subclass of AnotherBaseClass 
+0

por favor, proporcione un ejemplo – Konstantin

Cuestiones relacionadas