我正在尝试了解如何在调用对象属性时临时更改对象的属性,并在未调用对象时保持原始值。
让我用一些代码描述问题:
class DateCalc:
DEFAULT= "1/1/2001"
def __init__(self, day=DEFAULT):
self.day= day
def __call__(self, day=DEFAULT):
self.day= day
return self
def getday(self):
return self.day
如果用户在传递另一个值时调用getday方法 即2002年2月2日,self.day设置为2/2/2002。但是我希望能够在方法调用之后将self.day恢复为1/1/2001的原始值:
d_obj = DateCalc()
d_obj.getday() == "1/1/2001"
True
d_obj().getday() == "1/1/2001"
True
another_day_str = "2/2/2002"
d_obj(another_day_str).getday()
返回
"2/2/2002"
但是当我运行以下
时d_obj.getday()
返回
"2/2/2002"
我想知道恢复值的正确方法是什么,而不需要在每个方法调用中都包含代码。其次,在调用对象时也应如此。例如:
d_obj().getday()
应该返回
"1/1/2001"
我认为调用魔术方法的装饰者可以在这里工作,但我不确定从哪里开始。
非常感谢任何帮助
答案 0 :(得分:1)
您似乎需要像上下文管理器这样的行为:在有限时间内修改属性,使用更新的属性然后恢复为原始属性。您可以通过让__call__
返回一个上下文管理器来执行此操作,然后您可以在with
块中使用它,如下所示:
d_obj = DateCalc()
print(d_obj.getday()) # 1/1/2001
with d_obj('2/2/2002'):
print(d_obj.getday()) # 2/2/2002
print(d_obj.getday()) # 1/1/2001
有两种方法可以创建这样的上下文管理器。最简单的方法是在__call__
中使用嵌套方法并使用contextlib.contextmanager
进行装饰:
from contextlib import contextmanager
...
def __call__(self, day=DEFAULT):
@contextmanager
def context()
orig = self.day
self.day = day
yield
self.day = orig
return context
你也可以使用一个完全成熟的嵌套类,但除非你有一些非常复杂的要求,否则我不推荐它。我只是提供完整性:
def __call__(self, day=DEFAULT):
class Context:
def __init__(self, inst, new):
self.inst = inst
self.old = inst.day
self.new = new
def __enter__(self):
self.inst.day = self.new
def __exit__(self, *args):
self.inst.day = self.old
return Context(self, day)
此外,您应该考虑将getday
作为属性,特别是如果它是真正的只读属性。
另一种方法是让你的方法接受不同的值:
def getday(self, day=None):
if day is None:
day = self.day
return day
这实际上是一个相当常见的习语。
答案 1 :(得分:1)
由于您可能不希望在定义不明确的时间间隔内修改对象的属性,因此需要返回或以其他方式创建不同的对象。
最简单的情况是你有两个独立的对象,而且根本没有__call__
方法:
d1_obj = DateCalc()
d2_obj = DateCalc('2/2/2002')
print(d1_obj.getday()) # 1/1/2001
print(d2_obj.getday()) # 2/2/2002
如果您知道原始案例中要使用d_obj
vs d_obj()
的位置,那么您也清楚知道此版本中d1_obj
与d2_obj
的使用位置。
这可能不适用于DateCalc
实际上代表一个非常复杂的对象的情况,该对象具有您不想要更改的许多属性。在这种情况下,您可以让__call__
方法返回一个单独的对象,该对象可以智能地复制您想要的原始部分。
对于一个简单的案例,这可能只是
def __call__(self, day=DEFAULT):
return type(self)(day)
如果对象变得足够复杂,您将需要创建代理。代理是将大多数实现细节转发给另一个对象的对象。 super()
是一个代理示例,其中包含高度自定义的__getattribute__
实现,以及其他内容。
在您的特定情况下,您有几个要求:
self
参数传递给任何(至少非特殊的)被调用的方法。您可以根据需要复杂化(在这种情况下,查找如何正确实现here等代理对象)。这是一个相当简单的例子:
# Assume that there are many fields like `day` that you want to modify
class DateCalc:
DEFAULT= "1/1/2001"
def __init__(self, day=DEFAULT):
self.day= day
def getday(self):
return self.day
def __call__(self, **kwargs):
class Proxy:
def __init__(self, original, **kwargs):
self._self_ = original
self.__dict__.update(kwargs)
def __getattribute__(self, name):
# Don't forward any overriden, dunder or quasi-private attributes
if name.startswith('_') or name in self.__dict__:
return object.__getattribute__(self, name)
# This part is simplified:
# it does not take into account __slots__
# or attributes shadowing methods
t = type(self._self_)
if name in t.__dict__:
try:
return t.__dict__[name].__get__(self, t)
except AttributeError:
pass
return getattr(self._self_, name)
return Proxy(self, **kwargs)
代理将完全按照您的意愿工作:它会转发您在原始对象__call__
中未覆盖的任何值。有趣的是,它将实例方法绑定到代理对象而不是原始方法,因此getday
会被self
调用,其中包含覆盖的值:
d_obj = DateCalc()
print(type(d_obj)) # __main__.DateCalc
print(d_obj.getday()) # 1/1/2001
d2_obj = d_obj(day='2/2/2002')
print(type(d2_obj)) # __main__.DateCalc.__call__.<locals>.Proxy
print(d2_obj.getday()) # 2/2/2002
请记住,此处显示的代理对象实现的功能非常有限,并且在许多情况下无法正常工作。话虽如此,它可能涵盖了您开箱即用的许多用例。一个很好的例子是,如果你选择使day
成为一个属性而不是一个getter(这是更多的Pythonic方法):
class DateCalc:
DEFAULT= "1/1/2001"
def __init__(self, day=DEFAULT):
self.__dict__['day'] = day
@property
def day(self):
return self.__dict__['day']
# __call__ same as above
...
d_obj = DateCalc()
print(d_obj(day='2/2/2002').day) # 2/2/2002
这里的问题是代理的day
版本只是一个常规可写属性而不是只读属性。如果这对您来说是个问题,那么在代理上适当地实施__setattr__
将留给读者练习。