【问题标题】:Override @property getter in runtime在运行时覆盖 @property getter
【发布时间】:2024-01-06 21:17:01
【问题描述】:

有没有办法在 Python 的运行时覆盖 @property getter?

我希望我的班级有一个“复杂”的 getter,它运行一些代码并返回一个可以更改的值。但是,如果出于某种原因我将该变量设置为其他值,我希望它始终返回该值。

例如:

class Random(object):
    @property
    def random_number():
        return random.randint(0,10)


    @random_number.setter
    def random_number(value):
        # Overlord says that random must always return `value`
        (((I don't know that to do here)))


>>>r = Random()
>>>r.random_number
>>>(returns something between 0,10)

>>>r.random_number = 4 # Confirmed by dice roll
>>>r.random_number
>>>4 (always)

我知道我可以使用诸如“force_value”之类的实例变量来做到这一点,我总是检查我的 getter,但如果可能的话我想避免这种情况。

理想情况下,我的 setter 函数可以做类似的事情

self.random_number.getter = lambda:4

但我不知道如何做到这一点。

【问题讨论】:

    标签: python properties overriding


    【解决方案1】:

    您不能在实例级别“覆盖”属性,因为属性是 data descriptor(它同时具有 __get____set__ 挂钩)。

    这意味着你的 getter 来检查实例属性。这并不像听起来那么痛苦:

    class Random(object):
        @property
        def random_number(self):
            if hasattr(self, '_override'):
                return self._override
            return random.randint(0,10)
    
        @random_number.setter
        def random_number(self, value):
            # Overlord says that random must always return `value`
            self._override = value
    

    您甚至可以添加删除器:

        @random_number.deleter
        def random_number(self):
            if hasattr(self, '_override'):
                del self._override
    

    另一种方法是创建您自己的替代描述符类,它不实现__setter__ 挂钩,以便您可以直接在实例上设置属性并让它覆盖描述符。但是对于您的用例,仅使用显式设置器似乎更简单。

    【讨论】:

      【解决方案2】:

      您可以将数字的来源定义为使用属性设置器切换出来的函数。这是因为 Python 支持 first-class functions,这意味着您可以获得对它们的引用(无需调用它们)并像任何其他对象一样传递它们:

      from functools import partial
      import random
      
      
      class Random(object):
          def __init__(self):
              self.number_source = partial(random.randint, 0, 10)
      
          @property
          def random_number(self):
              return self.number_source()
      
          @random_number.setter
          def random_number(self, value):
              self.number_source = lambda: value
      
      
      r = Random()
      print r.random_number
      
      r.random_number = 42
      print r.random_number
      

      输出:

      <random int>
      42
      

      有关partial(random.randint, 0, 10) 的详细信息,请参阅functools.partial 的文档。它基本上创建了一个函数,当被调用时,调用random.randint(0, 10) 并返回结果。

      【讨论】:

        【解决方案3】:

        您可以在运行时替换属性。实际上,即使是定义属性并将其附加到类的行为也是运行时操作(在 Python 中运行时之前没有发生任何值得注意的事情)。但是,由于属性是一个描述符,而描述符是 class 的一部分,所以它不是存储每个实例数据的正确位置。你仍然应该使用一个单独的变量(可能是self._numberNone 表示尚未强制执行),前提是你真的必须实现这种愚蠢和令人困惑的行为。

        【讨论】:

          【解决方案4】:

          您可以编写自己的类似属性的类,但不使用__set__-方法。这样,设置属性会创建实例属性而不是调用 setter 方法

          import random
          
          class property_getter(object):
              def __init__(self, getter):
                  self.getter = getter
          
              def __get__(self, obj, cls):
                  return self.getter(obj)
          
          class Random(object):
              @property_getter
              def random_number(self):
                  return random.randint(0,10)
          
          x = Random()
          print x.random_number
          print x.random_number
          x.random_number = 5
          print x.random_number
          

          【讨论】:

          • 这很聪明。我只是不确定“聪明”在这里是否是一件好事。
          • 这正是我想要做的,所以我将其标记为已接受。但是,我同意其他所有人的观点,我将坚持使用简单且不那么神奇的 _override 实例属性。
          最近更新 更多