2

I'm wondering, what is the best practices to handle abstract property type validation?

from abc import ABC, abstractmethod

class Base(ABC):

    @property
    @abstractmethod
    def name(self):
       """
       :type str
       """
       pass

class MyClass(Base):
    name = 1    # TypeError
domin_1990
  • 25
  • 4
  • An abstract method should have no function body inside. But you seemed trying to put some logic there. – DumTux Feb 13 '20 at 16:02
  • 1
    @HotteShen Abstract doesn't imply unimplemented; it just means it needs to be overriden. It's fine to provide code which might be called via `super`. – chepner Feb 13 '20 at 16:30
  • Property function can be implemented with one line of code and only in that way. Then, why need to be re-implemented in sub classes? – DumTux Feb 13 '20 at 16:33
  • @HotteShen, actually I'm just asking whether there is such possibility or more like how to handle this case;) chepner served nice solution for this problem without adding any logic to property. Answering your question: "why need to be re-implemented in sub classes?" I want to get something like abstractpropertie which was available in python 2.7 as I know. – domin_1990 Feb 14 '20 at 09:57

2 Answers2

0

Not so simple, but it can be done like this:

from abc import ABC, abstractmethod

class Base(ABC):

    @property
    @abstractmethod
    def name(self):
        raise NotImplementedError("To be provided in derived classes")

class AClass(Base):
    def __init__(self):
        self._name = ""

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

    @name.setter
    def name(self, value):
        if type(value) is not str:
            raise ValueError("Only string is allowed")
        self._name = value

a = AClass()
a.name = "hello" # no error
a.name = 1       # raises a value error

DumTux
  • 668
  • 1
  • 9
  • 24
  • I think it is not exactly what I was looking for. In this case it is instance's property not a class property. – domin_1990 Feb 14 '20 at 09:41
0

Use __init_subclass__. You don't name to be a property at all, abstract or otherwise.

class Base:

    def __init_subclass__(cls, *args, **kwargs):
       super().__init_subclass__(**kwargs)
       if not isinstance(cls.name, str):
           raise TypeError(f'{cls.name} is not a string value')


class MyClass(Base):
    name = 1
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Nice! Really like your solution but could you also explain me when to use your approach to force implementation of properties/methods instead abstract class (except type validation possibility)?? Any best practices? – domin_1990 Feb 14 '20 at 09:48
  • I'm not sure I understand your question. Your original example was about a regular class attribute, not a property or method. You can think of `__init_subclass__` as just a way to examine the class someone creates after inheriting from you. After `MyClass` is created, but before moving on to the next line of code, `Base.__init_subclass__` is called to ensure that `cls` (in this case `MyClass`) passes any checks you wish to make. – chepner Feb 14 '20 at 12:07
  • Yes, u are right I meant class attribute not property, but I guess I can do the same with class methods to examine that someone create it, am I right? Anyway thanks for help! – domin_1990 Feb 14 '20 at 14:43
  • Yes; really, all methods are class attributes. It's the descriptor protocol that makes `a.foo()` behave like `A.foo(a)` (because if `A.foo` has a `__get__` method, then `a.foo` evaluates to `A.foo.__get__(a, A)`). – chepner Feb 14 '20 at 14:47
  • And the thing `__get__` returns remembers that `a` was passed to `__get__`, so when it gets called, it just calls `A.foo` with `a` and its own arguments. – chepner Feb 14 '20 at 14:53
  • To sum up, assuming that we want to just examine the class that someone create the attribute. Is there any reason to use __init_subclass__ over using abstract class and abstract decorator? – domin_1990 Feb 15 '20 at 12:57
  • @Premkumarchalmeti Not sure what you mean. If you try something like `class C1(Base): name = "foo"; class C2(C1): name = 1`, then `Base.__init_subclass__` correctly flags `C2.name` as invalid. (Unless `C1.__init_subclass__` is defined and fails to invoke `super().__init_subclass__`, but that's another problem altogether.) – chepner Jul 06 '22 at 14:26