Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Restore Python class to original state

Tags:

python

I have a class where I add some attributes dynamically and at some point I want to restore the class to it's pristine condition without the added attributes.

The situation:

class Foo(object):
  pass

Foo.x = 1
# <insert python magic here>
o = Foo() # o should not have any of the previously added attributes
print o.x # Should raise exception

My initial thought was to create a copy of the original class:

class _Foo(object):
  pass

Foo = _Foo
Foo.x = 1
Foo = _Foo # Clear added attributes
o = Foo()
print o.x # Should raise exception

But since Foo is just a reference to _Foo any attributes get added to the original _Foo as well. I also tried

Foo = copy.deepcopy(_Foo)

in case that would help but apparently it does not.

clarification:

The user should not need to care about how the class is implemented. It should, therefore, have the same features of a "normally defined" class, i.e. introspection, built-in help, subclassing, etc. This pretty much rules out anything based on __getattr__

like image 441
pafcu Avatar asked Oct 10 '10 07:10

pafcu


3 Answers

I agree with Glenn that this is a horribly broken idea. Anyways, here how you'd do it with a decorator. Thanks to Glenn's post as well for reminding me that you can delete items from a class's dictionary, just not directly. Here's the code.

def resetable(cls):
    cls._resetable_cache_ = cls.__dict__.copy()
    return cls

def reset(cls):
    cache = cls._resetable_cache_ # raises AttributeError on class without decorator
    for key in [key for key in cls.__dict__ if key not in cache]:
        delattr(cls, key)
    for key, value in cache.items():  # reset the items to original values
        try:
            setattr(cls, key, value)
        except AttributeError:
            pass

I'm torn on whether to reset the values by catching attempts to update non-assignable attributes with a try as I've shown or building a list of such attributes. I'll leave it up to you.

And here's a use:

@resetable   # use resetable on a class that you want to do this with
class Foo(object):
    pass

Foo.x = 1
print Foo.x
reset(Foo)
o = Foo() 
print o.x # raises AttributeError as expected
like image 109
aaronasterling Avatar answered Sep 20 '22 04:09

aaronasterling


You can use inspect and maintain an original list of members and than delete all members that are not in the original list

import inspect
orig_members = []
for name, ref in inspect.getmembers(o):
  orig_members.append(name)
...

Now, when you need to restore back to original

for name, ref in inspect.getmembers(o):
  if name in orig_members:
    pass
  else:
    #delete ref here
like image 23
molicule Avatar answered Sep 22 '22 04:09

molicule


You have to record the original state and restore it explicitly. If the value existed before you changed it, restore that value; otherwise delete the value you set.

class Foo(object):
  pass

try:
    original_value = getattr(Foo, 'x')
    originally_existed = True
except AttributeError:
    originally_existed = False

Foo.x = 1

if originally_existed:
    Foo.x = original_value
else:
    del Foo.x

o = Foo() # o should not have any of the previously added attributes
print o.x # Should raise exception

You probably don't want to be doing this. There are valid cases for monkey patching, but you generally don't want to try to monkey unpatch. For example, if two independent bits of code monkey patch the same class, one of them trying to reverse the action without being aware of the other is likely to break things. For an example of a case where this is actually useful, see https://stackoverflow.com/questions/3829742#3829849.

like image 35
Glenn Maynard Avatar answered Sep 18 '22 04:09

Glenn Maynard