1

I am working on a data analysis application in Python. I have a DataAttributes class that provides various details of a dataset. However, in obtaining some of these values, it uses another set of methods that directly modifies the data - my DataManipulator class.

To keep things simple for adding future methods to each class, I want to be able to reset the dataset to its original state AFTER running a method from DataAttributes. So this involves:

  • Storing the initial state of the dataset
  • Running a method from an instance of type DataAttributes
  • Resetting dataset to initial state

Of course I could accomplish this by running a method at the beginning and end of each DataAttributes method, but that feels extremely clunky, especially given that there are dozens of methods for obtaining data attributes. What is a clean way of implementing this? I have looked into making a metaclass, but am unsure of how to modify instance variables using that approach. I've also looked into decorators, but am not sure if that's best either.

In terms of an example of what I want to achieve, I want to do what is done below, but for "n" methods. Also, this is a vast simplification of what I'm working on - let's just pretend I have a good reason for what I'm doing, I know that conceptually, this example makes little sense. It's just meant to demonstrate what I want to achieve.

class MyClass():
    def __init__(self):
        self.mydata = np.array([1, 2, 3])
        self.my_initial_data = np.array([1, 2, 3])

    def store_initial_data(self):
        self.my_initial_data = np.array([1, 2, 3])

    def reset_data(self):
        self.mydata = self.my_initial_data
        print(self.mydata)

    def method_1(self):
        self.store_initial_data()
        self.mydata *= 2
        self.reset_data()

    def method_2(self):
        self.store_initial_data()
        self.mydata *= 2
        self.reset_data()
    .
    .
    .

    def method_n(self):
        self.store_initial_data()
        self.mydata *= 2
        self.reset_data()

my_inst = MyClass()
my_inst.method_1() # prints [1, 2, 3]
7
  • 1
    Could you do all of your operations on copies of data, without mutating the originals? Commented Dec 23, 2019 at 20:24
  • You can create an array of function handlers and call them in a for loop. Commented Dec 23, 2019 at 20:40
  • I could do this, but it would involve adding an additional default parameter in my DataManipulator class (since this design of that class is to manipulate the actual data object and plot it). Possible for sure, but it would be an ugly solution. Commented Dec 23, 2019 at 20:41
  • @Seyfi I'm not super familiar with function handlers, could you go into a little more detail? Commented Dec 23, 2019 at 20:43
  • Have a look at stackoverflow.com/questions/46793112/… Commented Dec 23, 2019 at 20:53

1 Answer 1

1

I think the easiest thing there is to customize attribute access so that when a method is retrieved from the instance, it is wrapped in code that will do the setup and clean-up tasks.

If you just have a pattern for your methods, so that they can be recognized, it can become foolproof. In the example bellow I do it for every callable retrieved from the instance, unless its name starts with a _ - that will avoid special methods, and any method you don't want to wrap can just be prefixed with a _.

This solution just needs a baseclass - no metaclass necessary:


def wrap(method, prefix, postfix):
    def wrapper(*args, **kw):
        prefix(*args, **kw)
        result = method(*args, **kw)
        postfix(*args, **kw)
        return result
    return wrapper


class SetupCleanupBase:

    def __getattribute__(self, name):
        attr = super().__getattribute__(name)
        if not name.startswith("_") and callable(attr):
            attr = wrap(attr, self._setup, self._cleanup)
        return attr 


class MyClass(SetupCleanupBase):

    ...

    def _setup(self):
        self.my_initial_data = np.array([1, 2, 3])

    def _cleanup(self):
        self.mydata = self.my_initial_data
        print(self.mydata)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.