【问题标题】:Restore Python class to original state将 Python 类恢复到原始状态
【发布时间】:2010-10-10 07:42:06
【问题描述】:

我有一个类,我在其中动态添加一些属性,并且在某些时候我想在没有添加属性的情况下将该类恢复到原始状态。

情况:

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__

【问题讨论】:

  • 请问您为什么觉得这很有用?
  • 因为我有一个类,当导入模块时,我会在其中动态添加带有默认前缀的方法(例如,在所有添加的内容前添加“m_”)。用户可能想要使用其他前缀,所以我需要“重新初始化”类。
  • 这听起来是个非常糟糕的想法。
  • @Glenn Maynard:这听起来像是一个非常没有建设性的评论,绝不会增加讨论。
  • 这是不言而喻的,抱怨我觉得没有必要解释一些似乎不言而喻的事情是非常没有建设性的,绝不会增加讨论。也许你不应该对刚刚回答你问题的人发牢骚。

标签: python


【解决方案1】:

我同意 Glenn 的观点,即这是一个非常糟糕的想法。无论如何,这里你将如何使用装饰器。还要感谢 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

【讨论】:

    【解决方案2】:

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

    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】:

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

      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

      【讨论】:

      • 我同意。您不应该修改外部类。这是一个修改自身的类。最终用户不应自行更改任何方法。方法的添加或多或少是透明的。需要更改前缀的可能性来保证类的未来,并且通常不需要(这些方法是根据外部数据添加的)。另一种方法是不使用默认前缀设置类,而是强制用户每次都明确指定它。这会导致用户编写更多代码、更多出错的可能性以及通常更烦人的行为。
      • 您应该安排在加载模块之前配置可用的配置,以便您可以设置正确的方法名称开头,或者使用__getattr__ 方法动态处理方法名称。但是,如果您真的想设置方法,则将它们删除并重新设置它们——那么您就是这样做的。如果您确定这些方法不存在,那么您可以简单地删除它们,而不必担心存储原始状态。
      • 或者,我想,你可以完全忽略这个答案。 (欢迎来到标准溢出。)
      • @pafcu:为每个要跟踪的添加属性设置一个外部变量是一个糟糕的解决方案,无法很好地扩展,并且需要知道所有原始属性的名称以及添加的所有名称属性。
      【解决方案4】:

      我发现的最简单的方法是这样的:

      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:正如cmets中指出的那样,实际上并不重置类,但在实践中具有相同的效果。

      【讨论】:

      • 这并没有将类恢复到它的原始值。这是一个全新的同名类。
      • @AaronMcSmooth:当然,但它具有我想要的所有属性。对于外部用户来说没有区别。
      • 创建修改后的Foo 类的实例。用“干净”版本替换 Foo 类。旧实例仍然具有新属性。这根本不是一回事。甚至不在球场上。
      • @AaronMcSmooth:我真的不在乎那个案子。这恰好适用于我的情况,因为我不需要更改旧实例(示例中也没有旧实例),但我同意这不是一个正确的通用解决方案,这就是为什么我不会接受这个解决方案作为“正确”回答我的问题。我只是在这里添加了这个解决方案作为替代方案。
      【解决方案5】:

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

      Foo = _Foo # 参考

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

      Foo = _Foo()

      #!/usr/bin/python

      类FooClass(对象): 通过

      FooInstance = FooClass() # 创建实例

      FooInstance.x = 100 # 修改实例

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

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

      打印 FooInstance.x # 异常

      【讨论】:

      【解决方案6】:

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

      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 想要的,虽然这真的很不寻常。

      【讨论】:

      • 我强烈建议避免在生产代码中使用reload。您不是在恢复同一个对象,而是在旧名称上加载一个新对象。在重新加载之前对该类的任何引用和实例都会导致损坏。
      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

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

        您可以为类提供一个__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")
        

        【讨论】:

        • 我想我应该更清楚我希望整个过程尽可能透明,即用户不需要知道类是如何实现的。走 getattr 路线会导致许多问题,例如缺乏内省,这会使用户感到困惑。
        【解决方案9】:

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

        【讨论】:

          【解决方案10】:

          要创建类的深层副本,您可以使用 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

          【讨论】:

          • 这些是旧式课程,您的建议仅适用于此类课程。不推荐使用的不仅仅是模块,而是您提出的整个该死的方案。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-02-04
          • 2023-03-25
          • 2021-09-09
          • 2013-08-29
          • 1970-01-01
          • 2011-04-06
          相关资源
          最近更新 更多