2012-02-10 15 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 

Nell'esempio sopra issubclass(TypeType*, ITestType) restituirà vero per 2 e falso per 1 e 3.Come si verificano le interfacce digitate con anatra in python?

C'è un modo alternativo di utilizzare issubclass, oppure un metodo alternativo per la prova interfaccia che permetterà 1 e 2 passare, ma rifiutare 3?

Sarebbe estremamente utile per me essere in grado di utilizzare la digitazione anatra piuttosto che associare esplicitamente le classi ai tipi astratti, ma anche consentire il controllo degli oggetti quando gli oggetti antropizzati passano attraverso particolari interfacce.

Sì, sono a conoscenza del fatto che alle persone di Python non piacciono le interfacce e la metodologia standard è "trova quando fallisce e avvolge tutto in eccezioni" ma anche completamente irrilevante alla mia domanda. No, non posso semplicemente non usare le interfacce in questo progetto.

modifica:

Perfetto! Per chiunque altro che trova questa domanda, ecco l'esempio di come utilizzare 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, non posso semplicemente non utilizzare le interfacce in questo progetto. Perché? (Non sono sarcastico, sinceramente curioso di sapere quale potrebbe essere il caso d'uso) –

risposta

9

Scopri i ABC module. Puoi definire una classe base astratta che fornisce un metodo __subclasshook__ che definisce se una particolare classe "è una sottoclasse" della classe base astratta basata su qualsiasi criterio tu voglia - come "ha metodi X, Y e Z" o qualsiasi altra cosa . Quindi è possibile utilizzare issubclass() o isinstance() per rilevare le interfacce su classi e istanze.

1

Ecco un'alternativa che funziona molto bene anche nella pratica, senza l'ingombrante controllo dell'intero dizionario su ogni creazione di istanza di classe.

(PY2 e PY3 compatibile)

Usage:

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') 
    ... 

si può anche fare cose ovvie come @implements (Stream, Folder, Bar), quando tutti richiedono alcuni degli stessi metodi, il che rende ciò più utile praticamente dell'ereditarietà.

Codice:

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

questo è un paio di anni di ritardo, ma qui è il modo in cui l'ho fatto:

import abc 

class MetaClass(object): 
    __metaclass__ = abc.ABCMeta 

    [...] 

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

Se C è un tentativo di classe di MetaClass, allora C.__abstractmethods__ sarà solo vuoto se C implementa tutti i metodi astratti.

Vedi qui per i dettagli: https://www.python.org/dev/peps/pep-3119/#the-abc-module-an-abc-support-framework (è sotto "Implementazione", ma una ricerca di __abstractmethods__ dovrebbe arrivare al punto giusto)

Qualora sia presente funzionato per me:

posso creare MetaClass. Posso quindi creare sottoclasse BaseClass e MetaClass per creare SubClass che richiede alcune funzionalità aggiuntive.Ma ho bisogno di trasmettere un'istanza di a SubClass modificando l'attributo __cls__ poiché non possiedo BaseClass ma ottengo istanze di esso che voglio eseguire il cast down.

Tuttavia, se impropriamente implementare SubClass, posso ancora gettato giù a meno che io uso il sopra __subclasshook__ e basta aggiungere un controllo sottoclasse quando faccio il processo gettato giù (che devo fare comunque perché voglio solo cercare di lanciare una classe genitore verso il basso). Se qualcuno richiede, posso fornire un MWE per questo.

ETA: ecco un MWE. Penso che ciò che stavo suggerendo prima fosse scorretto, quindi sembra che ciò che segue abbia fatto ciò che intendevo.

L'obiettivo è essere in grado di convertire un oggetto in SubClass e viceversa. Da SubClass a BaseClass è facile. Ma da BaseClass a SubClass non così tanto. La cosa standard da fare è aggiornare l'attributo __class__ ma lascia un'apertura quando SubClass non è in realtà una sottoclasse o deriva da una meta class astratta ma non è implementata correttamente.

Sotto, la conversione viene eseguita nel metodo convert che BaseMetaClass implementa. Tuttavia, in questa logica, controllo due cose. Uno, per convertirlo in sottoclasse, controllo se è effettivamente una sottoclasse. Secondo, controllo l'attributo __abstractmethods__ per vedere se è vuoto. Se lo è, allora è anche un metaclasse correttamente implementato. L'errore porta a un errore TypeError. Altrimenti l'oggetto viene convertito.

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

per favore, fornire un esempio – Konstantin

Problemi correlati