【问题标题】:Python abstract property() "Can't instantiate abstract class [] with abstract methods", but I didPython 抽象属性()“无法用抽象方法实例化抽象类 []”,但我做到了
【发布时间】:2020-12-09 20:56:25
【问题描述】:

我正在尝试在 python 3.7 中创建一个具有许多抽象 python 属性的基类。

我使用@property、@abstractmethod、@property.setter 注释尝试了一种方法(参见下面的“开始”)。这有效,但如果子类没有实现 setter,它不会引发异常。这就是我使用@abstract 的意义所在,所以这不好。

所以我尝试使用两个@abstractmethod 方法和一个'property()' 以另一种方式(参见下面的'end'),它本身不是抽象的,而是使用这些方法。这种方法在实例化子类时会产生错误:

        # {TypeError}Can't instantiate abstract class FirstStep with abstract methods end

我显然是在实现抽象方法,所以我不明白它的含义。 'end' 属性未标记为@abstract,但如果我将其注释掉,它会运行(但我没有得到我的属性)。我还添加了测试非抽象方法“test_elapsed_time”来证明我有正确的类结构和抽象(它有效)。

有没有可能我在做一些愚蠢的事情,或者 property() 周围有什么特殊行为导致了这种情况?

class ParentTask(Task):
    def get_first_step(self):
        # {TypeError}Can't instantiate abstract class FirstStep with abstract methods end
        return FirstStep(self)


class Step(ABC):
    #     __metaclass__ = ABCMeta
    def __init__(self, task):
        self.task = task

    # First approach.  Works, but no warnings if don't implement setter in subclass
    @property
    @abstractmethod
    def start(self):
        pass

    @start.setter
    @abstractmethod
    def start(self, value):
        pass

    # Second approach.  "This method for 'end' may look slight messier, but raises errors if not implemented.
    @abstractmethod
    def get_end(self):
        pass

    @abstractmethod
    def set_end(self, value):
        pass

    end = property(get_end, set_end)

    def test_elapsed_time(self):
        return self.get_end() - self.start


class FirstStep(Step):
    @property
    def start(self):
        return self.task.start_dt

    # No warnings if this is commented out.
    @start.setter
    def start(self, value):
        self.task.start_dt = value

    def get_end(self):
        return self.task.end_dt

    def set_end(self, value):
        self.task.end_dt = value

【问题讨论】:

  • 我无法在 3.7.4 或 3.8.5 上使用 start 重现您的问题。 Simplified example with just start, no *end, and an empty definition of FirstStep raises 我得到TypeError: Can't instantiate abstract class FirstStep with abstract methods start。这从 3.3 开始就有效。
  • 哦,我明白了。您定义了一个 getter 但没有定义 setter,并且您希望同时需要 getter 和 setter。无视我。
  • 是的@chepner 答案似乎解释了那部分。我还在努力消化剩下的。

标签: python python-3.x class oop abstract


【解决方案1】:

我怀疑这是抽象方法和属性交互中的错误。

在您的基类中,按顺序发生以下事情:

  1. 您定义了一个名为 start 的抽象方法。
  2. 您创建了一个使用 1) 中的抽象方法作为其 getter 的新属性。名称 start 现在指的是该属性,唯一指的是现在由 Self.start.fget 持有的原始名称。
  3. Python 保存了对 start.setter 的临时引用,因为名称 start 即将绑定到另一个对象。
  4. 您创建了第二个抽象方法,名为start
  5. 3) 中的引用被赋予了 4) 中的抽象方法来定义一个新属性来替换曾经绑定到名称 start 的新属性。该属性将 1 中的方法作为其 getter,并将 4 中的方法作为其设置器。现在start 指的是这个属性; start.fget 指 1) 中的方法; start.fset指的是4)中的方法。

此时,您有一个属性,其组件 函数是抽象方法。该属性本身没有被装饰为抽象,但property.__isabstractmethod__ 的定义将其标记为抽象,因为它的所有组件方法都是抽象的。更重要的是,您在Step.__abstractmethods__ 中有以下条目:

  1. startproperty
  2. endproperty
  3. set_endend 的设置器
  4. gen_endend 的吸气剂

请注意,start 属性的组件函数丢失了,因为__abstractmethods__ 存储名称,而不是对需要覆盖的事物的引用。使用 property 和结果属性的 setter 方法作为装饰器反复替换名称 start 所指的内容。

现在,在您的子类中,您定义了一个名为 startnew 属性,隐藏了继承的属性,该属性具有 no setter 和一个具体方法作为它的 getter .此时,您是否为此属性提供 setter 并不重要,因为就 abc 机器而言,您已经提供了它所要求的一切:

  1. 名称start的具体方法
  2. 名称get_endset_end 的具体方法
  3. 隐含名称 end 的具体定义,因为属性 end 的所有底层函数都已提供具体定义。

【讨论】:

  • The docs for abstractproperty (deprecated) 记录仅使用 @abstractmethod+@property 的替换方法。给出的示例直接说明属性可以是部分抽象的(例如,只是 setter,而不是示例中的 getter)并且您应该只需要覆盖 property 的那部分(暗示如果您有多个抽象组件,则必须覆盖所有抽象组件),它实际上并没有以这种方式运行的事实肯定构成了一个错误。
  • 显然有a closed bug for it。看起来我在那里读到的暗示并不存在;您可以通过引用父属性来覆盖单个元素,但是如果您创建自己的属性(而不是借用和覆盖父属性的位),它总是会满足 ABC 的要求,即使如果你忽略了重要的部分。
  • 接受了,因为我想我根据这个解释了解了这里发生的事情的要点。谢谢...并感谢@ShadowRanger 找到错误报告。
【解决方案2】:

@chepner 回答并解释得很好。基于此,我想出了一个解决方法,那就是......好吧......你决定。最好是偷偷摸摸。但它实现了我的 3 个主要目标:

  1. 为子类中未实现的 setter 引发异常
  2. 支持 python 属性语义(与函数等)
  3. 避免样板文件重新声明每个子类中的每个属性,这些属性可能仍然无法解决 #1。

只需在基类(而不是属性)中声明抽象的 get/set 函数。然后将@classmethod 初始化器添加到使用这些抽象方法创建实际属性的基类中,但此时它们将是子类上的具体方法。

它是在子类声明之后的一个衬里来初始化属性。没有什么可以强制执行该调用,因此它不是铁定的。在这个例子中节省不多,但我会有很多属性。最终结果看起来并不像我想象的那么脏。想听听我忽略的事物的 cmets 或警告。

from abc import abstractmethod, ABC

class ParentTask(object):
    def __init__(self):
        self.first_step = FirstStep(self)
        self.second_step = SecondStep(self)
        print(self.first_step.end)
        print(self.second_step.end)


class Step(ABC):
    def __init__(self, task):
        self.task = task

    @classmethod
    def init_properties(cls):
        cls.end = property(cls.get_end, cls.set_end)

    @abstractmethod
    def get_end(self):
        pass

    @abstractmethod
    def set_end(self, value):
        pass


class FirstStep(Step):
    def get_end(self):
        return 1

    def set_end(self, value):
        self.task.end = value


class SecondStep(Step):
    def get_end(self):
        return 2

    def set_end(self, value):
        self.task.end = value


FirstStep.init_properties()
SecondStep.init_properties()
ParentTask()

【讨论】:

    猜你喜欢
    • 2015-10-06
    • 2013-03-07
    • 2021-12-29
    • 2017-02-05
    • 2013-12-04
    • 2021-01-08
    • 2015-08-04
    • 1970-01-01
    相关资源
    最近更新 更多