Возможность задавать свойства у потомка абстрактного класса - PullRequest
0 голосов
/ 30 января 2019

Я боролся с этим в течение получаса, поэтому я сам попробовал правило полчаса и прошу вашей помощи.Я пытаюсь заставить Child перейти к абстрактному методу-установщику абстрактного класса, но он просто не будет работать ...

#!/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, он идет к сеттерам своего абстрактного класса Parent для db_ids и name, когда они назначаются.

Ответы [ 2 ]

0 голосов
/ 30 января 2019

Я читаю в 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)
0 голосов
/ 30 января 2019

Это на самом деле не имеет ничего общего с 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)

Поскольку метод получения и метод установки являются атрибутами экземпляра property, когда вы переопределяетесвойство в дочернем классе, вы не можете просто переопределить метод получения, вы также должны переопределить метод установки.

Распространенным примером здесь является делегирование метода получения и установки (если он есть) другому методу, поэтому вам не нужно переопределять все это, то есть:

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 без необходимости переопределять свойство.

Кроме того, применение property и abstractmethod к функции совершенно бесполезно.Это:

@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 перешел к методам установки класса Abstract для db_ids и name, когда они назначаются в конструкторе ValueHistorical

Тогда самое простое решение - этоодин, который я объяснил выше: определите методы реализации для метода получения и / или установки (вы можете сделать любой из них или оба абстрагировать, как считаете нужным) и использовать конкретное свойство для вызова этих методов реализации.

Oh ans yes: 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)]
...