You can use descriptors. Descriptors are, in layman's terms, reusable properties. The advantage over the __getattr__
and __setattr__
hooks is that you have more fine-grained control over what attributes are managed by descriptors.
class MyDescriptor:
def __init__(self, default='default'):
self.default = default
def __set_name__(self, owner, name): # new in Python3.6
self.name = name
def __get__(self, instance, owner):
print('getting {} on {}'.format(self.name, instance))
# your getter logic here
# dummy implementation:
if instance is not None:
try:
return vars(instance)[self.name]
except KeyError:
return self.default
return self
def __set__(self, instance, value):
print('setting {} on {}'.format(self.name, instance))
# your getter logic here
# dummy implementation:
vars(instance)[self.name] = value
class MyClass:
a = MyDescriptor()
b = MyDescriptor()
_id = 1
# some logic for demo __repr__
def __init__(self):
self.c = 'non-descriptor-handled'
self.id = MyClass._id
MyClass._id += 1
def __repr__(self):
return 'MyClass #{}'.format(self.id)
Demo:
>>> m1 = MyClass()
>>> m2 = MyClass()
>>> m1.c
'non-descriptor-handled'
>>> m1.a
getting a on MyClass #1
'default'
>>> m1.b
getting b on MyClass #1
'default'
>>> m1.b = 15
setting b on MyClass #1
>>> m1.b
getting b on MyClass #1
15
>>> m2.b
getting b on MyClass #2
'default'