web-dev-qa-db-ja.com

Python抽象クラスは、派生クラスに__init__の変数を強制的に初期化させる

すべての派生クラスに__init__メソッドで特定の属性を強制的に設定する抽象クラスが必要です。

私は問題を完全には解決しなかったいくつかの質問、具体的には here または here を見ました。 これ は有望に見えましたが、うまく機能させることができませんでした。

私の望ましい結果は次のようになると思います疑似コード

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__メソッドを上書きできるという欠点がありますが、今のところ、これにより、少なくとも要求されたプロパティが設定されていることが常にわかります(私にとっては)。

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'

これはどのように正しく実装されていますか?

15
k1next

Edit:カスタムメタクラスを使用したソリューション。

カスタムメタクラスはしばしば不快感を覚えることに注意してください。ただし、この問題を解決することができます。 ここ は、それらがどのように機能し、いつ役立つかを説明する良い記事です。ここでの解決策は基本的に、__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 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は冗長であり、実際に試行したソリューションを反映しています。そのソリューションの問題は、サブクラスが具象プロパティを実装せず、インスタンス属性で上書きするだけであるということです。 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のサブクラスを実装する方法によって異なります。私の提案は、xyzまたはweightsを抽象化せずに、実行時に設定されたかどうかを処理することです。つまり、値にアクセスするときにポップアップするAttributeErrorsをキャッチします。

9
Andrew F

クラス注釈ソリューション

これは、python 3.7(これを使用していることを願っています-これはクールなので!)への変更のために可能です type hinting および dataclasses に追加されたクラス annotations を追加する機能。それは私が思いつくことができるようにあなたの元の望ましい構文に近いです。必要なスーパークラスは次のようになります。

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のサブクラスのサブクラスは、クラスで注釈が付けられた属性を設定する必要があります(注釈に型を指定しない場合、注釈とは見なされません)。したがって、二次関数がxyzまたはweightsを設定しなかった場合、属性エラーが発生します。 initの終わりにコンストラクターを呼び出す必要がありますが、これは実際の問題ではなく、reallydon気に入らない。

PropertyEnforcedABCは必要に応じて変更できます(プロパティのタイプを強制するなど)など。 Optionalをチェックして無視することもできます。

2
modesitt

サブクラスにプロパティまたはメソッドを実装させるには、このメソッドが実装されていない場合にエラーを発生させる必要があります。

from abc import ABCMeta, abstractmethod, abstractproperty

class Quadrature(object, metaclass=ABCMeta):

    @abstractproperty
    def xyz(self):
        raise NotImplementedError


2
Lucas