将Python类还原到原始状态

时间:2010-10-10 07:42:06

标签: python

我有一个类,我动态添加一些属性,在某些时候我想将类恢复到原始状态而没有添加属性。

情况:

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

我最初的想法是创建原始类的副本:

class _Foo(object):
  pass

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

但由于Foo只是对_Foo的引用,因此任何属性都会添加到原始的_Foo中。我也试过

Foo = copy.deepcopy(_Foo)

如果有帮助但显然没有。

澄清

用户不需要关心如何实现类。因此,它应该具有“正常定义”类的相同特征,即内省,内置帮助,子类化等。这几乎排除了基于__getattr__

的任何内容。

10 个答案:

答案 0 :(得分:5)

我同意格伦的观点,这是一个非常糟糕的想法。无论如何,这里你是如何与装饰师一起做的。感谢Glenn的帖子以及提醒我你可以从类字典中删除项目,而不是直接删除。这是代码。

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

我是否通过捕获尝试使用try更新不可分配的属性(如我所示)或构建此类属性的列表来重置值。我会留给你的。

这是一个用途:

@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

答案 1 :(得分:3)

您必须记录原始状态并明确还原。如果在更改之前存在该值,则恢复该值;否则删除你设置的值。

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

你可能不想这样做。猴子补丁有一些有效的案例,但你通常不想尝试猴子 unpatch 。例如,如果两个独立的代码位修补相同的类,其中一个试图在不知道另一个的情况下反转动作可能会破坏事物。有关此实际有用的案例,请参阅https://stackoverflow.com/questions/3829742#3829849

答案 2 :(得分:1)

您可以使用检查和维护原始成员列表,然后删除不在原始列表中的所有成员

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

现在,当您需要恢复原来的

for name, ref in inspect.getmembers(o):
  if name in orig_members:
    pass
  else:
    #delete ref here

答案 3 :(得分:0)

我不完全明白为什么你需要这个,但我会去。普通继承可能不会这样做,因为你想“重置”到旧状态。 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)

将f用于普通的“原始”访问,使用p进行代理访问,它的行为应该与Foo类似。如果需要

,请使用新配置重新代理f

答案 4 :(得分:0)

我不知道你是否可以为课程接受额外的模块文件,如果可以的话:

my_class.py

class Foo(object):
  pass

您的主要脚本:

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

我不确定是否有任何副作用。它似乎做了OP想要的,虽然这真的很不寻常。

答案 5 :(得分:0)

在你的第二个例子中,你是在引用类而不是实例。

Foo = _Foo#Reference

如果您改为制作实例副本,那么您想要做的就是它的工作方式。您可以根据需要修改实例,并通过创建新实例来“恢复”它。

Foo = _Foo()

#!的/ usr / bin中/ Python的

class FooClass(object):       通

FooInstance = FooClass()#创建一个实例

FooInstance.x = 100#修改实例

print dir(FooClass)#Verify FooClass没有'x'属性

FooInstance = FooClass()#创建一个新实例

print FooInstance.x#Exception

答案 6 :(得分:0)

我不明白你要做什么,但请记住,你不必为类添加属性,以使看起来像你添加属性到类。

您可以为类提供一个__getattr__方法,该方法将针对任何缺失的属性进行调用。它取得的价值取决于你:

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")

答案 7 :(得分:0)

如果您添加的所有内容都以给定的独特前缀开头,则可以在对象的__dict__中搜索具有该前缀的成员,并在需要恢复时删除它们。

答案 8 :(得分:0)

我找到的最简单的方法是:

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

编辑:正如评论中指出的那样,实际上并没有重置课程,但在实践中却有同样的效果。

答案 9 :(得分:-1)

要创建类的深层副本,您可以使用new.classobj函数

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:不推荐使用模块new。相反,你应该使用types.ClassType使用相同的args