调用obj时临时更改全局变量

2024-04-26 12:47:29 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图理解如何在调用对象时临时更改其属性,并在不调用对象时保持原始值。你知道吗

让我用一些代码来描述这个问题:

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方法 i、 2002年2月2日,自我保护日设定为2002年2月2日。但是我希望能够恢复自我保护日方法调用后的原始值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"

我以为调用魔术方法的装饰器在这里可以工作,但我不确定从哪里开始。你知道吗

任何帮助都将不胜感激


Tags: 对象方法代码selftrueobjdefaultreturn
2条回答

似乎您需要一种类似于上下文管理器的行为:要在有限的时间内修改属性,请使用更新的属性,然后还原为原始属性。您可以让__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__中的嵌套方法,并用^{}装饰它:

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

这其实是一个相当普遍的成语。你知道吗

由于您可能并不真的希望修改对象的属性以获得一个定义不好的间隔,因此您需要返回或以其他方式创建一个不同的对象。你知道吗

最简单的情况是,您有两个独立的对象,并且根本没有__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_objd_obj(),那么您也清楚地知道在这个版本中在何处使用d1_objd2_obj。你知道吗

这对于DateCalc实际表示一个非常复杂的对象的情况来说可能是不够的,这个对象有许多您不想更改的属性。在这种情况下,可以让__call__方法返回一个独立的对象,该对象智能地复制所需的原始部分。你知道吗

对于一个简单的例子,这可能只是

def __call__(self, day=DEFAULT):
    return type(self)(day)

如果对象变得足够复杂,则需要创建代理。代理是将大部分实现细节转发给另一个对象的对象。^{}是具有高度定制的^{}实现的代理的一个示例。你知道吗

在您的特定情况下,您有几个要求:

  1. 代理必须存储所有重写的属性。你知道吗
  2. 代理必须从原始对象获取所有非重写属性。你知道吗
  3. 代理必须将自身作为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(这是一种更为python的方法):

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__将留给读者作为练习。你知道吗

相关问题 更多 >