2016-07-22 13 views
12

Ich finde oft, dass ich einige Element Variablen vorübergehend zuweisen müssen, z.Wie können Mitgliedervariablen temporär zugewiesen werden?

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 

aber ich wünschte, ich einfach

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

oder

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

Kann Python Dekorateure oder andere Sprach-Features ermöglichen diese Art von Muster schreiben könnte? (Ich könnte c 's Klasse nach Bedarf ändern)

+2

warum nicht eine neue Instanz erstellen – haifzhan

+0

@HaifengZhang 'c' viele andere Variablen haben könnte, dass? werden nicht geändert – MaxB

+0

Guter Aufruf @HaifengZhang, es sei denn 'do_something (c)' weist anderen Attributen von 'c' zu. –

Antwort

20

Context managers kann es leicht verwendet werden.

Zitiert offizielle Dokumentation:

Typische Anwendungen von Kontext-Manager umfassen speichern und verschiedene Arten von globalen Zustand wieder herstellt, Verriegeln und Entriegeln Ressourcen, geöffnet Schließen Dateien usw.

Es scheint, Wie das Speichern und Wiederherstellen von Zustand ist genau das, was wir hier machen wollen.

Beispiel:

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 
+7

das ist eine gute Antwort, aber Sie möchten vielleicht die Interna des Contextmanager in ein "try/finally" verpacken – acushner

3

mock bietet diese Funktionalität, insbesondere Blick auf die Context-Manager-Verwendung von patch.object. Es ist in Core-Bibliotheken in Python3 und available on pypi für ältere Python.

Setup:

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

Nutzungs Demo:

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

denke ich, ein contextmanager sollte das tun, was Sie wollen:

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

Sie können dies auch nativ __enter__ und __exit__ mit tun. Simplistic Beispiel:

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__) 
2

Goofy Lösung

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

Noch doof, aber weniger Lösung

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