すべての派生クラスに__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'
これはどのように正しく実装されていますか?
カスタムメタクラスはしばしば不快感を覚えることに注意してください。ただし、この問題を解決することができます。 ここ は、それらがどのように機能し、いつ役立つかを説明する良い記事です。ここでの解決策は基本的に、__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
を抽象化せずに、実行時に設定されたかどうかを処理することです。つまり、値にアクセスするときにポップアップするAttributeError
sをキャッチします。
これは、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
をチェックして無視することもできます。
サブクラスにプロパティまたはメソッドを実装させるには、このメソッドが実装されていない場合にエラーを発生させる必要があります。
from abc import ABCMeta, abstractmethod, abstractproperty
class Quadrature(object, metaclass=ABCMeta):
@abstractproperty
def xyz(self):
raise NotImplementedError