能够在抽象类的子级中设置属性

时间:2019-01-30 15:16:35

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

我已经为此奋斗了半个小时,所以我自己通过了尝试了半个小时的规则,并寻求您的帮助。我正在尝试让Child使用抽象类的setter抽象方法,但那样行不通...

#!/usr/bin/env python3
from abc import ABC, abstractmethod
from typing import List

class Indicator(ABC):
    def __init__(self, **kwargs):
        super().__init__()
        pass

    @abstractmethod
    def calculate(self):
        """
        kwargs in children will most likely be date_from, date_to, index
        """
        raise NotImplementedError("The calculate method is not implemented!")

    @property
    @abstractmethod
    def db_ids(self):
        return self._db_ids

    @db_ids.setter
    @abstractmethod
    def db_ids(self, ids: List[int]):
        assert isinstance(ids, list)
        assert all(isinstance(id_, int) for id_ in ids)
        self._db_ids = ids

    @property
    @abstractmethod
    def name(self):
        return self._name

    @name.setter
    @abstractmethod
    def name(self, set_name: str):
        assert isinstance(set_name, str)
        self._name = set_name

# …………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………


class ValueHistorical(Indicator):
    def __init__(self, **kwargs):
        if kwargs:
            self.kwargs = kwargs
            super(ValueHistorical, self).__init__(**kwargs)

        self.db_ids = [119, 120, 121, 122]
        self.name = 'Value Based on Historical'

    @property
    def db_ids(self):
        return self._db_ids

    @property
    def name(self):
        return self._name

    def calculate(self):
        pass

ValueHistorical(**{'date_from': '2010-01-01', 'date_to': '2012-01-01'})

这里的参数无关紧要。我得到的错误是AttributeError: can't set the attribute'

我想要实现的是在ValueHistorical构造函数中,它是分配给db_id和名称的父级抽象类的设置器。

2 个答案:

答案 0 :(得分:3)

这实际上与ABC无关,但事实是您反弹了子类中的属性,但没有设置器。这个:

class ValueHistorical(Indicator):

    @property
    def db_ids(self):
        return self._db_ids

    @property
    def name(self):
        return self._name

只需用新的属性替换父级的属性,但由于未提供设置器,因此将这些属性定义为只读。

请记住,装饰器语法只是语法糖,所以:

@property
def getter(...): pass

只是一种更好的写作方式

def getter(...): pass
getter = property(getter)

由于getter和setter是property实例的属性,因此,当您在子类中重新定义属性时,您不仅可以重新定义getter,还必须重新定义setter。

这里常见的模式是让getter和setter(如果有的话)委派给另一种方法,因此您不必重新实现整个过程,即:

class Base(object):

    @property
    def foo(self):
        return self._get_foo()

    @foo.setter
    def foo(self, value):
        self._set_foo(value)

    def _get_foo(self):
        # ...

    def _set_foo(self, value):
        # ...

因此子类可以重写_get_foo和/或_set_foo,而不必重新定义属性。

此外,将propertyabstractmethod都应用到一个函数完全没有用。这个:

@property
@abstractmethod
def db_ids(self):
    return self._db_ids

等于

def db_ids(self):
    return self._db_ids

db_ids = property(abstractmethod(db_ids))

因此,ABC在这里看到的是该属性-它的吸气剂(和/或二传手)已用abstractmethod装饰的事实将被忽略,ABC将不会检查属性的吸气剂和二传手。如果把它们反过来,即

db_ids = abstractmethod(property(db_ids))

然后,您根本就不定义属性(实际上,它根本不起作用-从头开始,您将获得一个异常,因为“'property”对象没有属性' isabstractmethod '“)

FWIW,abstractmethod装饰器仅可用于未定义的方法(空主体),因此子类必须实现它们。如果您有默认实现,请不要将其标记为抽象,否则为什么要完全提供默认实现?

编辑:

您在评论中(关于已删除的答案)提到:

  

当在ValueHistorical构造函数中为其分配db_ids和name时,我基本上希望ValueHistorical转到Abstract类的setter方法

然后最简单的解决方案就是上面我解释过的解决方案:定义getter和/或setter的实现方法(您可以根据需要将它们中的任何一个或两者抽象化),并使用具体的属性来调用这些实现方法。

哦,是的:assert是开发人员工具,请勿将其用于生产代码中的类型检查。如果您真的想要进行打字检查(这有时很有意义,但通常并非完全浪费时间),请使用isinstance并提出一个TypeError。例如,您的db_ids设置器应如下所示:

    if not isinstance(ids, list):
        raise TypeError("ids should be a list")

    if not all(isinstance(id_, int) for id_ in ids)
        raise TypeError("ids items should be ints")

甚至更好:

    # you don't care if it really was a list actually, 
    # as long as you can build a list out of it, and
    # you don't care if it really contains ints as long
    # as you can build ints out of them.
    #
    # No need for typecheck here, if `ids` is not iterable
    # or what it yields cannot be used to build an int, 
    # this will raise, with way enough informations to
    # debug the caller.

    ids = [int(id) for id in ids)]

答案 1 :(得分:1)

我读了https://pymotw.com/2/abc/

  

要使用装饰器语法处理读/写抽象属性,获取和设置值的方法应命名为相同。

不要认为没有任何方法可以不需要设置者。但是IMO比将超类设置逻辑与fset

结合使用更干净
from abc import ABC, abstractmethod, abstractproperty
from typing import List

class Indicator(ABC):
    def __init__(self, **kwargs):
        super().__init__()

    @abstractproperty
    def db_ids(self):
        return self._db_ids

    @db_ids.setter
    @abstractmethod
    def db_ids(self, ids: List[int]):
        self._db_ids = ids

class ValueHistorical(Indicator):
    def __init__(self, **kwargs):
        if kwargs:
            self.kwargs = kwargs
            super(ValueHistorical, self).__init__(**kwargs)

        self.db_ids = [119, 120, 121, 122]
    @property
    def db_ids(self):
        return self._db_ids

    @db_ids.setter
    def db_ids(self, ids: List[int]):
        self._db_ids = ids

i = ValueHistorical(**{'date_from': '2010-01-01', 'date_to': '2012-01-01'})

print(i.db_ids)