Python 中的抽象属性[重复]
- 2025-03-05 09:14:00
- admin 原创
- 102
问题描述:
在 Python 中使用抽象属性实现以下 Scala 代码的最短/最优雅的方法是什么?
abstract class Controller {
val path: String
}
Scala 编译器强制 的子类Controller
定义“路径”。子类如下所示:
class MyController extends Controller {
override val path = "/home"
}
解决方案 1:
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
或失败,将引发如下错误:b
`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
解决方案 2:
自从最初提出这个问题以来,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
访问时抛出错误,从而使执行不那么严格。
这并不是对原始答案的指责。自发布以来,抽象类语法已发生重大变化,在这种情况下,允许更简洁、更实用的实现。
解决方案 3:
Python 对此有一个内置异常,但您直到运行时才会遇到该异常。
class Base(object):
@property
def path(self):
raise NotImplementedError
class SubClass(Base):
path = 'blah'
解决方案 4:
在 Python 3.6+ 中,您可以注释抽象类(或任何变量)的属性,而无需为该属性提供值。
from abc import ABC
class Controller(ABC):
path: str
class MyController(Controller):
def __init__(self, path: str):
self.path = path
这使得代码非常干净,其中明显属性是抽象的。
需要注意的是,如果子类未提供实现,则在定义时不会引发异常。但是,AttributeError
如果有任何东西试图访问未定义的属性,则会引发异常。
解决方案 5:
您可以在abc.ABC抽象基类中创建一个具有如下值的属性NotImplemented
,这样如果该属性未被覆盖然后被使用,则会在运行时显示一个表达意图的明确错误。
以下代码使用PEP 484类型提示来帮助 PyCharm 正确地静态分析属性的类型path
。
from abc import ABC
class Controller(ABC):
path: str = NotImplemented
class MyController(Controller):
path = "/home"
解决方案 6:
从 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'
)
如果未定义缺失的类变量,则会在初始化子类时引发错误,因此您不必等到访问缺失的类变量。
解决方案 7:
对于Python 3.3以上版本,有一个优雅的解决方案
from abc import ABC, abstractmethod
class BaseController(ABC):
@property
@abstractmethod
def path(self) -> str:
...
class Controller(BaseController):
path = "/home"
# Instead of an elipsis, you can add a docstring for clarity
class AnotherBaseController(ABC):
@property
@abstractmethod
def path(self) -> str:
"""
:return: the url path of this controller
"""
尽管已经给出了一些很好的答案,但我认为这个答案仍然会增加一些价值。这种方法有两个优点:
...
在抽象方法体中比 更可取pass
。与 不同pass
,...
表示没有操作,pass
仅表示没有实际实现...
比 throwing 更推荐NotImplementedError(...)
。如果子类中缺少抽象字段的实现,这会自动提示极其详细的错误。相比之下,NotImplementedError
它本身并没有说明为什么缺少实现。此外,它需要人工来实际引发它。
解决方案 8:
我稍微修改了一下@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
解决方案 9:
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>
解决方案 10:
您的基类可以实现一种__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)
class C1(Controller):
path = 42
class C2(Controller):
pass
c1 = C1()
# ok
c2 = C2()
# NotImplementedError: 'Controller' subclasses should have a 'path' attribute
这样,实例化时就会引发错误
解决方案 11:
class AbstractStuff:
@property
@abc.abstractmethod
def some_property(self):
pass
我认为从 3.3 开始abc.abstractproperty
它已被弃用。
解决方案 12:
Bastien Léonard 的回答提到了抽象基类模块,而 Brendan Abel 的回答则涉及未实现的属性引发的错误。为了确保类未在模块之外实现,您可以在基类名称前加上下划线,表示它是模块私有的(即未导入)。
IE
class _Controller(object):
path = '' # There are better ways to declare attributes - see other answers
class MyController(_Controller):
path = '/Home'
解决方案 13:
看一下 abc(抽象基类)模块:http://docs.python.org/library/abc.html
然而,我认为最简单和最常见的解决方案是在创建基类的实例或访问其属性时引发异常。
扫码咨询,免费领取项目管理大礼包!