2

I'm trying to achieve the following:

class Data(object):
    def __init__(self, data):
        self.data = data
        self.valid = False
        #Analyze and validate data
        self.preprocess_data()
        self.validate_data()

    def preprocess_data():
        pass

    def validate_data():
        #process data

class MyData(Data):
    def __init__():
        super(MyData,self).__init__(data)

    def preprocess_data(self):
        #preprocess it

When a subclass executes the overriden preprocess_data method, I want to automatically perform the following operation: self.data = self.data.copy()

How can this be done (if at all)? I thought about decorating preprocess but I don't think that overridden methods that are decorated in the base class inherit the "decoration"

sebastian
  • 4,914
  • 2
  • 21
  • 21

4 Answers4

2
class Data(object):
    def __init__(self, data):
        self.data = data
        self.valid = False
        #Analyze and validate data
        self._preprocess_data()
        self.validate_data()
    def _preprocess_data(self):
        if self.preprocess_data.im_func != Data.preprocess_data.im_func:
            self.data = self.data.copy()
        return self.preprocess_data()

This tests if the function behind self.preprocess_data is Data.preprocess_data; if not it copies your data. Of course this requires you to call _preprocess_data in your class so the additional code is actually executed.

ThiefMaster
  • 310,957
  • 84
  • 592
  • 636
1

This can be trivially solved with a decorator and a simple metaclass. This is a little hacky, but does exactly what you asked for.

import functools

class DataMeta(type):
    def __new__(cls, name, bases, dictn):
        fn = dictn.get('preprocess_data')
        if fn:
            if getattr(fn, '_original', False) is False:
                @functools.wraps(fn)
                def wrapper(self, *args, **kwargs):
                    self.data = self.data.copy()
                    return fn(self, *args, **kwargs)
                dictn['preprocess_data'] = wrapper
        return type.__new__(cls, name, bases, dictn)

def base_method(fn):
    fn._original = True
    return fn

class Data(object):
    __metaclass__ = DataMeta

    def __init__(self, data):
        self.data = data
        self.valid = False
        #Analyze and validate data
        self.preprocess_data()
        self.validate_data()

    @base_method
    def preprocess_data(self):
        print "Original preprocess_data called"

    def validate_data(self):
        pass

class MyData(Data):
    def __init__(self, data):
        super(MyData, self).__init__(data)

    def preprocess_data(self):
        print "Overridden preprocess_data called"

class MyData1(Data):
    def __init__(self, data):
        super(MyData1, self).__init__(data)

class Dummy(object):
    def copy(self):
        print 'Copying data'

md = MyData(Dummy()) # Prints 'Copying data'
md1 = MyData1(Dummy()) # Doesn't print it, since MyData1 doesn't override preprocess_data
Jeethu
  • 429
  • 2
  • 4
1

I would vote for @ThiefMaster's way of doing things. If you want to complicate your life, you can use metaclasses with decorators:

from functools import wraps

class PreprocessMetaclass(type): 
    def __new__(cls, name, bases, dct):

        try:
            if Data in bases and "preprocess_data" in dct:
                f = dct["preprocess_data"]
                @wraps(f)
                def preprocess_data(self, *args, **kwargs):
                    self.data = self.data.copy()
                    return f(self, *args, **kwargs)

                attrs = dct.copy()
                attrs["preprocess_data"] = preprocess_data
        except NameError as e:
            # This is if Data itself is being created, just leave it as it is.
            # Raises an NameError, because Data does not exist yet in "if Data in bases"
            attrs = dct

        return super(PreprocessMetaclass, cls).__new__(cls, name, bases, attrs)


class Data(object):
    # This here works if the subclasses don't specify
    # their own metaclass that isn't a subclass of the above.
    __metaclass__ = PreprocessMetaclass

    def __init__(self, data):
        self.data = data
        self.valid = False
        #Analyze and validate data
        self.preprocess_data()
        self.validate_data()

    def preprocess_data(self):
        self.data["newkey"] = 3

    def validate_data(self):
        print "This is the result: self.data =", self.data

class MyData(Data):
    def __init__(self, data):
        super(MyData,self).__init__(data)

    def preprocess_data(self):
        """The docs of the subclass"""
        self.data["newkey"] = 4

if __name__ == "__main__":
    dct1, dct2 = {"data": 1}, {"mydata": 2}

    print "Create Data"
    d = Data(dct1)

    print "Create MyData"
    md = MyData(dct2)

    print "The original dict of Data (changed):", dct1
    print "The original dict of MyData (unchanged):", dct2

    # If you do that, you will see that the docstring is still there,
    # but the arguments are no longer the same.
    # If that is needed (you don't have arguments, but the subclass might have)
    # then just enter the same arguments as the subclass's function
    # help(MyData)

PS It's the first time I ever needed to use metaclasses, but here it's the perfect scenario. You need to override a function definition before the class is created (before __init__). Now, you might not need it, but more generally, you might be forced to use that.

An even more simple example:

if not (self.preprocess_data is Data.preprocess_data):
    self.data = self.data.copy()
self.preprocess_data()
Community
  • 1
  • 1
jadkik94
  • 7,000
  • 2
  • 30
  • 39
  • jadkik94, thanks for the metaclass + decorator approach. For now, Im gonna opt for simplicity and adopt @ThiefMaster's solution. It's always good to know other alternatives though. Thanks!!! Also, I like your last suggestion. – sebastian May 25 '12 at 18:16
  • You're welcome, I hope you'll make use of it at some point in time: personally, it's the first time I found metaclasses useful. You might consider using it if it gets much more complex. – jadkik94 May 25 '12 at 19:19
0

How about simply:

class Data(object):

    # Part of user API
    def preprocess_data():
        self.data = self.data.copy()
        self.preprocess_data_impl()

    # Part of internal API    
    def preprocess_data_impl():
        pass

class MyData(Data):

    def preprocess_data_impl():
        # Do stuff

I know this is not as glamorous as using a decorator, but makes it very easy to follow what actually happens.

NPE
  • 486,780
  • 108
  • 951
  • 1,012
  • Hi @aix, If you look at my `__init__ ` in the base class Data, I'm implementing a template for the validation process (preprocess first, then validate.) My intent is that all subclasses call this code. If the subclasses implement `preprocess_data`, then I want to `self.data.copy()`. Otherwise I do **not** want to copy it. – sebastian May 25 '12 at 17:05