Python 摘要 属性() "Can't instantiate abstract class [] with abstract methods",但我做到了

Python abstract property() "Can't instantiate abstract class [] with abstract methods", but I did

我正在尝试在 python 3.7.

中创建一个具有许多抽象 python 属性的基础 class

我使用 @属性、@abstractmethod、@property.setter 注释尝试了一种方法(请参阅下面的 'start')。这行得通,但如果 subclass 没有实现 setter,它不会引发异常。这就是我使用@abstract 的意义所在,所以这不好。

所以我尝试用另一种方式(参见下面的 'end')使用两个 @abstractmethod 方法和一个 'property()',它本身不是抽象的,但使用了这些方法。这种方法在实例化 subclass:

时会产生错误
        # {TypeError}Can't instantiate abstract class FirstStep with abstract methods end

我明明是在实现抽象方法,所以我不明白这是什么意思。 'end' 属性 未标记为@abstract,但如果我将其注释掉,它会 运行 (但我没有得到我的 属性)。我还添加了测试非抽象方法 'test_elapsed_time' 以证明我拥有 class 结构和抽象权(有效)。

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

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

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

在您的基地 class 中,会依次发生以下事情:

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

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

  1. start, property
  2. end, property
  3. set_end,setter为end
  4. gen_end,getter为end

请注意,缺少 start 属性 的组件函数,因为 __abstractmethods__ 存储 的名称,而不是对事物的引用需要被覆盖。使用 property 和由此产生的 属性 的 setter 方法作为装饰器重复替换名称 start 所指的内容。

现在,在您的 child class 中,您定义了一个名为 startnew 属性,隐藏继承的 属性,它有没有 setter和一个具体的方法作为它的getter。在这一点上,你是否为这个 属性 提供一个 setter 并不重要,因为就 abc 机器而言,你已经提供了它所要求的一切:

  1. 名字的具体方法start
  2. 名称get_endset_end
  3. 的具体方法
  4. 名称 end 的隐式具体定义,因为 属性 end 的所有基础函数都已提供具体定义。

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

  1. 为 subclasses
  2. 中未实现的设置器引发异常
  3. 支持 python 属性 语义(相对于函数等)
  4. 避免样板文件重新声明每个子class中的每个属性,这仍然可能无法解决#1。

只需在基 class(而不是 属性)中声明抽象 get/set 函数。然后将 @class 方法初始值设定项添加到基础 class 中,它使用这些抽象方法创建实际属性,但到那时,它们将成为 subclass 上的具体方法。

它是在 subclass 声明之后的一行来初始化属性。没有任何东西强制执行该调用,因此它不是铁定的。在这个例子中并没有很大的节省,但我会有很多属性。最终结果看起来并不像我想象的那么脏。想听听对我忽略的事情的评论或警告。

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