定义抽象实例属性的最佳做法是什么,而不是属性?
我想写一些像:
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配方可能是正确的方法,但我不确定。它也只适用于类属性,而不适用于实例属性。
答案 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__
(弱引用支持)。有关标准库中这种情况的示例,请参见abc
或collections.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())