开发者

Restore Python class to original state

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 except开发者_JAVA百科ion

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__


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


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


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.


The simplest way I found was this:

def foo_maker():
    class Foo(object):
        pass
    return Foo
Foo = foo_maker()
Foo.x = 1
Foo = foo_maker() # Foo is now clean again
o = Foo() # Does not have any of the previously added attributes
print o.x # Raises exception

edit: As pointed out in comments, does not actually reset class but has the same effect in practice.


In your second example you're making a reference to the class rather than an instance.

Foo = _Foo # Reference

If you instead made an instance copy, what you want to do is exactly the way it will work. You can modify the instance all you want and 'revert' it by creating a new instance.

Foo = _Foo()

#!/usr/bin/python

class FooClass(object): pass

FooInstance = FooClass() # Create an instance

FooInstance.x = 100 # Modify the instance

print dir(FooClass) # Verify FooClass doesn't have an 'x' attribute

FooInstance = FooClass() # Creates a new instance

print FooInstance.x # Exception


I don't know if you can accept an additional module file for class, if you can:

my_class.py

class Foo(object):
  pass

You main script:

import my_class  
Foo = my_class.Foo
Foo.x = 1
p = Foo()
print p.x # Printing '1'

# Some code....

reload(my_class) # reload to reset
Foo = my_class.Foo
o = Foo()
print p.x # Printing '1'
print o.__class__ == p.__class__ # Printing 'False'
print o.x # Raising exception

I am not sure if there is any side-effect. It seems to do what OP wants, though this is really unusal.


I don't fully understand why you need this, but I'll have a go. Ordinary inheritance probably won't do because you want to 'reset' to the old state. How about a proxy pattern?

class FooProxy(object):
    def __init__(self, f):
        self.f = foo
        self.magic = {}

    def set_magic(self, k, v):
        self.magic[k] = v

    def get_magic(self, k):
        return self.magic.get(k)

    def __getattr__(self, k):
        return getattr(self.f, k)

    def __setattr__(self, k, v):
        setattr(self.f, k, v)

    f = Foo() 
    p = FooProxy(f)
    p.set_magic('m_bla', 123)

use f for ordinary, 'original' access, use p for proxied access, it should behave mostly like Foo. Re-proxy f with new configuration if you need to


I don't understand what you are trying to do, but keep in mind that you don't have to add attributes to the class in order to make it look like you added attributes to the class.

You can give the class a __getattr__ method that will be invoked for any missing attribute. Where it gets the value from is up to you:

class MyTrickyClass(object):
    self.magic_prefix = "m_"
    self.other_attribute_source = SomeOtherObject()

    def __getattr__(self, name):
        if name.startswith(self.magic_prefix):
            stripped = name[len(self.magic_prefix):]
            return getattr(self.other_attribute_source, stripped)
        raise AttributeError

m = MyTrickyClass()
assert hasattr(m, "m_other")
MyTrickyClass.magic_prefix = "f_"
assert hasattr(m, "f_other")


If all the stuff you added starts with a given distinctive prefix, you could search the object's __dict__ for members with that prefix, and delete them, when it's time to restore.


To create a deep copy of a class you can use the new.classobj function

class Foo:
    pass

import new, copy
FooSaved = new.classobj(Foo.__name__, Foo.__bases__, copy.deepcopy(Foo.__dict__))

# ...play with original class Foo...

# revert changes
Foo = FooSaved

UPD: module new is deprecated. Instead you should use types.ClassType with the same args

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜