-1

I've been battling with this for half an hour, so I have passed the try it yourself for half an hour rule and am asking for your help. I am trying to get the Child go the abstract class's setter abstract method, but it just won't work...

#!/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'})

arguments here don't matter. And the error I get is AttributeError: can't set the attribute'.

What I want to achieve is inside of ValueHistorical constructor it goes to it's Parent's abstract class's setters for db_ids and name when those are being assigned.

Naz
  • 2,012
  • 1
  • 21
  • 45
  • If I execute this I get `Traceback (most recent call last): File "main.py", line 64, in ValueHistorical(**{'date_from': '2010-01-01', 'date_to': '2012-01-01'}) TypeError: Can't instantiate abstract class ValueHistorical with abstract methods calculate` ... why are you not providing the full stacktrace? Why are you not Implementing `calculate`? Why wonder about downvotes if you cull half the information from the error message? Did you try to implement `calculate` ? – Patrick Artner Jan 30 '19 at 15:28
  • sorry. forgot about that method – Naz Jan 30 '19 at 15:29
  • added just the pass – Naz Jan 30 '19 at 15:29
  • 2
    now I get `NameError: name 'DataHandler' is not defined` .... this is the **third Thing** I need to fix to help you out - which kindof is against [mcve] nothing of that has to do with your original error I presume? - None of the dv are by me btw. – Patrick Artner Jan 30 '19 at 15:31
  • ... sorry. should be good now – Naz Jan 30 '19 at 15:32
  • I have even added shebang for you kind sirs, just don't hate pls – Naz Jan 30 '19 at 15:38
  • 1
    We don't hate - it is just a waste of our time if you provide code with errors beyond what needs to be fixed. – Patrick Artner Jan 30 '19 at 15:39
  • I thought it was OK, I didn't check. Pardon me for wasting your time. Lesson learned. – Naz Jan 30 '19 at 15:41

2 Answers2

3

This has actually nothing to do with ABC, but with the fact that you rebound the properties in your child class, but without setters. This:

class ValueHistorical(Indicator):

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

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

Just replaces the parent's properties with the new ones, but defining those properties as read-only since you didn't provide a setter.

Remember that the decorator syntax is only syntactic sugar, so this:

@property
def getter(...): pass

is just a fancier way to write

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

Since the getter AND the setter are attributes of the property instance, when you redefine a property in a child class, you cannot just redefine the getter, you must also redefine the setter.

A common pattern here is to have the getter and setter (if there's one) delegating to another method, so you don't have to reimplement the whole thing, ie:

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):
        # ...

So a child class can override _get_foo and / or _set_foo without having to redefine the property.

Also, applying both property and abstractmethod to a function is totally useless. This:

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

is the equivalent of

def db_ids(self):
    return self._db_ids

db_ids = property(abstractmethod(db_ids))

So what ABC will see here is the property - the fact that it's getter (and/or setter) have been decorated with abstractmethod is ignored, ABC will not inspect the propertie's getter and setter. And if you put them the other way round ie

db_ids = abstractmethod(property(db_ids))

then you don't define a property at all (actually, it will not work at all - you'll get an exception right from the start with " 'property' object has no attribute 'isabstractmethod'")

FWIW, the abstractmethod decorator is only meant to be used on methods that are NOT defined (empty body) so the child classes must implement them. If you have a default implementation, don't mark it as abstract, else why provide a default implementation at all ?

EDIT:

You mentionned in a comment (on a deleted answer) that:

I basically want ValueHistorical to go to the Abstract class's setter methods for db_ids and name when they are being assigned in the ValueHistorical constructor

Then the simplest solution is the one I explained above: define implementation methods for the getter and/or setter (you can make any of them or both abstract as you see fit) and use a concrete property to call those implementation methods.

Oh ans yes: assert is a developper tool, don't use it for typechecking in production code. If you really want to do typecheking (which sometimes makes sense but is most often than not a complete waste of time), use isinstance and raise a TypeError. As an example, your db_ids setter should look like this:

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

Or even better:

    # 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)]
bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • Bruno, thank you for this very thorough answer. I will have a read through it later today. Thank you – Naz Jan 30 '19 at 16:41
  • how do you ensure types are correct across the code without typechecking/isinstancing them? – Naz Jan 31 '19 at 12:47
  • 1
    You don't. You just document what types (or implied interfaces) are expected and leave it to the calling code to pass the proper types (which actually means: anything that actually behave as expected - what class it belongs to being mostly irrelevant). This might seems frightening if you come from the BDSM-static-checking side of the force, but it actually JustWorks(tm). There _are_ cases where it makes sense to validate your inputs (type and/or value), but that's mostly at subsystems boundaries (user inputs => controller code, model code => persistance layer etc). – bruno desthuilliers Jan 31 '19 at 12:58
  • I added a simple example of 1/ how to typecheck when you __really__ have to (which is: very seldom actually), and 2/ how to make sure you have usable data without typechecking anything. – bruno desthuilliers Jan 31 '19 at 13:07
1

I read in https://pymotw.com/2/abc/

To use the decorator syntax does with read/write abstract properties, the methods to get and set the value should be named the same.

Don't think there's any way you can do this without requiring the setter. But IMO it's cleaner than using the super class setter logic with 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)
repoleved
  • 744
  • 6
  • 11
  • The code snippet that follows this mentions uses `abstractproperty`, not `property` stacked upon `abstractmethod`. – bruno desthuilliers Jan 30 '19 at 16:14
  • @brunodesthuilliers well noticed, and as you mentioned in your answer, decorating with `abstractmethod` is useless. Will edit my answer for completeness, but clearly yours should be accepted – repoleved Jan 30 '19 at 16:16
  • impressive stuff repoleved – Naz Jan 30 '19 at 16:41