Python abstract setters and getters - python

I want to write abstract class that will force inheriting classes to implement all methods AND properties in my abstract class.
Additionally I want to use of setters and getters for my abstract property to make my code uncluttered and looking nicely
However, current implementation:
import abc
class Component(metaclass=abc.ABCMeta):
#property
#abc.abstractmethod
def status(self):
pass
#property
#status.setter
#abc.abstractmethod
def status(self, value):
pass
does enforce inheriting class to implement getter for my abstract property getter, but does not enforce creating a property setter (what is exactly what I want)
How can I achieve this behavior without loosing all benefits from application of further mentioned method (aka writing new methods and executing them in my abstract class setter) ?

from abc import ABCMeta, abstractmethod
class Base(object):
__metaclass__ = ABCMeta
def __init__(self, val):
self._foo = val
#abstractmethod
def _doStuff(self, signals):
print ('Base does stuff')
#abstractmethod
def _get_foo(self):
return self._foo
#abstractmethod
def _set_foo(self, val):
self._foo = val + 'r'
foo = property(_get_foo, _set_foo)
class floor_1(Base):
__metaclass__ = ABCMeta
def __init__(self, val):
self._foo = val
super(floor_1, self).__init__(val)
def _doStuff(self, signals):
print ('floor_1 does stuff')
def _get_foo(self):
return self._foo
def _set_foo(self, val):
#self._foo = val + 'r'
super()._set_foo(val + 'r')
foo = property(_get_foo, _set_foo)
class floor_2(floor_1):
#property
def foo(self):
return self._foo
#foo.setter
def foo(self, val):
self._foo = val + 'r'
#super()._set_foo(val + 'r')
b1 = floor_1('bar')
# b1 = floor_2('bar')
print(b1.foo)
b1.foo = 'bar'
print(b1.foo)

The problem is that neither the getter nor the setter is a method of your abstract class; they are attributes of the property, which is a (non-callable) class attribute. Consider this equivalent definition:
def status_getter(self):
pass
def status_setter(self, value):
pass
class Component(metaclass=abc.ABCMeta):
# status = property(...)
# status.__isabstractmethod__ = True
status = abstractmethod(property(status_getter, status_setter))
Inheriting a property is quite different from inheriting a method. You are basically replacing the property, because your class itself does not have a reference to either the getter or the setter. Despite the name, abstractmethod does not actually make the property a method; it really does nothing more than add an attribute to whatever it is applied to and return the original value.
So, to ensure that a subclass provides a read/write property, what are you to do? Skip the decorator syntax, define the getter and setter as explicit abstract methods, then define the property explicitly in terms of those private methods.
class Component(metaclass=abc.ABCMeta):
#abstractmethod
def _get_status(self):
pass
#abstractmethod
def _set_status(self, v):
pass
status = property(lambda self: self._get_status(), lambda self, v: self._set_status(self, v))
Or, you can make use of __init_subclass__ (which postdates abc; its purpose is to allow class initialization that is otherwise only possible via a metaclass).
class Component:
def __init_subclass(cls, **kwargs):
super().__init_subclass__(**kwargs)
try:
p = cls.status
except AttributeError:
raise ValueError("Class does not define 'status' attribute")
if not isinstance(p, property):
raise ValueError("'status' is not a property")
if p.fget is None:
raise ValueError("'status' has no getter")
if p.fset is None:
raise ValueError("'status' has no setter")
This is actually an improvement over abc, in my opinion. If a subclass fails to define a read/write status property, an exception will be raised when the class is defined, not just when you attempt to instantiate the class.

Related

What is the conventional way in Python for defining attributes in an abstract base class? [duplicate]

In the following code, I create a base abstract class Base. I want all the classes that inherit from Base to provide the name property, so I made this property an #abstractmethod.
Then I created a subclass of Base, called Base_1, which is meant to supply some functionality, but still remain abstract. There is no name property in Base_1, but nevertheless python instatinates an object of that class without an error. How does one create abstract properties?
from abc import ABCMeta, abstractmethod
class Base(object):
# class Base(metaclass = ABCMeta): <- Python 3
__metaclass__ = ABCMeta
def __init__(self, str_dir_config):
self.str_dir_config = str_dir_config
#abstractmethod
def _do_stuff(self, signals):
pass
#property
#abstractmethod
def name(self):
"""This property will be supplied by the inheriting classes
individually.
"""
pass
class Base1(Base):
__metaclass__ = ABCMeta
"""This class does not provide the name property and should
raise an error.
"""
def __init__(self, str_dir_config):
super(Base1, self).__init__(str_dir_config)
# super().__init__(str_dir_config) <- Python 3
def _do_stuff(self, signals):
print "Base_1 does stuff"
# print("Base_1 does stuff") <- Python 3
class C(Base1):
#property
def name(self):
return "class C"
if __name__ == "__main__":
b1 = Base1("abc")
Since Python 3.3 a bug was fixed meaning the property() decorator is now correctly identified as abstract when applied to an abstract method.
Note: Order matters, you have to use #property above #abstractmethod
Python 3.3+: (python docs):
from abc import ABC, abstractmethod
class C(ABC):
#property
#abstractmethod
def my_abstract_property(self):
...
Python 2: (python docs)
from abc import ABC, abstractproperty
class C(ABC):
#abstractproperty
def my_abstract_property(self):
...
Until Python 3.3, you cannot nest #abstractmethod and #property.
Use #abstractproperty to create abstract properties (docs).
from abc import ABCMeta, abstractmethod, abstractproperty
class Base(object):
# ...
#abstractproperty
def name(self):
pass
The code now raises the correct exception:
Traceback (most recent call last):
File "foo.py", line 36, in
b1 = Base_1('abc')
TypeError: Can't instantiate abstract class Base_1 with abstract methods name
Based on James answer above
def compatibleabstractproperty(func):
if sys.version_info > (3, 3):
return property(abstractmethod(func))
else:
return abstractproperty(func)
and use it as a decorator
#compatibleabstractproperty
def env(self):
raise NotImplementedError()
In python 3.6+, you can also anotate a variable without providing a default. I find this to be a more concise way to make it abstract.
class Base():
name: str
def print_name(self):
print(self.name) # will raise an Attribute error at runtime if `name` isn't defined in subclass
class Base_1(Base):
name = "base one"
it may also be used to force you to initialize the variable in the __new__ or __init__ methods
As another example, the following code will fail when you try to initialize the Base_1 class
class Base():
name: str
def __init__(self):
self.print_name()
class Base_1(Base):
_nemo = "base one"
b = Base_1()
AttributeError: 'Base_1' object has no attribute 'name'
Using the #property decorator in the abstract class (as recommended in the answer by James) works if you want the required instance level attributes to use the property decorator as well.
If you don't want to use the property decorator, you can use super(). I ended up using something like the __post_init__() from dataclasses and it gets the desired functionality for instance level attributes:
import abc
from typing import List
class Abstract(abc.ABC):
"""An ABC with required attributes.
Attributes:
attr0
attr1
"""
#abc.abstractmethod
def __init__(self):
"""Forces you to implement __init__ in 'Concrete'.
Make sure to call __post_init__() from inside 'Concrete'."""
def __post_init__(self):
self._has_required_attributes()
# You can also type check here if you want.
def _has_required_attributes(self):
req_attrs: List[str] = ['attr0', 'attr1']
for attr in req_attrs:
if not hasattr(self, attr):
raise AttributeError(f"Missing attribute: '{attr}'")
class Concrete(Abstract):
def __init__(self, attr0, attr1):
self.attr0 = attr0
self.attr1 = attr1
self.attr2 = "some value" # not required
super().__post_init__() # Enforces the attribute requirement.
For example, you can define the abstract getter, setter and deleter with #abstractmethod and #property, #name.setter or #name.deleter in Person abstract class as shown below. *#abstractmethod must be the innermost decorator otherwise error occurs:
from abc import ABC, abstractmethod
class Person(ABC):
#property
#abstractmethod # The innermost decorator
def name(self): # Abstract getter
pass
#name.setter
#abstractmethod # The innermost decorator
def name(self, name): # Abstract setter
pass
#name.deleter
#abstractmethod # The innermost decorator
def name(self): # Abstract deleter
pass
Then, you can extend Person abstract class with Student class, override the abstract getter, setter and deleter in Student class, instantiate Student class and call the getter, setter and deleter as shown below:
class Student(Person):
def __init__(self, name):
self._name = name
#property
def name(self): # Overrides abstract getter
return self._name
#name.setter
def name(self, name): # Overrides abstract setter
self._name = name
#name.deleter
def name(self): # Overrides abstract deleter
del self._name
obj = Student("John") # Instantiates "Student" class
print(obj.name) # Getter
obj.name = "Tom" # Setter
print(obj.name) # Getter
del obj.name # Deleter
print(hasattr(obj, "name"))
Output:
John
Tom
False
Actually, even if you don't override the abstract setter and deleter in Student class and instantiate Student class as shown below:
class Student(Person): # Extends "Person" class
def __init__(self, name):
self._name = name
#property
def name(self): # Overrides only abstract getter
return self._name
# #name.setter
# def name(self, name): # Overrides abstract setter
# self._name = name
# #name.deleter
# def name(self): # Overrides abstract deleter
# del self._name
obj = Student("John") # Instantiates "Student" class
# ...
No error occurs as shown below:
John
Tom
False
But, if you don't override the abstract getter, setter and deleter in Student class and instantiate Student class as shown below:
class Student(Person): # Extends "Person" class
def __init__(self, name):
self._name = name
# #property
# def name(self): # Overrides only abstract getter
# return self._name
# #name.setter
# def name(self, name): # Overrides abstract setter
# self._name = name
# #name.deleter
# def name(self): # Overrides abstract deleter
# del self._name
obj = Student("John") # Instantiates "Student" class
# ...
The error below occurs:
TypeError: Can't instantiate abstract class Student with abstract methods name
And, if you don't override the abstract getter in Student class and instantiate Student class as shown below:
class Student(Person): # Extends "Person" class
def __init__(self, name):
self._name = name
# #property
# def name(self): # Overrides only abstract getter
# return self._name
#name.setter
def name(self, name): # Overrides abstract setter
self._name = name
#name.deleter
def name(self): # Overrides abstract deleter
del self._name
obj = Student("John") # Instantiates "Student" class
# ...
The error below occurs:
NameError: name 'name' is not defined
And, if #abstractmethod is not the innermost decorator as shown below:
from abc import ABC, abstractmethod
class Person(ABC):
#abstractmethod # Not the innermost decorator
#property
def name(self): # Abstract getter
pass
#name.setter
#abstractmethod # The innermost decorator
def name(self, name): # Abstract setter
pass
#name.deleter
#abstractmethod # The innermost decorator
def name(self): # Abstract deleter
pass
The error below occurs:
AttributeError: attribute 'isabstractmethod' of 'property' objects is not writable
Another possible solution is to use metaclasses.
A minimal example can look like this:
class BaseMetaClass(type):
def __new__(mcls, class_name, bases, attrs):
required_attrs = ('foo', 'bar')
for attr in required_attrs:
if not attr in attrs:
raise RunTimeError(f"You need to set {attr} in {class_name}")
return super().__new__(mcls, class_name, bases, attrs)
class Base(metaclass=BaseMeta):
foo: str
bar: int
One advantage of this approach is that the check will happen at definition time (not instantiation).
Also, setting class attributes in child classes is a bit easier than declaring properties (as long as they are simple values known in advance) and your final classes will look more concise

Ability to set properties in the child of an abstract class

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.
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)]
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)

Delegation design pattern with abstract methods in python

I have the following classes implementing a "Delegation Design Pattern" with an additional DelegatorParent class:
class DelegatorParent():
def __init__(self):
self.a = 'whatever'
class ConcreteDelegatee():
def myMethod(self):
return 'myMethod'
class Delegator(DelegatorParent):
def __init__(self):
self.delegatee = ConcreteDelegatee()
DelegatorParent.__init__(self)
def __getattr__(self, attrname):
return getattr(self.delegatee, attrname)
a = Delegator()
result = a.myMethod()
Everything looks fine.
Now I would like to put an abstract method in DelegatorParent, to ensure that "myMethod" is always defined.
from abc import ABCMeta, abstractmethod
class DelegatorParent():
__metaclass__ = ABCMeta
#abstractmethod
def myMethod(self):
pass
def __init__(self):
self.a = 'whatever'
class ConcreteDelegatee():
def myMethod(self):
return 'myMethod'
class Delegator(DelegatorParent):
def __init__(self):
self.delegatee = ConcreteDelegatee()
DelegatorParent.__init__(self)
def __getattr__(self, attrname):
return getattr(self.delegatee, attrname)
# This method seems unnecessary, but if I erase it an exception is
# raised because the abstract method's restriction is violated
def myMethod(self):
return self.delegatee.myMethod()
a = Delegator()
result = a.myMethod()
Can you help me find an "elegant" way to remove "myMethod" from "Delegator"... Intuition tells me that it is somehow redundant (considering that a custom getattr method is defined).
And more importantly, notice that with this implementation, if I forget to define myMethod in ConcreteDelegatee the program compiles, but it may crash in runtime if I call Delegator.myMethod(), which is exactly what I wanted to avoid by using abstract methods in DelegatorParent.
Obviously a simple solution would be to move #abstractmethod to the Delegator class, but I want to avoid doing that because in my program DelegatorParent is a very important class (and Delegator is just an auxiliary class).
You can decide to automatically implement abstract methods delegared to ConcreteDelegatee.
For each abstract method, check if it's name exist in the ConcreteDelegatee class and implement this method as a delegate to this class method.
from abc import ABCMeta, abstractmethod
class DelegatorParent(object):
__metaclass__ = ABCMeta
def __init__(self):
self.a = 'whatever'
#abstractmethod
def myMethod(self):
pass
class Delegatee(object):
pass
class ConcreteDelegatee(Delegatee):
def myMethod(self):
return 'myMethod'
def myMethod2(self):
return 'myMethod2'
class Delegator(DelegatorParent):
def __new__(cls, *args, **kwargs):
implemented = set()
for name in cls.__abstractmethods__:
if hasattr(ConcreteDelegatee, name):
def delegated(this, *a, **kw):
meth = getattr(this.delegatee, name)
return meth(*a, **kw)
setattr(cls, name, delegated)
implemented.add(name)
cls.__abstractmethods__ = frozenset(cls.__abstractmethods__ - implemented)
obj = super(Delegator, cls).__new__(cls, *args, **kwargs)
obj.delegatee = ConcreteDelegatee()
return obj
def __getattr__(self, attrname):
# Called only for attributes not defined by this class (or its bases).
# Retrieve attribute from current behavior delegate class instance.
return getattr(self.delegatee, attrname)
# All abstract methods are delegared to ConcreteDelegatee
a = Delegator()
print(a.myMethod()) # correctly prints 'myMethod'
print(a.myMethod2()) #correctly prints 'myMethod2'
This solves the main problem (prevent ConcreteDelegatee from forgetting to define myMethod). Other abstract methods are still checked if you forgot to implement them.
The __new__ method is in charge of the delegation, that frees your __init__ to do it.
Since you use ABCMeta, you must defined the abstract methods. One could remove your method from the __abstractmethods__ set, but it is a frozenset. Anyway, it involves listing all abstract methods.
So, instead of playing with __getattr__, you can use a simple descriptor.
For instance:
class Delegated(object):
def __init__(self, attrname=None):
self.attrname = attrname
def __get__(self, instance, owner):
if instance is None:
return self
delegatee = instance.delegatee
return getattr(delegatee, self.attrname)
class Delegator(DelegatorParent):
def __init__(self):
self.delegatee = ConcreteDelegatee()
DelegatorParent.__init__(self)
myMethod = Delegated('myMethod')
An advantage here: the developer has the explicit information that "myMethod" is delegated.
If you try:
a = Delegator()
result = a.myMethod()
It works! But if you forget to implement myMethod in Delegator class, you have the classic error:
Traceback (most recent call last):
File "script.py", line 40, in <module>
a = Delegator()
TypeError: Can't instantiate abstract class Delegator with abstract methods myMethod
Edit
This implementation can be generalized as follow:
class DelegatorParent():
__metaclass__ = ABCMeta
#abstractmethod
def myMethod1(self):
pass
#abstractmethod
def myMethod2(self):
pass
def __init__(self):
self.a = 'whatever'
class ConcreteDelegatee1():
def myMethod1(self):
return 'myMethod1'
class ConcreteDelegatee2():
def myMethod2(self):
return 'myMethod2'
class DelegatedTo(object):
def __init__(self, attrname):
self.delegatee_name, self.attrname = attrname.split('.')
def __get__(self, instance, owner):
if instance is None:
return self
delegatee = getattr(instance, self.delegatee_name)
return getattr(delegatee, self.attrname)
class Delegator(DelegatorParent):
def __init__(self):
self.delegatee1 = ConcreteDelegatee1()
self.delegatee2 = ConcreteDelegatee2()
DelegatorParent.__init__(self)
myMethod1 = DelegatedTo('delegatee1.myMethod1')
myMethod2 = DelegatedTo('delegatee2.myMethod2')
a = Delegator()
result = a.myMethod2()
Here, we can specify the delegatee name and delegatee method.
Here is my current solution. It solves the main problem (prevent ConcreteDelegatee from forgetting to define myMethod), but I'm still not convinced because I still need to define myMethod inside Delegator, which seems redundant
from abc import ABCMeta, abstractmethod
class DelegatorParent(object):
__metaclass__ = ABCMeta
def __init__(self):
self.a = 'whatever'
#abstractmethod
def myMethod(self):
pass
class Delegatee(object):
def checkExistence(self, attrname):
if not callable(getattr(self, attrname, None)):
error_msg = "Can't instantiate " + str(self.__class__.__name__) + " without abstract method " + attrname
raise NotImplementedError(error_msg)
class ConcreteDelegatee(Delegatee):
def myMethod(self):
return 'myMethod'
def myMethod2(self):
return 'myMethod2'
class Delegator(DelegatorParent):
def __init__(self):
self.delegatee = ConcreteDelegatee()
DelegatorParent.__init__(self)
for method in DelegatorParent.__abstractmethods__:
self.delegatee.checkExistence(method)
def myMethod(self, *args, **kw):
return self.delegatee.myMethod(*args, **kw)
def __getattr__(self, attrname):
# Called only for attributes not defined by this class (or its bases).
# Retrieve attribute from current behavior delegate class instance.
return getattr(self.delegatee, attrname)
# if I forget to implement myMethod inside ConcreteDelegatee,
# the following line will correctly raise an exception saying
# that 'myMethod' is missing inside 'ConcreteDelegatee'.
a = Delegator()
print a.myMethod() # correctly prints 'myMethod'
print a.myMethod2() #correctly prints 'myMethod2'

Python Class Name as Class Variable

I'm working as an application with classes and subclasses. For each class, both super and sub, there is a class variable called label. I would like the label variable for the super class to default to the class name. For example:
class Super():
label = 'Super'
class Sub(Super):
label = 'Sub'
Rather than manually type out the variable for each class, is it possible to derive the variable from the class name in the super class and have it automatically populated for the subclasses?
class Super():
label = # Code to get class name
class Sub(Super)
pass
# When inherited Sub.label == 'Sub'.
The reason for this is that this will be the default behavior. I'm also hoping that if I can get the default behavior, I can override it later by specifying an alternate label.
class SecondSub(Super):
label = 'Pie' # Override the default of SecondSub.label == 'SecondSub'
I've tried using __name__, but that's not working and just gives me '__main__'.
I would like to use the class variable label in #classmethod methods. So I would like to be able to reference the value without having to actually create a Super() or Sub() object, like below:
class Super():
label = # Magic
#classmethod
def do_something_with_label(cls):
print(cls.label)
you can return self.__class__.__name__ in label as a property
class Super:
#property
def label(self):
return self.__class__.__name__
class Sub(Super):
pass
print Sub().label
alternatively you could set it in the __init__ method
def __init__(self):
self.label = self.__class__.__name__
this will obviously only work on instantiated classes
to access the class name inside of a class method you would need to just call __name__ on the cls
class XYZ:
#classmethod
def my_label(cls):
return cls.__name__
print XYZ.my_label()
this solution might work too (snagged from https://stackoverflow.com/a/13624858/541038)
class classproperty(object):
def __init__(self, fget):
self.fget = fget
def __get__(self, owner_self, owner_cls):
return self.fget(owner_cls)
class Super(object):
#classproperty
def label(cls):
return cls.__name__
class Sub(Super):
pass
print Sub.label #works on class
print Sub().label #also works on an instance
class Sub2(Sub):
#classmethod
def some_classmethod(cls):
print cls.label
Sub2.some_classmethod()
You can use a descriptor:
class ClassNameDescriptor(object):
def __get__(self, obj, type_):
return type_.__name__
class Super(object):
label = ClassNameDescriptor()
class Sub(Super):
pass
class SecondSub(Super):
label = 'Foo'
Demo:
>>> Super.label
'Super'
>>> Sub.label
'Sub'
>>> SecondSub.label
'Foo'
>>> Sub().label
'Sub'
>>> SecondSub().label
'Foo'
If class ThirdSub(SecondSub) should have ThirdSub.label == 'ThirdSub' instead of ThirdSub.label == 'Foo', you can do that with a bit more work. Assigning label at the class level will be inherited, unless you use a metaclass (which is a lot more hassle than it's worth for this), but we can have the label descriptor look for a _label attribute instead:
class ClassNameDescriptor(object):
def __get__(self, obj, type_):
try:
return type_.__dict__['_label']
except KeyError:
return type_.__name__
Demo:
>>> class SecondSub(Super):
... _label = 'Foo'
...
>>> class ThirdSub(SecondSub):
... pass
...
>>> SecondSub.label
'Foo'
>>> ThirdSub.label
'ThirdSub'
A metaclass might be useful here.
class Labeller(type):
def __new__(meta, name, bases, dct):
dct.setdefault('label', name)
return super(Labeller, meta).__new__(meta, name, bases, dct)
# Python 2
# class Super(object):
# __metaclass__ = Labeller
class Super(metaclass=Labeller):
pass
class Sub(Super):
pass
class SecondSub(Super):
label = 'Pie'
class ThirdSub(SecondSub):
pass
Disclaimer: when providing a custom metaclass for your class, you need to make sure it is compatible with whatever metaclass(es) are used by any class in its ancestry. Generally, this means making sure your metaclass inherits from all the other metaclasses, but it can be nontrivial to do so. In practice, metaclasses aren't so commonly used, so it's usually just a matter of subclassing type, but it's something to be aware of.
As of Python 3.6, the cleanest way to achieve this is with __init_subclass__ hook introduced in PEP 487. It is much simpler (and easier to manage with respect to inheritance) than using a metaclass.
class Base:
#classmethod
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if 'label' not in cls.__dict__: # Check if label has been set in the class itself, i.e. not inherited from any of its superclasses
cls.label = cls.__name__ # If not, default to class's __name__
class Sub1(Base):
pass
class Sub2(Base):
label = 'Custom'
class SubSub(Sub2):
pass
print(Sub1.label) # Sub1
print(Sub2.label) # Custom
print(SubSub.label) # SubSub

How to call a property of the base class if this property is being overwritten in the derived class?

I'm changing some classes of mine from an extensive use of getters and setters to a more pythonic use of properties.
But now I'm stuck because some of my previous getters or setters would call the corresponding method of the base class, and then perform something else. But how can this be accomplished with properties? How to call the property getter or setter in the parent class?
Of course just calling the attribute itself gives infinite recursion.
class Foo(object):
#property
def bar(self):
return 5
#bar.setter
def bar(self, a):
print a
class FooBar(Foo):
#property
def bar(self):
# return the same value
# as in the base class
return self.bar # --> recursion!
#bar.setter
def bar(self, c):
# perform the same action
# as in the base class
self.bar = c # --> recursion!
# then do something else
print 'something else'
fb = FooBar()
fb.bar = 7
You might think you could call the base class function which is called by property:
class FooBar(Foo):
#property
def bar(self):
# return the same value
# as in the base class
return Foo.bar(self)
Though this is the most obvious thing to try I think - it does not work because bar is a property, not a callable.
But a property is just an object, with a getter method to find the corresponding attribute:
class FooBar(Foo):
#property
def bar(self):
# return the same value
# as in the base class
return Foo.bar.fget(self)
super() should do the trick:
return super().bar
In Python 2.x you need to use the more verbose syntax:
return super(FooBar, self).bar
There is an alternative using super that does not require to explicitly reference the base class name.
Base class A:
class A(object):
def __init__(self):
self._prop = None
#property
def prop(self):
return self._prop
#prop.setter
def prop(self, value):
self._prop = value
class B(A):
# we want to extend prop here
pass
In B, accessing the property getter of the parent class A:
As others have already answered, it's:
super(B, self).prop
Or in Python 3:
super().prop
This returns the value returned by the getter of the property, not the getter itself but it's sufficient to extend the getter.
In B, accessing the property setter of the parent class A:
The best recommendation I've seen so far is the following:
A.prop.fset(self, value)
I believe this one is better:
super(B, self.__class__).prop.fset(self, value)
In this example both options are equivalent but using super has the advantage of being independent from the base classes of B. If B were to inherit from a C class also extending the property, you would not have to update B's code.
Full code of B extending A's property:
class B(A):
#property
def prop(self):
value = super(B, self).prop
# do something with / modify value here
return value
#prop.setter
def prop(self, value):
# do something with / modify value here
super(B, self.__class__).prop.fset(self, value)
One caveat:
Unless your property doesn't have a setter, you have to define both the setter and the getter in B even if you only change the behaviour of one of them.
try
#property
def bar:
return super(FooBar, self).bar
Although I'm not sure if python supports calling the base class property. A property is actually a callable object which is set up with the function specified and then replaces that name in the class. This could easily mean that there is no super function available.
You could always switch your syntax to use the property() function though:
class Foo(object):
def _getbar(self):
return 5
def _setbar(self, a):
print a
bar = property(_getbar, _setbar)
class FooBar(Foo):
def _getbar(self):
# return the same value
# as in the base class
return super(FooBar, self)._getbar()
def bar(self, c):
super(FooBar, self)._setbar(c)
print "Something else"
bar = property(_getbar, _setbar)
fb = FooBar()
fb.bar = 7
Some small improvements to Maxime's answer:
Using __class__ to avoid writing B. Note that self.__class__ is the runtime type of self, but __class__ without self is the name of the enclosing class definition. super() is a shorthand for super(__class__, self).
Using __set__ instead of fset. The latter is specific to propertys, but the former applies to all property-like objects (descriptors).
class B(A):
#property
def prop(self):
value = super().prop
# do something with / modify value here
return value
#prop.setter
def prop(self, value):
# do something with / modify value here
super(__class__, self.__class__).prop.__set__(self, value)
You can use the following template:
class Parent():
def __init__(self, value):
self.__prop1 = value
#getter
#property
def prop1(self):
return self.__prop1
#setter
#prop1.setter
def prop1(self, value):
self.__prop1 = value
#deleter
#prop1.deleter
def prop1(self):
del self.__prop1
class Child(Parent):
#getter
#property
def prop1(self):
return super(Child, Child).prop1.__get__(self)
#setter
#prop1.setter
def prop1(self, value):
super(Child, Child).prop1.__set__(self, value)
#deleter
#prop1.deleter
def prop1(self):
super(Child, Child).prop1.__delete__(self)
Note! All of the property methods must be redefined together. If do not want to redefine all methods, use the following template instead:
class Parent():
def __init__(self, value):
self.__prop1 = value
#getter
#property
def prop1(self):
return self.__prop1
#setter
#prop1.setter
def prop1(self, value):
self.__prop1 = value
#deleter
#prop1.deleter
def prop1(self):
del self.__prop1
class Child(Parent):
#getter
#Parent.prop1.getter
def prop1(self):
return super(Child, Child).prop1.__get__(self)
#setter
#Parent.prop1.setter
def prop1(self, value):
super(Child, Child).prop1.__set__(self, value)
#deleter
#Parent.prop1.deleter
def prop1(self):
super(Child, Child).prop1.__delete__(self)
class Base(object):
def method(self):
print "Base method was called"
class Derived(Base):
def method(self):
super(Derived,self).method()
print "Derived method was called"
d = Derived()
d.method()
(that is unless I am missing something from your explanation)

Categories

Resources