Python中的抽象属性

时间:2010-04-29 09:47:58

标签: python oop scala abstract-class

在Python中使用抽象属性实现以下Scala代码的最短/最优雅的方法是什么?

abstract class Controller {

    val path: String

}

强制使用Controller的子类来定义Scala编译器的“路径”。子类看起来像这样:

class MyController extends Controller {

    override val path = "/home"

}

13 个答案:

答案 0 :(得分:59)

Python有一个内置的异常,但在运行时才会遇到异常。

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


class SubClass(Base):
    path = 'blah'

答案 1 :(得分:55)

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

未在派生类a中声明bB会引发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

答案 2 :(得分:8)

自从最初提出这个问题以来,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异常。如果要将常量用于抽象类中实现的任何功能,则必须通过type(self).CONSTANT而不是CONSTANT访问子类常量,因为该值在基类中未定义。

还有其他方法可以实现此目的,但是我喜欢这种语法,因为对我来说,这似乎对读者来说是最简单和显而易见的。

以前的答案都涉及到有用的观点,但是我认为被接受的答案并不能直接回答问题,因为

  • 该问题要求在抽象类中实现,但被接受的答案并不遵循抽象形式主义。
  • 问题要求实施是强制的。我认为在此答案中执行更加严格,因为如果未定义CONSTANT时实例化子类时,它将导致运行时错误。接受的答案允许实例化该对象,并且仅在访问CONSTANT时才引发错误,从而使实施的严格程度降低。

这不是对原始答案的错误。自发布以来,对抽象类语法进行了重大更改,在这种情况下,它可以实现更整洁,更实用的实现。

答案 3 :(得分:5)

您可以在abc.ABC抽象基类中创建一个属性,其值为NotImplemented,这样如果该属性未被覆盖然后使用,则会在运行时显示错误。

以下代码使用PEP 484类型提示来帮助PyCharm正确地静态分析path属性的类型。

import abc


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


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

答案 4 :(得分:3)

查看abc(Abtract Base Class)模块:http://docs.python.org/library/abc.html

但是,在我看来,最简单和最常见的解决方案是在创建基类的实例时,或者在访问其属性时引发异常。

答案 5 :(得分:3)

您的基类可以实现检查类属性的__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

这样在实例化时出现错误

答案 6 :(得分:3)

我仅修改了@James个答案,因此所有这些装饰器都不会占据太多位置。如果您要定义多个此类抽象属性,这将很方便:

from abc import ABC, abstractmethod

def abstractproperty(func):
   return property(classmethod(abstractmethod(func)))

class Base(ABC):

    @abstractproperty
    def CONSTANT(cls): ...

    def print_constant(self):
        print(type(self).CONSTANT)


class Derived(Base):
    CONSTANT = 42

class BadDerived(Base):
    BAD_CONSTANT = 42

Derived()       # -> Fine
BadDerived()    # -> Error

答案 7 :(得分: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>

答案 8 :(得分:2)

In Python 3.6+,您可以注释抽象类(或任何变量)的属性,而无需提供该属性的值。

class Controller:
    path: str

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

这使得代码很干净,很明显该属性是抽象的。如果尚未被覆盖,则尝试访问该属性的代码将引发AttributeError

答案 9 :(得分:1)

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

如果未定义缺少的类变量,则会在初始化子类时引发错误,因此您不必等到将访问丢失的类变量。

答案 10 :(得分:1)

对于 Python 3.3 +,有一个优雅的解决方案

from abc import ABC, abstractmethod

class BaseController(ABC):
    @property
    @abstractmethod
    def path(self) -> str:
        ...

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

与其他答案有何不同?

    抽象方法正文中的
  1. ...pass更可取。与pass不同,...表示无操作,其中pass仅表示没有实际的实现

  2. 与抛出...相比,更建议
  3. NotImplementedError(...)。如果子类中缺少抽象字段的实现,则会自动提示一个极其冗长的错误。相反,NotImplementedError本身并不能说明为什么缺少实现。此外,它需要体力劳动才能真正提高它。

答案 11 :(得分:0)

BastienLéonard的回答提到了抽象基类模块,Brendan Abel的回答涉及未实现的属性引发错误。为了确保该类不在模块外部实现,您可以在基本名称前加上一个下划线,表示它是模块的私有(即它不是导入的)。

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

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

答案 12 :(得分:0)

class AbstractStuff:
    @property
    @abc.abstractmethod
    def some_property(self):
        pass

我认为自abc.abstractproperty起已过3.3。