Python中的抽象属性
在Python中,如何用最简洁、最优雅的方式实现以下的Scala代码,其中包含一个抽象属性?
abstract class Controller {
val path: String
}
在Scala中,Controller
的子类必须由编译器定义一个叫“path”的属性。一个子类可能是这样的:
class MyController extends Controller {
override val path = "/home"
}
13 个回答
119
自从这个问题最初被提出来后,Python 在抽象类的实现方式上发生了变化。我使用了一种稍微不同的方法,利用了 Python 3.6 中的 abc.ABC 形式。在这里,我把常量定义为一个属性,这个属性必须在每个子类中定义。
from abc import ABC, abstractmethod
class Base(ABC):
@classmethod
@property
@abstractmethod
def CONSTANT(cls):
raise NotImplementedError
def print_constant(self):
print(type(self).CONSTANT)
class Derived(Base):
CONSTANT = 42
这样一来,派生类就必须定义这个常量,否则当你尝试创建子类的实例时,就会抛出一个 TypeError
错误。如果你想在抽象类中使用这个常量的任何功能,你必须通过 type(self).CONSTANT
来访问子类的常量,而不能直接用 CONSTANT
,因为在基类中这个值是未定义的。
还有其他实现方式,但我觉得这种语法对读者来说最简单明了。
之前的回答都提到了有用的观点,但我觉得被接受的答案并没有直接回答问题,因为:
- 问题是关于抽象类的实现,但被接受的答案没有遵循抽象的形式。
- 问题要求强制实现。我认为这个答案的强制性更严格,因为如果
CONSTANT
没有定义,创建子类实例时会导致运行时错误。而被接受的答案允许对象被实例化,只有在访问CONSTANT
时才会抛出错误,这样的强制性就没那么严格了。
这并不是要批评原来的答案。自从它们发布以来,抽象类的语法发生了重大变化,这在这种情况下允许更整洁和更实用的实现。
173
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
如果在派生类 B
中没有声明 a
或 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
116
Python有一个内置的异常处理机制,不过你要等到程序运行时才会遇到这个异常。
class Base(object):
@property
def path(self):
raise NotImplementedError
class SubClass(Base):
path = 'blah'