抽象属性(不是属性)?

时间:2014-05-23 14:12:21

标签: python abstract

定义抽象实例属性的最佳做法是什么,而不是属性?

我想写一些像:

class AbstractFoo(metaclass=ABCMeta):

    @property
    @abstractmethod
    def bar(self):
        pass

class Foo(AbstractFoo):

    def __init__(self):
        self.bar = 3

而不是:

class Foo(AbstractFoo):

    def __init__(self):
        self._bar = 3

    @property
    def bar(self):
        return self._bar

    @bar.setter
    def setbar(self, bar):
        self._bar = bar

    @bar.deleter
    def delbar(self):
        del self._bar

属性很方便,但对于不需要计算的简单属性,它们是一种过度杀伤力。这对于将由用户进行子类化和实现的抽象类尤其重要(我不想强迫某人在@property中写self.foo = foo时使用__init__ )。

Abstract attributes in Python问题建议仅作为使用@property@abstractmethod的答案:它不回答我的问题。

通过AbstractAttribute抽象类属性的ActiveState配方可能是正确的方法,但我不确定。它也只适用于类属性,而不适用于实例属性。

7 个答案:

答案 0 :(得分:20)

如果您确实希望强制子类定义给定属性,则可以使用metaclass。就个人而言,我认为这可能是矫枉过正而且不是非常pythonic,但你可以做这样的事情:

 class AbstractFooMeta(type):

     def __call__(cls, *args, **kwargs):
         """Called when you call Foo(*args, **kwargs) """
         obj = type.__call__(cls, *args, **kwargs)
         obj.check_bar()
         return obj


 class AbstractFoo(object):
     __metaclass__ = AbstractFooMeta
     bar = None

     def check_bar(self):
         if self.bar is None:
             raise NotImplementedError('Subclasses must define bar')


 class GoodFoo(AbstractFoo):
     def __init__(self):
         self.bar = 3


 class BadFoo(AbstractFoo):
     def __init__(self):
         pass

基本上,元类重新定义__call__以确保在实例上的init之后调用check_bar

GoodFoo()  # ok
BadFoo ()  # yield NotImplementedError

答案 1 :(得分:13)

仅仅因为你在抽象基类上将它定义为abstractproperty并不意味着你必须在子类上创建一个属性。

e.g。你可以:

In [1]: from abc import ABCMeta, abstractproperty

In [2]: class X(metaclass=ABCMeta):
   ...:     @abstractproperty
   ...:     def required(self):
   ...:         raise NotImplementedError
   ...:

In [3]: class Y(X):
   ...:     required = True
   ...:

In [4]: Y()
Out[4]: <__main__.Y at 0x10ae0d390>

如果要初始化__init__中的值,可以执行以下操作:

In [5]: class Z(X):
   ...:     required = None
   ...:     def __init__(self, value):
   ...:         self.required = value
   ...:

In [6]: Z(value=3)
Out[6]: <__main__.Z at 0x10ae15a20>

答案 2 :(得分:9)

2018年,我们应该得到更好的解决方案:

from better_abc import ABCMeta, abstract_attribute    # see below

class AbstractFoo(metaclass=ABCMeta):

    @abstract_attribute
    def bar(self):
        pass

class Foo(AbstractFoo):
    def __init__(self):
        self.bar = 3

class BadFoo(AbstractFoo):
    def __init__(self):
        pass

它的行为如下:

Foo()     # ok
BadFoo()  # will raise: NotImplementedError: Can't instantiate abstract class BadFoo
# with abstract attributes: bar

这个答案使用与接受答案相同的方法,但与内置ABC完美整合,不需要check_bar()助手的样板。

以下是better_abc.py内容:

from abc import ABCMeta as NativeABCMeta

class DummyAttribute:
    pass

def abstract_attribute(obj=None):
    if obj is None:
        obj = DummyAttribute()
    obj.__is_abstract_attribute__ = True
    return obj


class ABCMeta(NativeABCMeta):

    def __call__(cls, *args, **kwargs):
        instance = NativeABCMeta.__call__(cls, *args, **kwargs)
        abstract_attributes = {
            name
            for name in dir(instance)
            if getattr(getattr(instance, name), '__is_abstract_attribute__', False)
        }
        if abstract_attributes:
            raise NotImplementedError(
                "Can't instantiate abstract class {} with"
                " abstract attributes: {}".format(
                    cls.__name__,
                    ', '.join(abstract_attributes)
                )
            )
        return instance

好消息是你能做到:

class AbstractFoo(metaclass=ABCMeta):
    bar = abstract_attribute()

它将与上述相同。

也可以使用:

class ABC(ABCMeta):
    pass

定义自定义ABC帮助器。 PS。我认为此代码为CC0。

这可以通过使用AST解析器通过扫描__init__代码来提升(在类声明上)来改进,但现在似乎是一种矫枉过正(除非有人愿意实现)。

答案 3 :(得分:7)

问题不是什么,而

from abc import ABCMeta, abstractmethod

class AbstractFoo(metaclass=ABCMeta):
    @abstractmethod
    def bar():
        pass

class Foo(AbstractFoo):
    bar = object()

isinstance(Foo(), AbstractFoo)
#>>> True

bar不是一种方法并不重要!问题是__subclasshook__,即检查的方法是 classmethod ,所以只关心,而不是 instance ,具有属性。


我建议你不要强迫这个,因为这是一个难题。另一种方法是强制它们预定义属性,但这只会留下虚拟属性,只会使错误无效。

答案 4 :(得分:3)

正如Anentropic所说,您不必将abstractproperty用作另一个property

但是,所有答案似乎都被忽略的一件事是Python的成员槽(__slots__类属性)。如果只需要数据属性,则需要实现抽象属性的ABC用户只需在__slots__中定义它们即可。

所以类似的东西,

class AbstractFoo(abc.ABC):
    __slots__ = ()

    bar = abc.abstractproperty()

用户可以像定义子类一样简单

class Foo(AbstractFoo):
    __slots__ = 'bar',  # the only requirement

    # define Foo as desired

    def __init__(self):
        self.bar = ...

在这里,Foo.bar的行为就像常规实例属性一样,只是以不同的方式实现。这是简单,高效的方法,并且避免了您所描述的@property样板。

无论ABC是否在其班级正文中定义__slots__,此方法都有效。但是,完全使用__slots__不仅可以节省内存并提供更快的属性访问,而且还可以提供有意义的描述符,而不是在子类中使用中间物(例如bar = None或类似物)。 1

一些答案​​建议在实例化后 (即在元类__call__()方法下)进行“抽象”属性检查,但我发现这样做不仅浪费而且还可能效率低下初始化步骤可能很耗时。

简而言之,ABC的子类所需要的是覆盖相关的描述符(无论是属性还是方法),这无关紧要,并向用户证明可以使用{{1 }}作为抽象属性的实现在我看来是一种更适当的方法。


1 在任何情况下,ABC至少应始终定义一个空的__slots__类属性,因为否则子类会被强制在实例化时具有__slots__(动态属性访问)和__dict__(弱引用支持)。有关标准库中这种情况的示例,请参见abccollections.abc模块。

答案 5 :(得分:0)

我已经搜索了一段时间,但没有看到我喜欢的任何东西。你可能知道如果你这样做:

class AbstractFoo(object):
    @property
    def bar(self):
        raise NotImplementedError(
                "Subclasses of AbstractFoo must set an instance attribute "
                "self._bar in it's __init__ method")

class Foo(AbstractFoo):
    def __init__(self):
        self.bar = "bar"

f = Foo()

你得到一个很烦人的AttributeError: can't set attribute

要解决这个问题,你可以这样做:

class AbstractFoo(object):

    @property
    def bar(self):
        try:
            return self._bar
        except AttributeError:
            raise NotImplementedError(
                "Subclasses of AbstractFoo must set an instance attribute "
                "self._bar in it's __init__ method")

class OkFoo(AbstractFoo):
    def __init__(self):
        self._bar = 3

class BadFoo(AbstractFoo):
    pass

a = OkFoo()
b = BadFoo()
print a.bar
print b.bar  # raises a NotImplementedError

这可以避免AttributeError: can't set attribute,但如果您只是将抽象属性放在一起:

class AbstractFoo(object):
    pass

class Foo(AbstractFoo):
    pass

f = Foo()
f.bar

你得到的AttributeError: 'Foo' object has no attribute 'bar'几乎和NotImplementedError一样好。所以我的解决方案只是交换来自​​另一个的错误消息......你必须在 init 中使用self._bar而不是self.bar。

答案 6 :(得分:0)

关注https://docs.python.org/2/library/abc.html,您可以在Python 2.7中执行类似的操作:

from abc import ABCMeta, abstractproperty


class Test(object):
    __metaclass__ = ABCMeta

    @abstractproperty
    def test(self): yield None

    def get_test(self):
        return self.test


class TestChild(Test):

    test = None

    def __init__(self, var):
        self.test = var


a = TestChild('test')
print(a.get_test())