在运行时重写@property getter

1 投票
4 回答
2779 浏览
提问于 2025-04-18 05:54

有没有办法在运行时重写Python中的@property获取器?

我希望我的类有一个“复杂”的获取器,它可以运行一些代码并返回一个可能会变化的值。但是,如果出于某种原因我把那个变量设置成其他值,我希望它总是返回那个值。

比如说:

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',在我的获取器中一直检查它,但我希望尽量避免这样做。

理想情况下,我的设置函数可以做类似这样的事情:

self.random_number.getter = lambda:4

但是我不知道怎么让它工作。

4 个回答

0

你可以把数字的来源定义成一个函数,然后通过属性设置器来切换这个函数。这是可行的,因为Python支持一等函数,这意味着你可以获取函数的引用(而不是直接调用它),并像其他对象一样传递它们。

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

想了解更多关于functools.partial的信息,可以查看文档。partial(random.randint, 0, 10)基本上是创建了一个函数,当你调用这个函数时,它会执行random.randint(0, 10)并返回结果。

0

你可以在程序运行时替换一个属性。实际上,定义这个属性并把它绑定到类上也是在运行时进行的(在Python中,运行之前并没有什么特别的事情发生)。不过,由于属性是描述符,而描述符是类的一部分,所以它并不是存储每个实例数据的合适地方。你还是应该使用一个单独的变量(比如 self._number,用 None 来表示还没有被强制赋值),前提是你真的需要实现这种让人困惑的行为。

6

你不能在实例级别“重写”一个属性,因为这个属性是一个数据描述符(它同时有 __get____set__ 这两个功能)。

这意味着你的获取器必须检查实例属性。其实这并没有听起来那么复杂:

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__ 功能,这样你就可以直接在实例上设置属性,并且让它覆盖描述符。不过对于你的情况,使用一个明确的设置器似乎更简单。

4

你可以自己写一个像属性一样的类,但不需要使用 __set__ 这个方法。这样一来,设置属性的时候就会创建一个实例属性,而不是调用一个设置方法。

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

撰写回答