是否有一种可以重载赋值运算符的魔术方法,例如__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()
有可能吗?这是疯了吗?我应该上药吗?
答案 0 :(得分:52)
你描述它的方式是绝对不可能的。对名称的赋值是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
请注意,有一个成员变量_locked
,用于控制是否允许分配。您可以将其解锁以更新值。
答案 1 :(得分:26)
不,因为赋值是language intrinsic,它没有修改钩子。
答案 2 :(得分:8)
我不认为这是可能的。我看到它的方式,赋值给变量对它之前提到的对象没有任何作用:它只是变量“指向”现在的另一个对象。
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!
但是,您可以通过使用引发异常的__setitem__()
或__setattr__()
方法创建包装器对象来模仿所需的行为,并保留“不可更改”的内容。
答案 3 :(得分:2)
没有
考虑一下,在您的示例中,您将名称var重新绑定为新值。 您实际上并没有触及Protect的实例。
如果您要重新绑定的名称实际上是某个其他实体的财产,即 myobj.var然后你可以防止为实体的属性/属性赋值。 但我认为这不是你想要的例子。
答案 4 :(得分:2)
在全局命名空间中,这是不可能的,但您可以利用更高级的Python元编程来防止创建Protect
对象的多个实例。 Singleton pattern就是一个很好的例子。
在Singleton的情况下,您将确保一旦实例化,即使重新分配引用该实例的原始变量,该对象也将保持不变。任何后续实例都只返回对同一对象的引用。
尽管存在这种模式,但您永远无法阻止重新分配全局变量名称。
答案 5 :(得分:2)
使用顶级命名空间,这是不可能的。当你运行
var = 1
它将键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()
这将启动几乎正常运行的REPL,但拒绝任何设置变量val
的尝试。您也可以使用execfile(filename, myg)
。请注意,这并不能防止恶意代码。
答案 6 :(得分:1)
一个丑陋的解决方案是重新分配析构函数。但这并不是真正的超载分配。
import copy
global a
class MyClass():
def __init__(self):
a = 1000
# ...
def __del__(self):
a = copy.copy(self)
a = MyClass()
a = 1
答案 7 :(得分:1)
是的,您可以通过修改__assign__
来处理ast
。
pip install assign
class T():
def __assign__(self, v):
print('called with %s' % v)
b = T()
c = b
>>> import magic
>>> import test
called with c
该项目位于https://github.com/RyanKung/assign
更简单的要点:https://gist.github.com/RyanKung/4830d6c8474e6bcefa4edd13f122b4df
答案 8 :(得分:1)
通常,我发现的最佳方法是将__ilshift__
用作设置方法,将__rlshift__
用作获取方法,并由属性装饰器复制。
几乎是最后一个要解析的运算符,只是(|&^)并且逻辑较低。
很少使用({__lrshift__
较少,但是可以考虑)。
在使用PyPi Assign软件包时,只能控制正向分配,因此操作员的实际“力量”较低。 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
输出:
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'
与shift相同:
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
输出:
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
因此,<<=
运算符能够在某种属性上获得价值,这是一种更加直观的解决方案,它不是试图让用户犯一些反射性错误,例如:
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
答案 9 :(得分:1)
在模块内部,通过一点黑魔法,这绝对是可能的。
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
请注意,即使在模块内部,一旦发生类更改,也无法写入受保护的变量。上面的示例假定代码驻留在名为tst
的模块中。您可以在repl
中将tst
更改为__main__
。
答案 10 :(得分:1)
正如其他人所说,没有办法直接做到这一点。不过它可以被类成员覆盖,这对很多情况都有好处。
正如 Ryan Kung 所提到的,可以对包的 AST 进行检测,因此如果分配的类实现了特定的方法,则所有分配都会产生副作用。基于他处理对象创建和属性分配案例的工作,修改后的代码和完整描述可在此处获得:
https://github.com/patgolez10/assignhooks
软件包可以安装为:pip3 install assignhooks
示例
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)
对其进行检测,例如
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