修改Python中已定义类的属性(并再次运行其定义)

8mmmxcuj  于 2023-02-07  发布在  Python
关注(0)|答案(2)|浏览(147)

我试图通过改变一个属性的值来修改一个已经定义的类。重要的是,我希望这个改变在内部传播。
例如,考虑以下类:

class Base:
    x = 1
    y = 2 * x

    # Other attributes and methods might follow

assert Base.x == 1
assert Base.y == 2

我想将x更改为2,使其等效于此。

class Base:
    x = 2
    y = 2 * x

assert Base.x == 2
assert Base.y == 4

但我想以如下方式提出:

Base = injector(Base, x=2)

有没有一种方法可以做到这一点,而无需重新编译原来的类源代码?

t0ybt7op

t0ybt7op1#

由于Base类是用dependent动态赋值属性声明的,所以这个想法比较复杂,虽然我们可以检查类的静态属性,但是我认为除了解析类的sourcecode之外,没有其他方法可以得到动态表达式。查找并替换“injected”属性名,使用其值和exec/eval重新定义。但这是您希望避免的方式。(此外:如果您期望injector对于所有类都是统一的)。
如果您希望继续依赖于动态计算的属性,请将依赖属性定义为lambda函数。

class Base:
    x = 1
    y = lambda: 2 * Base.x

Base.x = 2
print(Base.y())   # 4
hgc7kmma

hgc7kmma2#

您想要达到的效果属于“React式编程”的范畴--一种编程范式(现在无处不在的Javascript库就是从这种编程范式中得到灵感而得名的)。
虽然Python有很多机制可以实现这一点,但你需要编写代码来真正 * 利用**这些机制。
默认情况下,你在示例中使用的普通Python代码使用的是命令式范式,它是急切的:每当遇到表达式时,就执行该表达式,并使用该表达式的结果(在这种情况下,结果存储在class属性中)。
Python的优势还在于,一旦你编写了一个允许一些被动代码发生的代码库,你的代码库的 * 用户 * 就不必意识到这一点,事情或多或少会“神奇地”运行。
但是,如上所述,这不是免费的。对于能够在x发生变化时重新定义y的情况,

class Base:
    x = 1
    y = 2 * x

有两条路径可以遵循-最重要的是,在执行“*”操作符时(当Python * 解析 * 类体时就会发生这种情况),至少操作的一端不再是普通的数字,而是一个实现定制__mul__方法的特殊对象然后,不是将结果数存储在y中,而是将表达式存储在某个地方,并且当y作为类属性被检索时,其他机制强制表达式解析。
如果你想在instance级别而不是类级别实现,那么它会更容易实现,但是要记住,你必须在你的特殊“source”类中为原始值定义 each 操作符。
此外,这种方法和使用property的更简单的示例描述符方法都是“延迟求值”的:这意味着,y的值是在使用时计算的(如果它将被多次使用,则可以缓存)。如果您希望在每次分配x时(而不是在使用y时)对其求值,则需要其他机制。虽然缓存,但惰性方法可以将对急切求值的需要减少到不需要的程度。

1 -在那里挖掘之前

Python编写这样的代码最简单的方法就是把要计算的表达式写成函数,然后使用property内置函数作为描述符来获取这些值,缺点很小:你只需要把表达式 Package 在一个函数中(然后,把这个函数 Package 在一个可以给它添加描述符属性的东西中,比如property)。你可以在你的表达式中自由地使用任何Python代码,包括函数调用、对象示例化、I/O等等。2(注意,另一种方法需要连接每个所需的操作符,仅仅是为了入门)。
让您想要的东西为Base的 * 示例 * 工作的简单“101”方法是:

class Base:
   x = 1
   @property
   def y(self):
       return self.x * 2

b = Base()
b.y
-> 2
Base.x = 3
b.y
-> 6

property的工作可以重写,以便从类中检索y,而不是从示例中检索,也可以达到同样的效果(这仍然比其他方法更容易)。
如果这样做对您有用,我建议您这样做,如果您需要缓存y的值直到x实际发生变化,可以使用普通编码来完成

2 -正是您所要求的,带有元类

如上所述,在计算2 * x表达式时,Python需要知道y属性的特殊状态。在赋值时,这已经太晚了。幸运的是,Python 3允许类体在 custom 命名空间中运行,以进行属性赋值,方法是在元类中实现__prepare__方法,然后记录所有发生的事情。以及用实现__mul__和其他特殊方法的特制对象替换感兴趣的原始属性。
这样做甚至可以让值被急切地计算出来,这样它们就可以像普通的Python对象一样工作,但是要注册信息,这样一个特殊的injector函数就可以重新创建类,重做所有依赖于表达式的属性,它还可以实现惰性求值,就像上面描述的那样。
我可以做,但要花上几个小时--这比我能提供的S. O.答复要多,抱歉。

3 -将你的原始值 Package 在注册它们的基类中

这样injector就可以工作了,只是不要用__prepare__方法来做元类,并要求将基元值(如本例中的x) Package 在一个专门的类中,这个类将创建惰性操作符,并跟踪所有操作。

class Base:
   x = Wrapper(1)
   y = 2 * x

但是保存某些复杂性保存在那里,它几乎相同的工作量的另一种方法。
如果你真的需要这个,而不是property的方法,取得联系(甚至通过评论)

相关问题