Reputation: 44295
I am defining a base class in python like
class Base(object):
def __init__(self):
self._changed = False
and some derived classes:
class Car(Base):
def set_type(self, type_):
# do something
def set_mileage(self, mileage):
# do something
class Flower(base):
def set_name(self, name):
# do something
In this example I now want to set the attribute '_changed' to True
whenever I call a set
method of one of the derived classes. I simply could add the line
self._changed = True
to every set
method, or use a decorator, but I am looking for a more convenient and automatic way to do this whenever a method is called whose name starts with 'set_'. I am thinking using __getattribute__
like in the following not tried (and not working example:
def __getattribute__(self, name):
if name.startswith('set_'):
self._changed = True
return self.__getattribute__(name)
So how to implement this in the correct way?
Upvotes: 1
Views: 68
Reputation: 250881
Update: A fully working example this time using a metaclass and descriptor with both setter and a getter:
class Field(object):
def __get__(self, ins, type):
return getattr(ins, self.field_name, None)
def __set__(self, ins, val):
setattr(ins, self.field_name, val)
ins._changed = True
class Meta(type):
def __new__(cls, clsname, bases, dct):
for k, v in dct.items():
if isinstance(v, Field):
v.field_name = '_' + k
return type.__new__(cls, clsname, bases, dct)
class Base(object):
__metaclass__ = Meta
def __init__(self):
self._changed = False
class Car(Base):
type = Field()
mileage = Field()
class Flower(Base):
name = Field()
Demo:
>>> c = Car()
>>> c._changed
False
>>> c.type = "4X4"
>>> c._changed
True
>>> c1 = Car()
>>> c1._changed
False
>>> c1.mileage = 100
>>> c1._changed
True
>>> c.type
'4X4'
>>> c1.mileage
100
>>> f = Flower()
>>> f._changed
False
>>> f.name = "Rose"
>>> f._changed
True
>>> f.name
'Rose'
Upvotes: 2
Reputation: 94871
A metaclass would work here:
from types import FunctionType
from functools import wraps
class Setter(type):
def __new__(cls, clsname, bases, dct):
for item in dct:
if item.startswith("set_") and isinstance(dct[item], FunctionType):
dct[item] = cls.changer(dct[item])
return super(Setter, cls).__new__(cls, clsname, bases, dct)
@staticmethod
def changer(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
self._changed = True
return func(self, *args, **kwargs)
return wrapper
class Base(object):
__metaclass__ = Setter
def __init__(self):
self._changed = False
Then just inherit from Base like you normally would.
Sample usage:
>>> from meta import Car
>>> c = Car()
>>> c._changed
False
>>> c.set_type("blah")
ok
>>> c._changed
True
The metaclass is just automatically decorating any method in your class' __dict__
that starts with set_
.
Upvotes: 0
Reputation: 336
I would use a decorator for this. Something like this (not tested):
def isGet(func):
def newFunc(self, var):
self._changed = True
func(self, var)
return
return newFunc
And then in any get method you want this behaviour, you simply do
@isGet
def set_mileage(self, mileage):
# dosomething
Upvotes: 0