Alex
Alex

Reputation: 44295

How to call a baseclass function whenever a certain method is called in derived class in python?

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 Truewhenever 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

Answers (3)

Ashwini Chaudhary
Ashwini Chaudhary

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

dano
dano

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

ipinyol
ipinyol

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

Related Questions