web-dev-qa-db-ja.com

Pythonの抽象属性

Pythonで抽象属性を持つ次のScalaコードを実装する最短/最もエレガントな方法は何ですか?

abstract class Controller {

    val path: String

}

Controllerのサブクラスは、Scalaコンパイラーによって「パス」を定義するために実施されます。サブクラスは次のようになります。

class MyController extends Controller {

    override val path = "/home"

}
50
deamon

Pythonにはこのための組み込みの例外がありますが、実行時まで例外は発生しません。

class Base(object):
    @property
    def path(self):
        raise NotImplementedError


class SubClass(Base):
    path = 'blah'
61
user297250

Python 3.3以降

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

派生クラスabまたはBを宣言できないと、次のようなTypeErrorが発生します。

TypeError:抽象メソッドBで抽象クラスaをインスタンス化できません

Python 2.7

これには @ 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
63
Wtower

この質問はもともと質問されていたため、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にアクセスしたときにのみエラーがスローされ、施行の厳格さが緩和されます。

これは、元の答えに誤りがあるわけではありません。抽象クラスの構文は、ポストされてから大幅に変更されました。この場合、よりきれいで機能的な実装が可能になります。

13
James

abc.ABC 抽象ベースクラスに NotImplemented などの値を持つ属性を作成して、属性がオーバーライドされてから使用されない場合、実行時にエラーが表示されます。

次のコードでは、PyCharmがpath属性のタイプも正しく静的に分析するのに役立つ PEP 484 タイプヒントを使用しています。

import abc


class Controller(abc.ABC):
    path = NotImplemented  # type: str


class MyController(Controller):
    path = '/home'
6
phoenix

Python 3.6 + では、抽象クラス(または任意の変数)の属性に値を指定せずに、その属性に注釈を付けることができます。

class Controller:
    path: str

class MyController(Controller):
    path = "/home"

これにより、属性が抽象的であることが明らかな非常にクリーンなコードになります。上書きされていない場合に属性にアクセスしようとするコードは、AttributeErrorを発生させます。

4
drhagen

基本クラスは、クラス属性をチェックする__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

これにより、インスタンス化時にエラーが発生します

3
Juh_

Abc(Abtract Base Class)モジュールをご覧ください: http://docs.python.org/library/abc.html

ただし、私の意見では、最も単純で最も一般的な解決策は、基本クラスのインスタンスが作成されたとき、またはそのプロパティにアクセスしたときに例外を発生させることです。

2

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'
                )

これにより、欠落しているクラス変数が定義されていない場合、子クラスの初期化時にエラーが発生するため、欠落しているクラス変数にアクセスするまで待つ必要はありません。

2

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>
2
Artem Zhukov
class AbstractStuff:
    @property
    @abc.abstractmethod
    def some_property(self):
        pass

3.3の時点でabc.abstractpropertyは非推奨です。

0
grisaitis

BastienLéonardの答えは抽象基本クラスモジュールに言及しており、Brendan Abelの答えは実装されていない属性がエラーを引き起こすことを扱っています。クラスがモジュールの外部に実装されないようにするには、ベース名の前にアンダースコアを付けて、モジュールに対してプライベートであることを示します(つまり、インポートされません)。

つまり.

class _Controller(object):
    path = '' # There are better ways to declare attributes - see other answers

class MyController(_Controller):
    path = '/Home'
0
Brendan