11

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

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根据需要修改类)

4

5 回答 5

20

上下文管理器可以很容易地用于它。

引用官方文档:

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

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

例子:

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
于 2016-07-22T17:19:17.593 回答
5

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

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) 
于 2016-07-22T17:19:23.787 回答
3

mock提供了这个功能,具体看上下文管理器的用法patch.object。它位于 python3 的核心库中,可在 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
于 2016-07-22T17:14:08.550 回答
3

您也可以使用__enter__and本地执行此操作__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__)
于 2016-07-22T17:25:26.807 回答
2

愚蠢的解决方案

>>> 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
于 2016-07-22T17:25:31.513 回答