Python抽象类应强制派生类在__init__中初始化变量

k1n*_*ext 14 python oop abstract-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 
Run Code Online (Sandbox Code Playgroud)

这是我尝试过的一些方法:

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
Run Code Online (Sandbox Code Playgroud)

然后,我尝试创建一个实例:

>>> 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
>>> 
Run Code Online (Sandbox Code Playgroud)

哪个告诉我实现这些方法,但是我以为我说这些是properties

我当前的解决方法有一个缺陷,即该__init__方法可以在派生类中被覆盖,但是就目前而言,这至少可以确保(对我而言)我始终知道所请求的属性已设置:

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
Run Code Online (Sandbox Code Playgroud)

但是问题是

>>> 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'
Run Code Online (Sandbox Code Playgroud)

如何正确实施?

And*_*w F 7

编辑:具有自定义元类的解决方案。

值得注意的是,自定义元类通常不被接受,但是您可以用一个人解决这个问题。 是一篇很好的文章,讨论了它们如何工作以及何时有用。实质上,解决方案是在__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')
Run Code Online (Sandbox Code Playgroud)

以下是我的原始答案,它更全面地探讨了该主题。

原始答案

我认为其中某些原因是混淆了实例属性property装饰器包装的对象。

  • 实例属性是嵌套在实例名称空间中的普通数据块。同样,类属性嵌套在类的名称空间中(并由该类的实例共享,除非它们将其覆盖)。
  • 属性是具有语法快捷方式的功能,可以使它们像属性一样可以访问,但是其功能性质使它们可以动态化。

一个不引入抽象类的小例子是

>>> 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'
Run Code Online (Sandbox Code Playgroud)

根据Python文档,从3.3开始,它abstractproperty是多余的,实际上反映了您尝试的解决方案。该解决方案的问题在于,您的子类没有实现具体的属性,而是仅使用实例属性覆盖它。为了继续使用该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
Run Code Online (Sandbox Code Playgroud)

我认为这虽然有些笨拙,但实际上取决于您打算如何实现的子类Quadrature。我的建议是不要做xyzweights抽象的,而是处理他们是否在运行时设置的,即捕获任何AttributeError访问该值时可能会弹出秒。

  • 不错的解决方案!但是,在对super的__call__的调用中,您在args前面错过了一个*。 (2认同)