【问题标题】:How to assign member variables temporarily?如何临时分配成员变量?
【发布时间】:2016-11-26 16:14:08
【问题描述】:

我经常发现我需要临时分配一些成员变量,例如

old_x = c.x
old_y = c.y
# keep c.z unchanged

c.x = new_x
c.y = new_y

do_something(c)

c.x = old_x
c.y = old_y

但我希望我可以简单地写

with c.x = new_x; c.y = new_y:
    do_something(c)

甚至

do_something(c with x = new_x; y = new_y)

Python 的装饰器或其他语言特性可以启用这种模式吗? (我可以根据需要修改c的类)

【问题讨论】:

  • 为什么不新建一个实例?
  • @HaifengZhang c 可能有许多其他变量没有被修改
  • 好调用@HaifengZhang,除非do_something(c) 分配给c 的其他属性。
  • @MaxB 创建一个包含您修改的实例 c 属性的新类,并将c 用于其他任务。

标签: python python-2.7 python-decorators python-descriptors


【解决方案1】:

Context managers 可以轻松使用。

引用官方文档:

上下文管理器的典型用途包括保存和恢复各种 各种全局状态,锁定和解锁资源,关闭打开 文件等

看起来保存和恢复状态正是我们想要在这里做的。

例子:

from contextlib import contextmanager


@contextmanager
def temporary_change_attributes(something, **kwargs):
    previous_values = {k: getattr(something, k) for k in kwargs}
    for k, v in kwargs.items():
        setattr(something, k, v)
    try:
        yield
    finally:
        for k, v in previous_values.items():
            setattr(something, k, v)


class Something(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def say_hello(self):
        print("hello", self.x, self.y)


s = Something(1, 2)
s.say_hello()  # hello 1 2
with temporary_change_attributes(s, x=4, y=5):
    s.say_hello()  # hello 4 5
s.say_hello()  # hello 1 2

【讨论】:

  • 这是一个很好的答案,但您可能希望将 contextmanager 的内部封装在 try/finally
【解决方案2】:

我认为contextmanager 应该做你想做的事:

from contextlib import contextmanager

@contextmanager
def current_instance(c, temp_x, temp_y):
    old_x, old_y = c.x, c.y
    c.x, c.y = temp_x, temp_y
    yield c
    c.x, c.y = old_x, old_y

with current_instance(c, x, y) as c_temp:
    do_something(c_temp) 

【讨论】:

    【解决方案3】:

    您也可以使用 __enter____exit__ 在本地执行此操作。简单的例子:

    class SomeObject(object):
        def __init__(self):
            self.a = 1
            self.b = 2
            self.c = 3
    
    class Temporary(object):
        def __init__(self, target, **kv):
            self.target = target
            self.to_set = kv
            self.to_restore = {}
    
        def __enter__(self):
            self.to_restore = map(partial(getattr, self.target), filter(partial(hasattr, self.target), self.to_set.keys()))
            for k,v in self.to_set.items():
                if hasattr(self.target, k):
                    self.to_restore[k] = getattr(self.target, k)
                setattr(self.target, k, v)
    
        def __exit__(self, *_):
            for k,v in self.to_restore.items():
                setattr(self.target, k, v)
            for k in self.to_set.keys():
                if k not in self.to_restore:
                    delattr(self.target, k)
    
    o = SomeObject()
    
    print(o.__dict__)
    with Temporary(o, a=42, d=1337):
        print(o.__dict__)
    print(o.__dict__)
    

    【讨论】:

      【解决方案4】:

      mock 提供了这个功能,具体看一下patch.object 的上下文管理器用法。它位于 python3 的核心库中,available on pypi 用于旧版 python。

      设置:

      >>> class C:
      ...     def __init__(self, x, y, z):
      ...         self.x = x
      ...         self.y = y
      ...         self.z = z
      ...         
      >>> c = C(0,1,2)
      

      使用演示:

      >>> print(c.x, c.y, c.z)
      0 1 2
      >>> with patch.object(c, 'x', 'spam'), patch.object(c, 'y', 'eggs'):
      ...     print(c.x, c.y, c.z)
      ...     
      spam eggs 2
      >>> print(c.x, c.y, c.z)
      0 1 2
      

      【讨论】:

        【解决方案5】:

        愚蠢的解决方案

        >>> class Foo(object):
                def __init__(self):
                     self._x = []
                     self._y = []
        
        
                @property
                def x(self):
                   return self._x[-1] or None
        
                @x.setter 
                def x(self, val):
                   self._x.append(val)
        
                def reset_vals(self):
                   if len(self._x) > 1:
                      self._x.pop()
        
        
        >>> bar = Foo()
        >>> bar.x = 1
        >>> bar.x
        1
        >>> bar.x = 2
        >>> bar.x
        2
        >>> bar.reset_vals()
        >>> bar.x
        1
        >>> bar.reset_vals()
        >>> bar.x
        1
        

        仍然很愚蠢,但解决方案不太好

        >>> class Foo(object):
            def __init__(self):
                pass
        
        
        >>> import copy
        >>> bar = Foo()
        >>> bar.x = 1
        >>> bar.x
        1
        >>> bar2 = copy.copy(bar)
        >>> bar2.x
        1
        >>> bar2.x = 5
        >>> bar2.x
        5
        >>> bar
        <__main__.Foo object at 0x0426A870>
        >>> bar.x
        1
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-09-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多