2009-07-19 10 views
40

en Python 2.x cuando se quiere marcar un método tan abstracto, se puede definir así:Equivalente de NotImplementedError para los campos en Python

class Base: 
    def foo(self): 
     raise NotImplementedError("Subclasses should implement this!") 

Entonces, si usted se olvida de anularlo, se obtiene una buena excepción recordatorio. ¿Hay una manera equivalente de marcar un campo como abstracto? ¿O lo está diciendo en la clase docstring todo lo que puede hacer?

Al principio pensé que podía configurar el campo para NotImplemented, pero cuando busqué su significado (comparaciones enriquecidas), me pareció abusivo.

+0

Todavía funciona, incluso si su intención original era realizar comparaciones enriquecedoras. ¿Qué pasa con eso? –

+0

El primer problema es que puedes leer el campo desde el objeto (myvar = Base.field) y lo siguiente que sabes es que NotImplementeds está por todas partes hasta que otra parte intente usarlo y obtenga un AttributeError misterioso. – Kiv

+0

El segundo problema es que IMO dificulta la legibilidad ("¿Qué hace esa cosa de comparación rica haciendo allí? ¿Perdí algo?) La solución de Evan expresa exactamente lo que está sucediendo de una manera familiar. – Kiv

Respuesta

42

Sí, puedes. Use el decorador @property. Por ejemplo, si tiene un campo llamado "ejemplo", entonces no se puede hacer algo como esto:

class Base(object): 

    @property 
    def example(self): 
     raise NotImplementedError("Subclasses should implement this!") 

ejecutando los siguientes produce un NotImplementedError al igual que lo desee.

b = Base() 
print b.example 
+2

Esto es más simple, pero me gusta cómo se lanza mi versión inmediatamente y no solo si el atributo pasa a ser usado. –

+4

Pero Glenn, ¿qué ocurre si el "ejemplo" de la propiedad se configuró en algún otro punto? Si lo arroja inmediatamente, tal vez nunca tenga la posibilidad de ser configurado por otros medios. Recuerde que los campos y métodos se pueden establecer en una clase en cualquier momento y no solo cuando se define la clase. –

+0

Ah, esto es lo que estaba buscando. – Kiv

1
def require_abstract_fields(obj, cls): 
    abstract_fields = getattr(cls, "abstract_fields", None) 
    if abstract_fields is None: 
     return 

    for field in abstract_fields: 
     if not hasattr(obj, field): 
      raise RuntimeError, "object %s failed to define %s" % (obj, field) 

class a(object): 
    abstract_fields = ("x",) 
    def __init__(self): 
     require_abstract_fields(self, a) 

class b(a): 
    abstract_fields = ("y",) 
    x = 5 
    def __init__(self): 
     require_abstract_fields(self, b) 
     super(b, self).__init__() 

b() 
a() 

nota el paso del tipo de clase en require_abstract_fields, por lo que si varias clases heredadas utilizan esto, no todos validan los campos de la clase más derivada. Es posible que pueda automatizar esto con una metaclase, pero no profundicé en eso. Se acepta la definición de un campo como Ninguno.

28

respuesta alternativo:

@property 
def NotImplementedField(self): 
    raise NotImplementedError 

class a(object): 
    x = NotImplementedField 

class b(a): 
    # x = 5 
    pass 

b().x 
a().x 

Esto es como Evan, pero conciso y barato - usted sólo recibe una única instancia de NotImplementedField.

+3

Clever, Glenn. :) El único inconveniente que puedo ver es que no puede especificar que se muestren diferentes mensajes cuando se lanza NotImplementedError. –

+1

Puede definir NotImplementedField como una función que toma un mensaje para mostrar. Tendrías que hacerte un poco inteligente para mantenerlo usando una sola instancia de la función cuando no hay ningún mensaje adjunto (caché de un singleton para ningún mensaje), pero eso es todo. –

+1

Quizás menor, pero no soy tan fanático de este enfoque porque restringe su capacidad de establecer un mensaje para la excepción. Al usar @property puedes configurar el mensaje a lo que quieras. – umbrae

0

Y aquí está mi solución:

def not_implemented_method(func): 
    from functools import wraps 
    from inspect import getargspec, formatargspec 

    @wraps(func) 
    def wrapper(self, *args, **kwargs): 
     c = self.__class__.__name__ 
     m = func.__name__ 
     a = formatargspec(*getargspec(func)) 
     raise NotImplementedError('\'%s\' object does not implement the method \'%s%s\'' % (c, m, a)) 

    return wrapper 


def not_implemented_property(func): 
    from functools import wraps 
    from inspect import getargspec, formatargspec 

    @wraps(func) 
    def wrapper(self, *args, **kwargs): 
     c = self.__class__.__name__ 
     m = func.__name__ 
     raise NotImplementedError('\'%s\' object does not implement the property \'%s\'' % (c, m)) 

    return property(wrapper, wrapper, wrapper) 

Puede ser utilizado como

class AbstractBase(object): 
    @not_implemented_method 
    def test(self): 
     pass 

    @not_implemented_property 
    def value(self): 
     pass 

class Implementation(AbstractBase): 
    value = None 

    def __init__(self): 
     self.value = 42 

    def test(self): 
     return True 
0

Una mejor manera de hacer esto es utilizando Abstract Base Classes:

import abc 

class Foo(abc.ABC): 

    @property 
    @abc.abstractmethod 
    def demo_attribute(self): 
     raise NotImplementedError 

    @abc.abstractmethod 
    def demo_method(self): 
     raise NotImplementedError 

class BadBar(Foo): 
    pass 

class GoodBar(Foo): 

    demo_attribute = 'yes' 

    def demo_method(self): 
     return self.demo_attribute 

bad_bar = BadBar() 
# TypeError: Can't instantiate abstract class BadBar \ 
# with abstract methods demo_attribute, demo_method 

good_bar = GoodBar() 
# OK 

en cuenta que debe todavía tiene raise NotImplementedError en lugar de algo así como pass, porque no hay nada que impida que la clase heredera llame al super().demo_method(), y si el resumen demo_method es solo pass, esto fallará de manera silenciosa.

Cuestiones relacionadas