在Python中使用抽象属性实现以下Scala代码的最短/最优雅的方法是什么?
abstract class Controller {
val path: String
}
强制使用Controller
的子类来定义Scala编译器的“路径”。子类看起来像这样:
class MyController extends Controller {
override val path = "/home"
}
答案 0 :(得分:59)
Python有一个内置的异常,但在运行时才会遇到异常。
class Base(object):
@property
def path(self):
raise NotImplementedError
class SubClass(Base):
path = 'blah'
答案 1 :(得分:55)
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
答案 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"
与其他答案有何不同?
...
比pass
更可取。与pass
不同,...
表示无操作,其中pass
仅表示没有实际的实现
...
相比,更建议 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。