Car*_*cio 65 python methods class magic-methods assignment-operator
有没有一个可以重载赋值运算符的魔术方法,比如__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()
Run Code Online (Sandbox Code Playgroud)
可能吗?这是疯了吗?我应该上药吗?
ste*_*eha 58
你描述它的方式是绝对不可能的.对名称的赋值是Python的基本特性,并且没有提供用于改变其行为的钩子.
但是,可以根据需要通过覆盖来控制对类实例中成员的赋值.__setattr__()
.
class MyClass(object):
def __init__(self, x):
self.x = x
self._locked = True
def __setattr__(self, name, value):
if self.__dict__.get("_locked", False) and name == "x":
raise AttributeError("MyClass does not allow assignment to .x member")
self.__dict__[name] = value
>>> m = MyClass(3)
>>> m.x
3
>>> m.x = 4
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __setattr__
AttributeError: MyClass does not allow assignment to .x member
Run Code Online (Sandbox Code Playgroud)
请注意,有一个成员变量_locked
,用于控制是否允许赋值.您可以解锁它以更新值.
我不认为这是可能的.我看到它的方式,对变量的赋值对它之前提到的对象没有任何作用:它只是变量"指向"现在的另一个对象.
In [3]: class My():
...: def __init__(self, id):
...: self.id=id
...:
In [4]: a = My(1)
In [5]: b = a
In [6]: a = 1
In [7]: b
Out[7]: <__main__.My instance at 0xb689d14c>
In [8]: b.id
Out[8]: 1 # the object is unchanged!
Run Code Online (Sandbox Code Playgroud)
但是,您可以通过使用__setitem__()
或__setattr__()
引发异常的方法创建包装器对象来模仿所需的行为,并保持"不可更改"的内容.
在模块内部,这绝对是可能的,通过一点黑魔法。
import sys
tst = sys.modules['tst']
class Protect():
def __assign__(self, value):
raise Exception("This is an ex-parrot")
var = Protect() # once assigned...
Module = type(tst)
class ProtectedModule(Module):
def __setattr__(self, attr, val):
exists = getattr(self, attr, None)
if exists is not None and hasattr(exists, '__assign__'):
exists.__assign__(val)
super().__setattr__(attr, val)
tst.__class__ = ProtectedModule
Run Code Online (Sandbox Code Playgroud)
上面的示例假设代码驻留在名为tst
. 您可以repl
通过更改tst
为在 中执行此操作__main__
。
如果要保护通过本地模块的访问,请通过tst.var = newval
.
我会在 Python 地狱中燃烧,但生活怎能没有一点乐趣。
重要免责声明:
也许你不能重载赋值,但你可以(至少使用Python ~3.9)实现你想要的,即使在顶级命名空间。对于所有情况都很难“正确”地做到这一点,但这里有一个 hacking 的小例子audithook
:
import sys
import ast
import inspect
import dis
import types
def hook(name, tup):
if name == "exec" and tup:
if tup and isinstance(tup[0], types.CodeType):
# Probably only works for my example
code = tup[0]
# We want to parse that code and find if it "stores" a variable.
# The ops for the example code would look something like this:
# ['LOAD_CONST', '<0>', 'STORE_NAME', '<0>',
# 'LOAD_CONST', 'POP_TOP', 'RETURN_VALUE', '<0>']
store_instruction_arg = None
instructions = [dis.opname[op] for op in code.co_code]
# Track the index so we can find the '<NUM>' index into the names
for i, instruction in enumerate(instructions):
# You might need to implement more logic here
# or catch more cases
if instruction == "STORE_NAME":
# store_instruction_arg in our case is 0.
# This might be the wrong way to parse get this value,
# but oh well.
store_instruction_arg = code.co_code[i + 1]
break
if store_instruction_arg is not None:
# code.co_names here is: ('a',)
var_name = code.co_names[store_instruction_arg]
# Check if the variable name has been previously defined.
# Will this work inside a function? a class? another
# module? Well... :D
if var_name in globals():
raise Exception("Cannot re-assign variable")
# Magic
sys.addaudithook(hook)
Run Code Online (Sandbox Code Playgroud)
这是例子:
>>> a = "123"
>>> a = 123
Traceback (most recent call last):
File "<stdin>", line 21, in hook
Exception: Cannot re-assign variable
>>> a
'123'
Run Code Online (Sandbox Code Playgroud)
*对于 Jupyter,我找到了另一种看起来更干净的方法,因为我解析的是 AST 而不是代码对象:
import sys
import ast
def hook(name, tup):
if name == "compile" and tup:
ast_mod = tup[0]
if isinstance(ast_mod, ast.Module):
assign_token = None
for token in ast_mod.body:
if isinstance(token, ast.Assign):
target, value = token.targets[0], token.value
var_name = target.id
if var_name in globals():
raise Exception("Can't re-assign variable")
sys.addaudithook(hook)
Run Code Online (Sandbox Code Playgroud)
使用顶级命名空间,这是不可能的。当你跑
var = 1
Run Code Online (Sandbox Code Playgroud)
它将键var
和值存储1
在全局字典中。它大致相当于调用globals().__setitem__('var', 1)
. 问题是您不能在正在运行的脚本中替换全局字典(您可能可以通过弄乱堆栈,但这不是一个好主意)。但是,您可以在辅助命名空间中执行代码,并为其全局变量提供自定义字典。
class myglobals(dict):
def __setitem__(self, key, value):
if key=='val':
raise TypeError()
dict.__setitem__(self, key, value)
myg = myglobals()
dict.__setitem__(myg, 'val', 'protected')
import code
code.InteractiveConsole(locals=myg).interact()
Run Code Online (Sandbox Code Playgroud)
这将启动一个几乎正常运行的 REPL,但拒绝任何设置变量的尝试val
。您也可以使用execfile(filename, myg)
. 请注意,这并不能防止恶意代码。
小智 5
一般来说,我发现的最好方法是重写__ilshift__
作为 setter 和__rlshift__
getter,由属性装饰器复制。它几乎是最后一个被解析的运算符(| & ^),逻辑较低。很少使用(__lrshift__
较少,但可以考虑)。
在使用 PyPi 分配包时,只能控制前向分配,因此操作员的实际“强度”较低。PyPi 分配包示例:
class Test:
def __init__(self, val, name):
self._val = val
self._name = name
self.named = False
def __assign__(self, other):
if hasattr(other, 'val'):
other = other.val
self.set(other)
return self
def __rassign__(self, other):
return self.get()
def set(self, val):
self._val = val
def get(self):
if self.named:
return self._name
return self._val
@property
def val(self):
return self._val
x = Test(1, 'x')
y = Test(2, 'y')
print('x.val =', x.val)
print('y.val =', y.val)
x = y
print('x.val =', x.val)
z: int = None
z = x
print('z =', z)
x = 3
y = x
print('y.val =', y.val)
y.val = 4
Run Code Online (Sandbox Code Playgroud)
输出:
x.val = 1
y.val = 2
x.val = 2
z = <__main__.Test object at 0x0000029209DFD978>
Traceback (most recent call last):
File "E:\packages\pyksp\pyksp\compiler2\simple_test2.py", line 44, in <module>
print('y.val =', y.val)
AttributeError: 'int' object has no attribute 'val'
Run Code Online (Sandbox Code Playgroud)
与移位相同:
class Test:
def __init__(self, val, name):
self._val = val
self._name = name
self.named = False
def __ilshift__(self, other):
if hasattr(other, 'val'):
other = other.val
self.set(other)
return self
def __rlshift__(self, other):
return self.get()
def set(self, val):
self._val = val
def get(self):
if self.named:
return self._name
return self._val
@property
def val(self):
return self._val
x = Test(1, 'x')
y = Test(2, 'y')
print('x.val =', x.val)
print('y.val =', y.val)
x <<= y
print('x.val =', x.val)
z: int = None
z <<= x
print('z =', z)
x <<= 3
y <<= x
print('y.val =', y.val)
y.val = 4
Run Code Online (Sandbox Code Playgroud)
输出:
x.val = 1
y.val = 2
x.val = 2
z = 2
y.val = 3
Traceback (most recent call last):
File "E:\packages\pyksp\pyksp\compiler2\simple_test.py", line 45, in <module>
y.val = 4
AttributeError: can't set attribute
Run Code Online (Sandbox Code Playgroud)
因此,<<=
获取属性值的操作员是视觉上更加干净的解决方案,并且它不会尝试用户犯一些反思性错误,例如:
var1.val = 1
var2.val = 2
# if we have to check type of input
var1.val = var2
# but it could be accendently typed worse,
# skipping the type-check:
var1.val = var2.val
# or much more worse:
somevar = var1 + var2
var1 += var2
# sic!
var1 = var2
Run Code Online (Sandbox Code Playgroud)