Pythonで抽象属性を持つ次のScalaコードを実装する最短/最もエレガントな方法は何ですか?
abstract class Controller {
val path: String
}
Controller
のサブクラスは、Scalaコンパイラーによって「パス」を定義するために実施されます。サブクラスは次のようになります。
class MyController extends Controller {
override val path = "/home"
}
Pythonにはこのための組み込みの例外がありますが、実行時まで例外は発生しません。
class Base(object):
@property
def path(self):
raise NotImplementedError
class SubClass(Base):
path = 'blah'
from abc import ABCMeta, abstractmethod
class A(metaclass=ABCMeta):
def __init__(self):
# ...
pass
@property
@abstractmethod
def a(self):
pass
@abstractmethod
def b(self):
pass
class B(A):
a = 1
def b(self):
pass
派生クラスa
でb
またはB
を宣言できないと、次のようなTypeError
が発生します。
TypeError
:抽象メソッドB
で抽象クラスa
をインスタンス化できません
これには @ abstractproperty デコレーターがあります:
from abc import ABCMeta, abstractmethod, abstractproperty
class A:
__metaclass__ = ABCMeta
def __init__(self):
# ...
pass
@abstractproperty
def a(self):
pass
@abstractmethod
def b(self):
pass
class B(A):
a = 1
def b(self):
pass
この質問はもともと質問されていたため、pythonは抽象クラスの実装方法を変更しました。python 3.6でabc.ABC形式を使用して少し異なるアプローチを使用しましたここでは、各サブクラスで定義する必要があるプロパティとして定数を定義します。
_from abc import ABC, abstractmethod
class Base(ABC):
@property
@classmethod
@abstractmethod
def CONSTANT(cls):
return NotImplementedError
def print_constant(self):
print(type(self).CONSTANT)
class Derived(Base):
CONSTANT = 42
_
これにより、派生クラスで定数を強制的に定義するか、サブクラスをインスタンス化しようとするとTypeError
例外が発生します。抽象クラスで実装された機能に定数を使用する場合、値は基本クラスで未定義なので、CONSTANT
だけでなくtype(self).CONSTANT
でサブクラス定数にアクセスする必要があります。
これを実装する方法は他にもありますが、読者にとって最も明白で明白なように思えるので、この構文が好きです。
以前の回答はすべて有用なポイントに触れましたが、受け入れられた回答は質問に直接回答しないと感じています
CONSTANT
が定義されていない場合、サブクラスがインスタンス化されると実行時エラーが発生するため、この回答では施行がより厳格であると主張します。受け入れられた回答により、オブジェクトをインスタンス化することができ、CONSTANT
にアクセスしたときにのみエラーがスローされ、施行の厳格さが緩和されます。これは、元の答えに誤りがあるわけではありません。抽象クラスの構文は、ポストされてから大幅に変更されました。この場合、よりきれいで機能的な実装が可能になります。
abc.ABC 抽象ベースクラスに NotImplemented
などの値を持つ属性を作成して、属性がオーバーライドされてから使用されない場合、実行時にエラーが表示されます。
次のコードでは、PyCharmがpath
属性のタイプも正しく静的に分析するのに役立つ PEP 484 タイプヒントを使用しています。
import abc
class Controller(abc.ABC):
path = NotImplemented # type: str
class MyController(Controller):
path = '/home'
Python 3.6 + では、抽象クラス(または任意の変数)の属性に値を指定せずに、その属性に注釈を付けることができます。
class Controller:
path: str
class MyController(Controller):
path = "/home"
これにより、属性が抽象的であることが明らかな非常にクリーンなコードになります。上書きされていない場合に属性にアクセスしようとするコードは、AttributeError
を発生させます。
基本クラスは、クラス属性をチェックする__new__
メソッドを実装できます。
class Controller(object):
def __new__(cls, *args, **kargs):
if not hasattr(cls,'path'):
raise NotImplementedError("'Controller' subclasses should have a 'path' attribute")
return object.__new__(cls,*args,**kargs)
class C1(Controller):
path = 42
class C2(Controller):
pass
c1 = C1()
# ok
c2 = C2()
# NotImplementedError: 'Controller' subclasses should have a 'path' attribute
これにより、インスタンス化時にエラーが発生します
Abc(Abtract Base Class)モジュールをご覧ください: http://docs.python.org/library/abc.html
ただし、私の意見では、最も単純で最も一般的な解決策は、基本クラスのインスタンスが作成されたとき、またはそのプロパティにアクセスしたときに例外を発生させることです。
Python 3.6以降、__init_subclass__
初期化時に子クラスのクラス変数を確認するには:
from abc import ABC
class A(ABC):
@classmethod
def __init_subclass__(cls):
required_class_variables = [
"foo",
"bar"
]
for var in required_class_variables:
if not hasattr(cls, var):
raise NotImplementedError(
f'Class {cls} lacks required `{var}` class attribute'
)
これにより、欠落しているクラス変数が定義されていない場合、子クラスの初期化時にエラーが発生するため、欠落しているクラス変数にアクセスするまで待つ必要はありません。
Python3.6の実装は次のようになります。
In [20]: class X:
...: def __init_subclass__(cls):
...: if not hasattr(cls, 'required'):
...: raise NotImplementedError
In [21]: class Y(X):
...: required =5
...:
In [22]: Y()
Out[22]: <__main__.Y at 0x7f08408c9a20>
class AbstractStuff:
@property
@abc.abstractmethod
def some_property(self):
pass
3.3の時点でabc.abstractproperty
は非推奨です。
BastienLéonardの答えは抽象基本クラスモジュールに言及しており、Brendan Abelの答えは実装されていない属性がエラーを引き起こすことを扱っています。クラスがモジュールの外部に実装されないようにするには、ベース名の前にアンダースコアを付けて、モジュールに対してプライベートであることを示します(つまり、インポートされません)。
つまり.
class _Controller(object):
path = '' # There are better ways to declare attributes - see other answers
class MyController(_Controller):
path = '/Home'