Python 中的抽象属性

发布于 2024-08-31 05:19:02 字数 296 浏览 4 评论 0原文

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

abstract class Controller {

    val path: String

}

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

class MyController extends Controller {

    override val path = "/home"

}

What is the shortest / most elegant way to implement the following Scala code with an abstract attribute in Python?

abstract class Controller {

    val path: String

}

A subclass of Controller is enforced to define "path" by the Scala compiler. A subclass would look like this:

class MyController extends Controller {

    override val path = "/home"

}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

空气里的味道 2024-09-07 05:19:02

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 中声明 ab 将引发 TypeError,例如:

TypeError:无法使用抽象方法a实例化抽象类B

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

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

Failure to declare a or b in the derived class B will raise a TypeError such as:

TypeError: Can't instantiate abstract class B with abstract methods a

Python 2.7

There is an @abstractproperty decorator for this:

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
故事还在继续 2024-09-07 05:19:02

自从这个问题最初被提出以来,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 时抛出错误,从而使执行不那么严格。

这并不是要指责原来的答案。自发布以来,抽象类语法发生了重大变化,在本例中允许更简洁、更实用的实现。

Since this question was originally asked, python has changed how abstract classes are implemented. I have used a slightly different approach using the abc.ABC formalism in python 3.6. Here I define the constant as a property which must be defined in each subclass.

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

This forces the derived class to define the constant, or else a TypeError exception will be raised when you try to instantiate the subclass. When you want to use the constant for any functionality implemented in the abstract class, you must access the subclass constant by type(self).CONSTANT instead of just CONSTANT, since the value is undefined in the base class.

There are other ways to implement this, but I like this syntax as it seems to me the most plain and obvious for the reader.

The previous answers all touched useful points, but I feel the accepted answer does not directly answer the question because

  • The question asks for implementation in an abstract class, but the accepted answer does not follow the abstract formalism.
  • The question asks that implementation is enforced. I would argue that enforcement is stricter in this answer because it causes a runtime error when the subclass is instantiated if CONSTANT is not defined. The accepted answer allows the object to be instantiated and only throws an error when CONSTANT is accessed, making the enforcement less strict.

This is not to fault the original answers. Major changes to the abstract class syntax have occurred since they were posted, which in this case allow a neater and more functional implementation.

琉璃梦幻 2024-09-07 05:19:02

Python 为此有一个内置异常,尽管直到运行时您才会遇到该异常。

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


class SubClass(Base):
    path = 'blah'

Python has a built-in exception for this, though you won't encounter the exception until runtime.

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


class SubClass(Base):
    path = 'blah'
毁虫ゝ 2024-09-07 05:19:02

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

from abc import ABC

class Controller(ABC):
    path: str

class MyController(Controller):
    def __init__(self, path: str):
        self.path = path

这使得代码非常干净,很明显该属性是抽象的。

应该注意的是,如果子类不提供实现,则不会在定义时引发异常。但是,如果任何东西尝试访问未定义的属性,则会引发 AttributeError 异常。

In Python 3.6+, you can annotate an attribute of an abstract class (or any variable) without providing a value for that attribute.

from abc import ABC

class Controller(ABC):
    path: str

class MyController(Controller):
    def __init__(self, path: str):
        self.path = path

This makes for very clean code where it is obvious that the attribute is abstract.

It should be noted that this will not raise an exception at definition time if a subclass does not provide an implementation. However, an AttributeError exception will be raised if anythhing tries to access the undefined attribute.

与君绝 2024-09-07 05:19:02

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

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

from abc import ABC

class Controller(ABC):
    path: str = NotImplemented

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

You could create an attribute in the abc.ABC abstract base class with a value such as NotImplemented so that if the attribute is not overriden and then used, a clear error that expresses intent is shown at run time.

The following code uses a PEP 484 type hint to help PyCharm correctly statically analyze the type of the path attribute as well.

from abc import ABC

class Controller(ABC):
    path: str = NotImplemented

class MyController(Controller):
    path = "/home"
勿忘初心 2024-09-07 05:19:02

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

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

As of Python 3.6 you can use __init_subclass__ to check for the class variables of the child class upon initialisation:

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

This raises an Error on initialisation of the child class, if the missing class variable is not defined, so you don't have to wait until the missing class variable would be accessed.

摇划花蜜的午后 2024-09-07 05:19:02

对于 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
        """

尽管已经给出了一些很好的答案,但我认为这个答案仍然会增加一些价值。这种方法有两个优点:

  1. 抽象方法体中的

    ...pass 更可取。与 pass 不同,... 意味着无操作,其中 pass 仅表示缺少实际实现

  2. 比抛出 NotImplementedError(...) 更推荐使用...。如果子类中缺少抽象字段的实现,这会自动提示极其详细的错误。相反,NotImplementedError 本身并没有说明为什么缺少实现。而且,实际饲养它还需要体力劳动。

For Python 3.3+ there's an elegant solution

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
        """

Despite some great answers have already been given, I thought this answer would nevertheless add some value. This approach has two advantages:

  1. ... in an abstract method's body is more preferable than pass. Unlike pass, ... implies no operations, where pass only means the absence of an actual implementation

  2. ... is more recommended than throwing NotImplementedError(...). This automatically prompts an extremely verbose error if the implementation of an abstract field is missing in a subclass. In contrast, NotImplementedError itself doesn't tell why the implementation is missing. Moreover, it requires manual labor to actually raise it.

如歌彻婉言 2024-09-07 05:19:02

我修改了一点 @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

I've modified just a bit @James answer, so that all those decorators do not take so much place. If you had multiple such abstract properties to define, this is handy:

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

梦行七里 2024-09-07 05:19:02

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>

Python3.6 implementation might looks like this:

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>
长安忆 2024-09-07 05:19:02

您的基类可以实现一个 __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

这样在实例化时会引发错误

Your base class could implement a __new__ method that check for class attribute:

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

This way the error raise at instantiation

不乱于心 2024-09-07 05:19:02
class AbstractStuff:
    @property
    @abc.abstractmethod
    def some_property(self):
        pass

我认为从 3.3 开始,abc.abstractproperty 已被弃用。

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

As of 3.3 abc.abstractproperty is deprecated, I think.

凉月流沐 2024-09-07 05:19:02

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'

Bastien Léonard's answer mentions the abstract base class module and Brendan Abel's answer deals with non-implemented attributes raising errors. To ensure that the class is not implemented outside of the module, you could prefix the base name with an underscore which denotes it as private to the module (i.e. it is not imported).

i.e.

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

class MyController(_Controller):
    path = '/Home'
月亮是我掰弯的 2024-09-07 05:19:02

看一下 abc(抽象基类)模块: http://docs.python.org /library/abc.html

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

Have a look at the abc (Abtract Base Class) module: http://docs.python.org/library/abc.html

However, in my opinion the simplest and most common solution is to raise an exception when an instance of the base class is created, or when its property is accessed.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文