Python中的抽象属性

dea*_*mon 58 python oop abstract-class scala

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

abstract class Controller {

    val path: String

}
Run Code Online (Sandbox Code Playgroud)

Controller强制使用子类来定义Scala编译器的"路径".子类看起来像这样:

class MyController extends Controller {

    override val path = "/home"

}
Run Code Online (Sandbox Code Playgroud)

Wto*_*wer 70

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
Run Code Online (Sandbox Code Playgroud)

未能声明ab在派生类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
Run Code Online (Sandbox Code Playgroud)

  • 这对我来说没有通过类型检查:文字不能分配给声明的类型“property” (6认同)
  • 您的python 3解决方案将`a`实现为`B`的类属性,而不是对象属性。如果改为使用类B(A):def __init __(self):self.a = 1 ...`则在实例化B时会出错:“无法使用抽象实例化抽象B类方法“ (3认同)

小智 62

虽然在运行时之前不会遇到异常,但Python有一个内置的异常.

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


class SubClass(Base):
    path = 'blah'
Run Code Online (Sandbox Code Playgroud)

  • 具体来说,在访问attrtibute之前不会遇到异常,在这种情况下,无论如何都会遇到AttributeError. (9认同)
  • 并不是说只有``path`直接在`SubClass`上设置才有效.给定一个实例`sc = SubClass()`,如果你试图设置`sc.path ='blah'`或者有一个方法包含类似`self.path ='blah'`的东西,而不直接定义`path`` SubClass`,你会得到一个`AttributeError:无法设置属性`. (8认同)
  • 我认为提出一个'NotImplementedError`是更明确的,因此可能比将它留给`AttributeError`更好. (5认同)
  • 这不是答案!!这不是实例字段。类中的Scala 字段是实例字段,因为Scala 将静态和实例分开。 (5认同)
  • @Simmovation参见/sf/answers/1169497171/ (3认同)
  • 应该是“raise NotImplementedError()”,否则您只是将类型作为异常引发,而不是异常对象本身。 (2认同)

Ser*_*ich 17

对于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
    """
Run Code Online (Sandbox Code Playgroud)

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

  1. ...在抽象方法的主体中比pass. 与pass,...意味着没有操作,其中pass仅意味着没有实际实现

  2. ...比扔更推荐NotImplementedError(...)。如果子类中缺少抽象字段的实现,这会自动提示极其详细的错误。相比之下,NotImplementedError它本身并没有说明为什么缺少实现。此外,它需要手工劳动才能真正抬起它。

  • 你的第二点不正确。无论您输入“...”、引发“NotImplementedError”还是简单地“pass”,对于尝试使用未实现的方法实例化抽象类(或子类)时引发的错误都没有影响。 (3认同)
  • 最好为抽象方法至少声明一个文档字符串,而不是使用“pass”或省略号(“...”)。皮林特告诉我的。 (3认同)

Jam*_*mes 13

自从最初提出这个问题以来,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
Run Code Online (Sandbox Code Playgroud)

这将强制派生类定义常量,否则TypeError当您尝试实例化子类时将引发异常。如果要将常量用于抽象类中实现的任何功能,则必须使用type(self).CONSTANT而不是just 访问子类常量CONSTANT,因为该值在基类中未定义。

还有其他方法可以实现此功能,但是我喜欢这种语法,因为对我来说,这似乎对读者来说是最简单和显而易见的。

先前的答案都触及了有用的观点,但是我认为被接受的答案并不能直接回答问题,因为

  • 该问题要求在抽象类中实现,但可接受的答案并不遵循抽象形式主义。
  • 问题要求实施是强制的。我认为在此答案中强制执行更为严格,因为如果CONSTANT未定义子类,则在实例化子类时会导致运行时错误。接受的答案允许实例化该对象,并且仅在CONSTANT访问该对象时抛出错误,从而使执行不太严格。

这不是对原始答案的错。自发布以来,对抽象类语法进行了重大更改,在这种情况下,它可以实现更整洁,更实用的实现。

  • 请注意,[从 Python 3.11 开始,堆叠 `@classmethod` 和 `@property` 似乎已被弃用](https://docs.python.org/3.11/library/functions.html#classmethod)(是的,它Python 3.9 中刚刚引入)。请参阅[关于另一个问题的答案](/sf/answers/4531719531/) 进行一些讨论和一些相关链接。 (9认同)
  • @BrianJoseph - `print_constant` 方法是为了说明如何访问父类中的常量,因为它仅在子类中定义 (4认同)
  • `self.CONSTANT` 也可以,而不是 `type(self).CONSTANT`。 (3认同)
  • 仅供参考:由于装饰器交互的方式,“@classmethod”应该位于“@property”之上 (3认同)
  • 包含`@classmethod`装饰器有什么意义?这不是没有必要吗? (2认同)

jon*_*ach 11

从 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'
                )
Run Code Online (Sandbox Code Playgroud)

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

  • @notnami 确实如此。但是,问题要求的是类变量,而不是在“__init__”方法期间设置的实例变量。 (3认同)
  • 嗯……是我还是你唯一严格回答这个问题的人?:) +1。 (3认同)

drh*_*gen 8

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

class Controller:
    path: str

class MyController(Controller):
    path = "/home"
Run Code Online (Sandbox Code Playgroud)

This makes for very clean code where it is obvious that the attribute is abstract. Code that tries to access the attribute when if has not been overwritten will raise an AttributeError.

  • @DaniTeba这个解决方案在访问时引发错误,而不是定义时。如果在类定义时引发错误对您很重要,则必须使用装饰器解决方案之一。 (13认同)
  • 这是不合适的。这些是完全不同类别的属性。“Controller.path”是类的一个属性,在实例之间共享(相当于“C#”中的“static”属性)。`MyController().path` 是实例的属性。 (7认同)

pho*_*nix 7

您可以在abc.ABC抽象基类中创建一个属性,其值NotImplemented如此,如果该属性未被覆盖然后使用,则会在运行时显示错误.

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

import abc


class Controller(abc.ABC):
    path = NotImplemented  # type: str


class MyController(Controller):
    path = '/home'
Run Code Online (Sandbox Code Playgroud)

  • 请注意,这不是实例字段。Scala 字段始终是类定义中的实例成员。因为Scala将静态和实例分开。 (2认同)

Juh*_*uh_ 5

您的基类可以实现一个__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
Run Code Online (Sandbox Code Playgroud)

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


Art*_*kov 5

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>
Run Code Online (Sandbox Code Playgroud)


dan*_*444 5

我对@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

Run Code Online (Sandbox Code Playgroud)