Python 抽象 class 应强制派生 classes 初始化 __init__ 中的变量
Python abstract class shall force derived classes to initialize variable in __init__
我想要一个抽象 class,它强制每个派生 class 在其 __init__
方法中设置某些属性。
我看了几个问题,但没有完全解决我的问题,特别是 or here. This 看起来很有希望,但我无法让它工作。
我假设我想要的结果可能如下所示伪代码:
from abc import ABCMeta, abstractmethod
class Quadrature(object, metaclass=ABCMeta):
@someMagicKeyword #<==== This is what I want, but can't get working
xyz
@someMagicKeyword #<==== This is what I want, but can't get working
weights
@abstractmethod
def __init__(self, order):
pass
def someStupidFunctionDefinedHere(self, n):
return self.xyz+self.weights+n
class QuadratureWhichWorks(Quadrature):
# This shall work because we initialize xyz and weights in __init__
def __init__(self,order):
self.xyz = 123
self.weights = 456
class QuadratureWhichShallNotWork(Quadrature):
# Does not initialize self.weights
def __init__(self,order):
self.xyz = 123
以下是我尝试过的一些方法:
from abc import ABCMeta, abstractmethod
class Quadrature(object, metaclass=ABCMeta):
@property
@abstractmethod
def xyz(self):
pass
@property
@abstractmethod
def weights(self):
pass
@abstractmethod
def __init__(self, order):
pass
def someStupidFunctionDefinedHere(self, n):
return self.xyz+self.weights+n
class QuadratureWhichWorks(Quadrature):
# This shall work because we initialize xyz and weights in __init__
def __init__(self,order):
self.xyz = 123
self.weights = 456
然后我尝试创建一个实例:
>>> from example1 import *
>>> Q = QuadratureWhichWorks(10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class QuadratureWhichWorks with abstract methods weights, xyz
>>>
这告诉我实现方法,但我以为我说这些是properties
?
我目前的解决方法有一个缺陷,即 __init__
方法可以在派生的 classes 中被覆盖,但现在这至少确保(对我而言)我总是知道请求的属性已设置:
from abc import ABCMeta, abstractmethod
class Quadrature(object, metaclass=ABCMeta):
@abstractmethod
def computexyz(self,order):
pass
@abstractmethod
def computeweights(self,order):
pass
def __init__(self, order):
self.xyz = self.computexyz(order)
self.weights = self.computeweights(order)
def someStupidFunctionDefinedHere(self, n):
return self.xyz+self.weights+n
class QuadratureWhichWorks(Quadrature):
def computexyz(self,order):
return order*123
def computeweights(self,order):
return order*456
class HereComesTheProblem(Quadrature):
def __init__(self,order):
self.xyz = 123
# but nothing is done with weights
def computexyz(self,order):
return order*123
def computeweights(self,order): # will not be used
return order*456
但问题是
>>> from example2 import *
>>> Q = HereComesTheProblem(10)
>>> Q.xyz
123
>>> Q.weights
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'HereComesTheProblem' object has no attribute 'weights'
这是如何正确实施的?
编辑: 使用自定义元的解决方案class。
值得注意的是,自定义元classes 通常不受欢迎,但您可以用一个来解决这个问题。
Here 是一篇讨论它们如何工作以及何时有用的好文章。这里的解决方案本质上是在调用 __init__
之后检查您想要的属性。
from abc import ABCMeta, abstractmethod
# our version of ABCMeta with required attributes
class MyMeta(ABCMeta):
required_attributes = []
def __call__(self, *args, **kwargs):
obj = super(MyMeta, self).__call__(*args, **kwargs)
for attr_name in obj.required_attributes:
if not getattr(obj, attr_name):
raise ValueError('required attribute (%s) not set' % attr_name)
return obj
# similar to the above example, but inheriting MyMeta now
class Quadrature(object, metaclass=MyMeta):
required_attributes = ['xyz', 'weights']
@abstractmethod
def __init__(self, order):
pass
class QuadratureWhichWorks(Quadrature):
# This shall work because we initialize xyz and weights in __init__
def __init__(self,order):
self.xyz = 123
self.weights = 456
q = QuadratureWhichWorks('foo')
class QuadratureWhichShallNotWork(Quadrature):
def __init__(self, order):
self.xyz = 123
q2 = QuadratureWhichShallNotWork('bar')
以下是我的原始回答,更笼统地探讨了该主题。
原答案
我认为部分原因是混淆了 实例属性 与 property
装饰器包装的对象。
- 实例属性是嵌套在实例命名空间中的纯数据块。同样,class 属性嵌套在 class 的命名空间中(并且由 class 的实例共享,除非它们覆盖它)。
- 属性 是一个具有语法快捷方式的函数,使它们可以像属性一样访问,但它们的功能性质允许它们是动态的。
一个没有引入抽象 classes 的小例子是
>>> class Joker(object):
>>> # a class attribute
>>> setup = 'Wenn ist das Nunstück git und Slotermeyer?'
>>>
>>> # a read-only property
>>> @property
>>> def warning(self):
>>> return 'Joke Warfare is explicitly banned bythe Geneva Conventions'
>>>
>>> def __init__(self):
>>> self.punchline = 'Ja! Beiherhund das Oder die Flipperwaldt gersput!'
>>> j = Joker()
>>> # we can access the class attribute via class or instance
>>> Joker.setup == j.setup
>>> # we can get the property but cannot set it
>>> j.warning
'Joke Warfare is explicitly banned bythe Geneva Conventions'
>>> j.warning = 'Totally safe joke...'
AttributeError: cant set attribute
>>> # instance attribute set in __init__ is only accessible to that instance
>>> j.punchline != Joker.punchline
AttributeError: type object 'Joker' has no attribute 'punchline'
根据 Python docs,自 3.3 以来,abstractproperty
是多余的,实际上反映了您尝试的解决方案。
该解决方案的问题是您的子 classes 没有实现具体的 属性,它们只是用实例属性覆盖它。
为了继续使用 abc
包,您可以通过实现这些属性来处理这个问题,即
>>> from abc import ABCMeta, abstractmethod
>>> class Quadrature(object, metaclass=ABCMeta):
>>>
>>> @property
>>> @abstractmethod
>>> def xyz(self):
>>> pass
>>>
>>> @property
>>> @abstractmethod
>>> def weights(self):
>>> pass
>>>
>>> @abstractmethod
>>> def __init__(self, order):
>>> pass
>>>
>>> def someStupidFunctionDefinedHere(self, n):
>>> return self.xyz+self.weights+n
>>>
>>>
>>> class QuadratureWhichWorks(Quadrature):
>>> # This shall work because we initialize xyz and weights in __init__
>>> def __init__(self,order):
>>> self._xyz = 123
>>> self._weights = 456
>>>
>>> @property
>>> def xyz(self):
>>> return self._xyz
>>>
>>> @property
>>> def weights(self):
>>> return self._weights
>>>
>>> q = QuadratureWhichWorks('foo')
>>> q.xyz
123
>>> q.weights
456
虽然我认为这有点笨拙,但这实际上取决于您打算如何实现 Quadrature
的子classes。
我的建议是不要将 xyz
或 weights
抽象化,而是处理它们是否在运行时设置,即捕获在访问值时可能弹出的任何 AttributeError
s。
为了强制子类实现一个属性或方法,你需要抛出一个错误,如果这个方法没有被实现:
from abc import ABCMeta, abstractmethod, abstractproperty
class Quadrature(object, metaclass=ABCMeta):
@abstractproperty
def xyz(self):
raise NotImplementedError
Class 注释解决方案
这是可能的,因为对 python 3.7 进行了更改(我希望您正在使用它 - 因为它很酷!),因为它添加了 type hinting
and the ability to add class annotations, which were added for dataclasses
。它与我能想到的最接近您最初所需的语法。你想要的 superclass 看起来像这样:
from abc import ABC, abstractmethod
from typing import List
class PropertyEnfocedABC(ABC):
def __init__(self):
annotations = self.__class__.__dict__.get('__annotations__', {})
for name, type_ in annotations.items():
if not hasattr(self, name):
raise AttributeError(f'required attribute {name} not present '
f'in {self.__class__}')
现在,看看它的实际效果。
class Quadratic(PropertyEnfocedABC):
xyz: int
weights: List[int]
def __init__(self):
self.xyz = 2
self.weights = [4]
super().__init__()
或者更准确地说,在你的情况下,混合使用抽象方法和属性:
class Quadrature(PropertyEnforcedABC):
xyz: int
weights: int
@abstractmethod
def __init__(self, order):
pass
@abstractmethod
def some_stupid_function(self, n):
return self.xyz + self.weights + n
现在,PropertyEnforcedABC
的子class 的任何子class 必须设置 class 中注释的属性(如果您不提供type 到注解,它不会被认为是注解)因此,如果 quadratic 的构造函数没有设置 xyz
或 weights
,则会引发属性错误。请注意,您必须在 init 的末尾调用构造函数,但这应该不是一个真正的问题,如果您 really 不喜欢。
您可以根据需要修改 PropertyEnforcedABC
(例如强制执行属性的类型)等等。您甚至可以检查 Optional
并忽略它们。
我想要一个抽象 class,它强制每个派生 class 在其 __init__
方法中设置某些属性。
我看了几个问题,但没有完全解决我的问题,特别是
我假设我想要的结果可能如下所示伪代码:
from abc import ABCMeta, abstractmethod
class Quadrature(object, metaclass=ABCMeta):
@someMagicKeyword #<==== This is what I want, but can't get working
xyz
@someMagicKeyword #<==== This is what I want, but can't get working
weights
@abstractmethod
def __init__(self, order):
pass
def someStupidFunctionDefinedHere(self, n):
return self.xyz+self.weights+n
class QuadratureWhichWorks(Quadrature):
# This shall work because we initialize xyz and weights in __init__
def __init__(self,order):
self.xyz = 123
self.weights = 456
class QuadratureWhichShallNotWork(Quadrature):
# Does not initialize self.weights
def __init__(self,order):
self.xyz = 123
以下是我尝试过的一些方法:
from abc import ABCMeta, abstractmethod
class Quadrature(object, metaclass=ABCMeta):
@property
@abstractmethod
def xyz(self):
pass
@property
@abstractmethod
def weights(self):
pass
@abstractmethod
def __init__(self, order):
pass
def someStupidFunctionDefinedHere(self, n):
return self.xyz+self.weights+n
class QuadratureWhichWorks(Quadrature):
# This shall work because we initialize xyz and weights in __init__
def __init__(self,order):
self.xyz = 123
self.weights = 456
然后我尝试创建一个实例:
>>> from example1 import *
>>> Q = QuadratureWhichWorks(10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class QuadratureWhichWorks with abstract methods weights, xyz
>>>
这告诉我实现方法,但我以为我说这些是properties
?
我目前的解决方法有一个缺陷,即 __init__
方法可以在派生的 classes 中被覆盖,但现在这至少确保(对我而言)我总是知道请求的属性已设置:
from abc import ABCMeta, abstractmethod
class Quadrature(object, metaclass=ABCMeta):
@abstractmethod
def computexyz(self,order):
pass
@abstractmethod
def computeweights(self,order):
pass
def __init__(self, order):
self.xyz = self.computexyz(order)
self.weights = self.computeweights(order)
def someStupidFunctionDefinedHere(self, n):
return self.xyz+self.weights+n
class QuadratureWhichWorks(Quadrature):
def computexyz(self,order):
return order*123
def computeweights(self,order):
return order*456
class HereComesTheProblem(Quadrature):
def __init__(self,order):
self.xyz = 123
# but nothing is done with weights
def computexyz(self,order):
return order*123
def computeweights(self,order): # will not be used
return order*456
但问题是
>>> from example2 import *
>>> Q = HereComesTheProblem(10)
>>> Q.xyz
123
>>> Q.weights
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'HereComesTheProblem' object has no attribute 'weights'
这是如何正确实施的?
编辑: 使用自定义元的解决方案class。
值得注意的是,自定义元classes 通常不受欢迎,但您可以用一个来解决这个问题。
Here 是一篇讨论它们如何工作以及何时有用的好文章。这里的解决方案本质上是在调用 __init__
之后检查您想要的属性。
from abc import ABCMeta, abstractmethod
# our version of ABCMeta with required attributes
class MyMeta(ABCMeta):
required_attributes = []
def __call__(self, *args, **kwargs):
obj = super(MyMeta, self).__call__(*args, **kwargs)
for attr_name in obj.required_attributes:
if not getattr(obj, attr_name):
raise ValueError('required attribute (%s) not set' % attr_name)
return obj
# similar to the above example, but inheriting MyMeta now
class Quadrature(object, metaclass=MyMeta):
required_attributes = ['xyz', 'weights']
@abstractmethod
def __init__(self, order):
pass
class QuadratureWhichWorks(Quadrature):
# This shall work because we initialize xyz and weights in __init__
def __init__(self,order):
self.xyz = 123
self.weights = 456
q = QuadratureWhichWorks('foo')
class QuadratureWhichShallNotWork(Quadrature):
def __init__(self, order):
self.xyz = 123
q2 = QuadratureWhichShallNotWork('bar')
以下是我的原始回答,更笼统地探讨了该主题。
原答案
我认为部分原因是混淆了 实例属性 与 property
装饰器包装的对象。
- 实例属性是嵌套在实例命名空间中的纯数据块。同样,class 属性嵌套在 class 的命名空间中(并且由 class 的实例共享,除非它们覆盖它)。
- 属性 是一个具有语法快捷方式的函数,使它们可以像属性一样访问,但它们的功能性质允许它们是动态的。
一个没有引入抽象 classes 的小例子是
>>> class Joker(object):
>>> # a class attribute
>>> setup = 'Wenn ist das Nunstück git und Slotermeyer?'
>>>
>>> # a read-only property
>>> @property
>>> def warning(self):
>>> return 'Joke Warfare is explicitly banned bythe Geneva Conventions'
>>>
>>> def __init__(self):
>>> self.punchline = 'Ja! Beiherhund das Oder die Flipperwaldt gersput!'
>>> j = Joker()
>>> # we can access the class attribute via class or instance
>>> Joker.setup == j.setup
>>> # we can get the property but cannot set it
>>> j.warning
'Joke Warfare is explicitly banned bythe Geneva Conventions'
>>> j.warning = 'Totally safe joke...'
AttributeError: cant set attribute
>>> # instance attribute set in __init__ is only accessible to that instance
>>> j.punchline != Joker.punchline
AttributeError: type object 'Joker' has no attribute 'punchline'
根据 Python docs,自 3.3 以来,abstractproperty
是多余的,实际上反映了您尝试的解决方案。
该解决方案的问题是您的子 classes 没有实现具体的 属性,它们只是用实例属性覆盖它。
为了继续使用 abc
包,您可以通过实现这些属性来处理这个问题,即
>>> from abc import ABCMeta, abstractmethod
>>> class Quadrature(object, metaclass=ABCMeta):
>>>
>>> @property
>>> @abstractmethod
>>> def xyz(self):
>>> pass
>>>
>>> @property
>>> @abstractmethod
>>> def weights(self):
>>> pass
>>>
>>> @abstractmethod
>>> def __init__(self, order):
>>> pass
>>>
>>> def someStupidFunctionDefinedHere(self, n):
>>> return self.xyz+self.weights+n
>>>
>>>
>>> class QuadratureWhichWorks(Quadrature):
>>> # This shall work because we initialize xyz and weights in __init__
>>> def __init__(self,order):
>>> self._xyz = 123
>>> self._weights = 456
>>>
>>> @property
>>> def xyz(self):
>>> return self._xyz
>>>
>>> @property
>>> def weights(self):
>>> return self._weights
>>>
>>> q = QuadratureWhichWorks('foo')
>>> q.xyz
123
>>> q.weights
456
虽然我认为这有点笨拙,但这实际上取决于您打算如何实现 Quadrature
的子classes。
我的建议是不要将 xyz
或 weights
抽象化,而是处理它们是否在运行时设置,即捕获在访问值时可能弹出的任何 AttributeError
s。
为了强制子类实现一个属性或方法,你需要抛出一个错误,如果这个方法没有被实现:
from abc import ABCMeta, abstractmethod, abstractproperty
class Quadrature(object, metaclass=ABCMeta):
@abstractproperty
def xyz(self):
raise NotImplementedError
Class 注释解决方案
这是可能的,因为对 python 3.7 进行了更改(我希望您正在使用它 - 因为它很酷!),因为它添加了 type hinting
and the ability to add class annotations, which were added for dataclasses
。它与我能想到的最接近您最初所需的语法。你想要的 superclass 看起来像这样:
from abc import ABC, abstractmethod
from typing import List
class PropertyEnfocedABC(ABC):
def __init__(self):
annotations = self.__class__.__dict__.get('__annotations__', {})
for name, type_ in annotations.items():
if not hasattr(self, name):
raise AttributeError(f'required attribute {name} not present '
f'in {self.__class__}')
现在,看看它的实际效果。
class Quadratic(PropertyEnfocedABC):
xyz: int
weights: List[int]
def __init__(self):
self.xyz = 2
self.weights = [4]
super().__init__()
或者更准确地说,在你的情况下,混合使用抽象方法和属性:
class Quadrature(PropertyEnforcedABC):
xyz: int
weights: int
@abstractmethod
def __init__(self, order):
pass
@abstractmethod
def some_stupid_function(self, n):
return self.xyz + self.weights + n
现在,PropertyEnforcedABC
的子class 的任何子class 必须设置 class 中注释的属性(如果您不提供type 到注解,它不会被认为是注解)因此,如果 quadratic 的构造函数没有设置 xyz
或 weights
,则会引发属性错误。请注意,您必须在 init 的末尾调用构造函数,但这应该不是一个真正的问题,如果您 really 不喜欢。
您可以根据需要修改 PropertyEnforcedABC
(例如强制执行属性的类型)等等。您甚至可以检查 Optional
并忽略它们。