Python能否进行赋值重载?

111

是否有一种类似于 __assign__(self, new_value) 的魔术方法可以重载赋值运算符?

我想禁止实例的重新绑定:

class Protect():
  def __assign__(self, value):
    raise Exception("This is an ex-parrot")

var = Protect()  # once assigned...
var = 1          # this should raise Exception()

这可行吗?这是疯了吗?我应该吃药吗?


2
使用案例:人们将使用我的服务API编写小脚本,我想防止他们更改内部数据并将此更改传播到下一个脚本。 - Caruccio
7
Python明确避免承诺阻止恶意或无知的程序员访问。其他语言允许您避免由于无知而导致的一些程序员错误,但人们有一种神奇的能力可以绕过这些错误。 - msw
你可以使用 exec in d 来执行这段代码,其中 d 是某个字典。如果代码在模块级别上,每个赋值都应该被发送回字典。你可以在执行后恢复你的值/检查值是否改变,或者拦截字典赋值,即用另一个对象替换变量的字典。 - Ant6n
哦不,所以在模块级别上模拟VBA行为,如ScreenUpdating = False是不可能的。 - Winand
你可以使用模块的 __all__ 属性 来防止私有数据被导出,这是 Python 标准库常用的方法。 - Ben
我们能将此推广至全局赋值操作吗?比如 x=1x=2 会报错。 - Poojan
13个回答

1
自 Python 3.8 开始,可以使用 typing.Final 来提示某个值是只读的。这意味着在运行时该值不会发生任何变化,但如果您使用了任何可以读取类型提示的 linter,则用户在尝试分配该值时将收到警告。请注意保留 HTML 标记。
from typing import Final

x: Final[int] = 3

x = 5  # Cannot assign to final name "x" (mypy)

这样可以让代码更加简洁,但它完全信任用户在运行时尊重它,不会试图阻止用户更改值。
另一种常见的模式是暴露函数而不是模块常量,例如sys.getrecursionlimit和sys.setrecursionlimit。
def get_x() -> int:
    return 3

虽然用户可以执行module.get_x = my_get_x,但这显然是用户试图破坏它的一种尝试,而这是无法修复的。通过这种方式,我们可以在最小化复杂度的情况下防止人们“意外地”更改我们模块中的值。


1

正如其他人所提到的,没有直接的方法可以做到这一点。但是对于类成员,可以进行覆盖,这对许多情况都很有用。

正如Ryan Kung所提到的,一个包的AST可以被插装,以便如果分配的类实现了特定的方法,则所有分配都可以具有副作用。在他的工作基础上,处理对象创建和属性分配情况,修改后的代码和完整描述在此处提供:

https://github.com/patgolez10/assignhooks

该包可以通过以下方式安装:pip3 install assignhooks 示例<testmod.py>:
class SampleClass():

   name = None

   def __assignpre__(self, lhs_name, rhs_name, rhs):
       print('PRE: assigning %s = %s' % (lhs_name, rhs_name))
       # modify rhs if needed before assignment
       if rhs.name is None:
           rhs.name = lhs_name
       return rhs

   def __assignpost__(self, lhs_name, rhs_name):
       print('POST: lhs', self)
       print('POST: assigning %s = %s' % (lhs_name, rhs_name))


def myfunc(): 
    b = SampleClass()
    c = b
    print('b.name', b.name)

将其翻译为中文:用于工具化,例如 <test.py>。保留HTML,不解释。
import assignhooks

assignhooks.instrument.start()  # instrument from now on

import testmod

assignhooks.instrument.stop()   # stop instrumenting

# ... other imports and code bellow ...

testmod.myfunc()

将产生以下结果:

$ python3 ./test.py

POST: lhs <testmod.SampleClass object at 0x1041dcc70>
POST: assigning b = SampleClass
PRE: assigning c = b
POST: lhs <testmod.SampleClass object at 0x1041dcc70>
POST: assigning c = b
b.name b

0
一个丑陋的解决方案是在析构函数中重新分配。但这并不是真正的重载赋值。
import copy
global a

class MyClass():
    def __init__(self):
            a = 1000
            # ...

    def __del__(self):
            a = copy.copy(self)


a = MyClass()
a = 1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接