定义抽象实例属性的最佳实践是什么,而不是属性?
我想写一些类似的东西:
class AbstractFoo(metaclass=ABCMeta):
@property
@abstractmethod
def bar(self):
pass
class Foo(AbstractFoo):
def __init__(self):
self.bar = 3
Run Code Online (Sandbox Code Playgroud)
代替:
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
Run Code Online (Sandbox Code Playgroud)
属性很方便,但对于不需要计算的简单属性,它们是一种过度杀伤.这对于将由用户进行子类化和实现的抽象类尤其重要(我不想强迫某人@property在他刚写入self.foo = foo时使用__init__).
Python问题中的抽象属性建议仅作为使用的答案,@property并且@abstractmethod:它不回答我的问题.
抽象类属性的ActiveState配方AbstractAttribute可能是正确的方法,但我不确定.它也只适用于类属性而不适用于实例属性.
Séb*_*rez 25
如果您确实希望强制子类定义给定属性,则可以使用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
Run Code Online (Sandbox Code Playgroud)
基本上,元类重新定义__call__以确保 check_bar在实例上的init之后调用.
GoodFoo() # ok
BadFoo () # yield NotImplementedError
Run Code Online (Sandbox Code Playgroud)
kra*_*ski 24
它是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
Run Code Online (Sandbox Code Playgroud)
它会表现得像这样:
Foo() # ok
BadFoo() # will raise: NotImplementedError: Can't instantiate abstract class BadFoo
# with abstract attributes: bar
Run Code Online (Sandbox Code Playgroud)
这个答案使用与接受的答案相同的方法,但与内置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
Run Code Online (Sandbox Code Playgroud)
好的是你可以这样做:
class AbstractFoo(metaclass=ABCMeta):
bar = abstract_attribute()
Run Code Online (Sandbox Code Playgroud)
它将与上面相同.
也可以使用:
class ABC(ABCMeta):
pass
Run Code Online (Sandbox Code Playgroud)
定义自定义ABC帮助器.PS.我认为此代码为CC0.
这可以通过使用AST解析器通过扫描__init__代码来提前(在类声明上)来改进,但现在似乎是一种矫枉过正(除非有人愿意实现).
Ane*_*pic 14
仅仅因为你abstractproperty在抽象基类上定义它并不意味着你必须在子类上创建一个属性.
你可以:
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>
Run Code Online (Sandbox Code Playgroud)
如果要初始化值,__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>
Run Code Online (Sandbox Code Playgroud)
问题不是什么,而是在什么时候:
from abc import ABCMeta, abstractmethod
class AbstractFoo(metaclass=ABCMeta):
@abstractmethod
def bar():
pass
class Foo(AbstractFoo):
bar = object()
isinstance(Foo(), AbstractFoo)
#>>> True
Run Code Online (Sandbox Code Playgroud)
这bar不是一种方法并不重要!问题在于__subclasshook__,执行检查的方法是a classmethod,因此只关心类而不是实例是否具有该属性.
我建议你不要强迫这个,因为这是一个难题.另一种方法是强制它们预定义属性,但这只会留下虚拟属性,只会使错误无效.
小智 6
正如Anentropic所说,您不必将 an 实现abstractproperty为 another property。
然而,所有答案似乎都忽略的一件事是 Python 的成员槽(__slots__类属性)。__slots__如果需要的只是数据属性,那么需要实现抽象属性的 ABC 的用户可以简单地在其中定义它们。
所以用类似的东西,
class AbstractFoo(abc.ABC):
__slots__ = ()
bar = abc.abstractproperty()
Run Code Online (Sandbox Code Playgroud)
用户可以简单地定义子类,
class Foo(AbstractFoo):
__slots__ = 'bar', # the only requirement
# define Foo as desired
def __init__(self):
self.bar = ...
Run Code Online (Sandbox Code Playgroud)
在这里,它的Foo.bar行为就像一个普通的实例属性,只是实现方式不同。这很简单、高效,并且避免了@property您描述的样板文件。
无论 ABC 是否__slots__在其类的主体中定义,这都有效。然而,__slots__一路走下去不仅可以节省内存并提供更快的属性访问,而且还提供了一个有意义的描述符,而不是bar = None在子类中具有中间体(例如或类似的)。1
一些答案建议在实例化后(即在元类__call__()方法中)进行“抽象”属性检查,但我发现这不仅浪费而且可能效率低下,因为初始化步骤可能是一个耗时的步骤。
简而言之,ABC 的子类所需要的是覆盖相关的描述符(无论是属性还是方法),无论如何,并向您的用户记录可以__slots__用作抽象属性的实现似乎对我来说是更合适的方法。
1 在任何情况下,至少,ABC 应该始终定义一个空的__slots__类属性,否则子类在实例化时被迫具有__dict__(动态属性访问)和__weakref__(弱引用支持)。有关标准库中这种情况的示例,请参阅abc或collections.abc模块。