在Python中使用抽象属性实现以下Scala代码的最短/最优雅的方法是什么?
abstract class Controller {
val path: String
}
Controller
Scala编译器强制使用的子类来定义“路径”。子类如下所示:
class MyController extends Controller {
override val path = "/home"
}
在Python中使用抽象属性实现以下Scala代码的最短/最优雅的方法是什么?
abstract class Controller {
val path: String
}
Controller
Scala编译器强制使用的子类来定义“路径”。子类如下所示:
class MyController extends Controller {
override val path = "/home"
}
Answers:
Python有一个内置的异常,尽管直到运行时您都不会遇到该异常。
class Base(object):
@property
def path(self):
raise NotImplementedError
class SubClass(Base):
path = 'blah'
NotImplementedError
更为明确,因此可能比将其交给a更好AttributeError
。
path
直接在上设置时才有效SubClass
。给定一个实例sc = SubClass()
,如果您尝试设置sc.path = 'blah'
或有一个包含诸如此类的方法self.path = 'blah'
而没有path
直接定义的方法SubClass
,您将得到一个AttributeError: can't set attribute
。
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
a
为的类属性B
,而不是对象属性。如果改为使用类B(A):def __init __(self):self.a = 1 ...```实例化时会出现错误B
:“无法使用抽象方法a实例化抽象类B”
class MyController(val path: String) extends Controller
成为Scala版本中可用的有价值的选择,但在此缺失
自从最初提出这个问题以来,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
而不是just访问子类常量CONSTANT
,因为该值在基类中未定义。
还有其他方法可以实现此功能,但是我喜欢这种语法,因为对我来说,这似乎对读者来说是最简单和显而易见的。
先前的答案都触及了有用的观点,但是我认为被接受的答案并不能直接回答问题,因为
CONSTANT
未定义子类,则在实例化子类时会导致运行时错误。接受的答案允许实例化该对象,并且仅在CONSTANT
访问该对象时抛出错误,从而使执行不太严格。 这不是对原始答案的错。自发布以来,对抽象类语法进行了重大更改,在这种情况下,它可以实现更整洁,更实用的实现。
mypy
print_constant
方法的目的是什么?据我所知,它什么都没做?
print_constant
那里的方法说明了如何在父类中访问常量,因为该常量仅在子类中定义
Base.CONSTANT
则mypy还将检查子类是否使用正确的类型来定义CONSTANT
。
在Python 3.6+中,您可以注释抽象类(或任何变量)的属性,而无需提供该属性的值。
class Controller:
path: str
class MyController(Controller):
path = "/home"
这样就可以很明显地看到属性是抽象的代码。如果尚未被覆盖,则尝试访问该属性的代码将引发一个AttributeError
。
您可以在abc.ABC抽象基类中创建一个属性,其值例如为,NotImplemented
这样,如果不重写该属性然后使用该属性,则在运行时会显示错误。
以下代码使用PEP 484类型提示来帮助PyCharm正确地静态分析path
属性的类型。
import abc
class Controller(abc.ABC):
path: str = NotImplemented
class MyController(Controller):
path = "/home"
对于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,
# This approach provides more clarity
class AnotherBaseController(ABC):
@property
@abstractmethod
def path(self) -> str:
"""
:return: the url path of this controller
"""
与其他答案有何不同:
...
在抽象方法的主体中比在中更可取pass
。与不同pass
,...
表示没有操作,pass
仅表示没有实际的实现
...
比投掷更建议NotImplementedError(...)
。如果子类中缺少抽象字段的实现,则会自动提示一个极其冗长的错误。相反,NotImplementedError
它本身并不能说明为什么缺少实现。此外,它需要体力劳动才能真正提高它。
...
还是引发,NotImplementedError
或者根本pass
不影响引发的错误。
pass
elipsis(...
)。皮林特告诉我。
从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'
)
如果未定义缺少的类变量,则在子类的初始化上会引发错误,因此您不必等到将访问丢失的类变量。
__init_subclass__
在初始化子类之前,将调用父类的。因此,如果您要检查子类的中设置的属性__init__
,则无法使用。
__init__
方法期间设置的实例变量。
您的基类可以实现一种__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
这样,实例化时错误会增加
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>
我做了一点修改 @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
BastienLéonard的答案提到了抽象基类模块,而Brendan Abel的答案涉及未实现的属性,从而引起错误。为确保未在模块外部实现该类,可以在基本名称前添加下划线,以表示该名称对模块是私有的(即,未导入)。
即
class _Controller(object):
path = '' # There are better ways to declare attributes - see other answers
class MyController(_Controller):
path = '/Home'
_Controller
类中省略路径声明会更好吗?如果已经存在(无效)值,则“鸭子输入”将不会生效。否则,在某个时候,我需要path
定义该字段,就不会有错误,因为已经有一个值。