Python抽象类应强制派生类在__init __

时间:2019-04-02 18:27:50

标签: python oop abstract-class

我想要一个抽象类,该抽象类强制每个派生类在其__init__方法中设置某些属性。

我查看了几个不能完全解决问题的问题,特别是herehereThis看起来很有希望,但我无法使其正常工作。

我认为我想要的结果可能类似于以下伪代码

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__方法可以在派生类中被覆盖,但是就目前而言,这至少确保了(对我而言)我始终知道所请求的属性已设置: / p>

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'

这如何正确实施?

3 个答案:

答案 0 :(得分:6)

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

值得注意的是,自定义元类经常被皱眉,但是您可以用一个人解决这个问题。 Here是一篇很好的文章,讨论了它们如何工作以及何时有用。此处的解决方案实质上是在调用Transform.scale( scale: 0.5, child: IconButton( onPressed: (){}, icon: new Image.asset("images/IG.png"), ), ), 之后对所需的属性进行检查。

__init__

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

原始答案

我认为部分原因是将 instance属性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

根据Python docs,由于3.3,>>> 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' 是多余的,实际上反映了您尝试的解决方案。 该解决方案的问题在于,您的子类没有实现具体的属性,而是仅使用实例属性覆盖它。 为了继续使用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 的子类。 我的建议是不要使Quadraturexyz抽象,而是处理是否在运行时设置它们,即捕获访问值时可能弹出的weights

答案 1 :(得分:2)

为了强制子类实现属性或方法,如果未实现此方法,则需要引发错误:

from abc import ABCMeta, abstractmethod, abstractproperty

class Quadrature(object, metaclass=ABCMeta):

    @abstractproperty
    def xyz(self):
        raise NotImplementedError


答案 2 :(得分:2)

类注释解决方案

这可能是由于python 3.7的更改(我希望您正在使用-因为它很酷!),因为它添加了type hinting和添加类annotations的功能,这些功能是为dataclasses。我认为,它与您最初想要的语法非常接近。您想要的超类将如下所示:

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的子类的任何子类都必须设置在该类中被注释的属性(如果您不为注释提供类型,它将不被视为注释),因此,如果二次方的构造函数未设置xyzweights,将引发属性错误。请注意,您必须在init的末尾调用构造函数,但这不是真正的问题,如果您真的不喜欢,可以通过在上述代码周围包装自己的元类来解决此问题。

您可以根据需要修改PropertyEnforcedABC(例如,强制设置属性的类型)等等。您甚至可以检查Optional并忽略它们。