模块的__setattr__和__getattr__在访问全局变量时

3 投票
3 回答
1492 浏览
提问于 2025-04-16 08:28

__getattr____setattr__ 可以很好地处理通过点号来获取或设置对象的属性。那么,我该如何在模块内部拦截对全局变量的获取和设置呢?

我创建了一个模块类型的子类:

from types import ModuleType
class WUserModule(ModuleType):
    def __init__(self, name):
        super().__init__(name)

    def __setattr__(self, name, value):
        if name in self.__dict__:
             super().__setattr__(name, value)
             return
        print('Set name {}, value {}'.format(name, str(value)))

    def __getattr__(self, name):
        print('Get name {}'.format(name))

接着创建一个空模块并加载代码:

module = WUserModule('form_module')
with open('user_module.py', 'r', encoding='utf8') as f:
    exec(f.read(), module.__dict__)

user_module.py 加载的代码:

a=1
print(a)

我需要以某种方式拦截对变量 a 的访问。在加载的代码中,预期会访问一些在模块的 globals() 中不存在的变量,而我想要替换这些请求的值。

更新:

上面的代码并没有按我需要的那样工作:对变量 a 的访问没有在 print 中反映出来。

我正在使用 PyQt4 来编写一个“平台”,让其他用户(程序员)可以添加表单和模块来处理与这些表单的交互。用户模块中的表单通过“注入”的变量 form 进行访问。我想给用户提供一种更简单的方式来访问表单控件的值。比如,不用写 if form.myCheckbox.isChecked()form.myCheckbox.setChecked(myValue),我想提供一个快捷方式:if myCheckboxmyCheckbox = myValue,这样可以在后台拦截值的访问并完成必要的工作:

def __getattr__(self, name):
    formWidget = getatttr(form, name)
    if isinstance(formWidget, QLineEdit):
        formWidget.setText(value)
...

3 个回答

0

这个可以用:

assignments = {}

# extending dict class
class WUserModule(dict):
    def __init__(self, dict):
        self.__dict__ = globals() | dict # to support of import statements inside exec
    def __setitem__(self, key, value):
        self.__dict__[key] = value
        print(f"new assinment {key} = {value}")
        assignments[key] = value
    def __getitem__(self, key): # without this method doesn't work - don't know why ;)
        return self.__dict__[key]

# initial values if needed
input_vars_dict={"a":0}
module = WUserModule(input_vars_dict)
with open('user_module.py', 'r', encoding='utf8') as f:
    exec(f.read(), module)

# assignments filled with all assignment that happpened
print(assignments)

用户空间模块:

print("user_module.py")
print(a)
a=1
b=2

输出结果:

user_module.py
0
new assinment a = 1
new assinment b = 2
{'a': 1, 'b': 2}
0

你想做的事情在一般情况下是行不通的。一个属性不能同时有一个值和另一个属性。你可以选择用 form.myCheckbox = Truebool(form.myCheckbox)或者form.myCheckbox.setChecked(True)bool(form.myCheckbox.isChecked())但不能同时这样做。也就是说,form.myCheckbox 不能同时是 True 和一个 QCheckbox 对象。对于 boolintunicode 这些类型可以处理,但会比较麻烦,而其他类型通常是没办法的。

另外,你对 __getattr__ 的作用有些混淆。它不会被调用来获取已经存在的属性,__getattribute__ 才会被调用。不过,使用 __getattribute__ 是比较复杂的,你可能想把你的复选框做成描述符,或者使用属性,但那样的话你就得放弃模块,改用类。

附注:为什么 form 是一个模块,而不是某个对象的实例呢?

2

你不能这样做。

根本的问题是,属性重载只有在你访问某个属性的时候才有效,具体来说就是:

expr . ident

没有点(.),就无法进行属性重载。因此,无论你怎么做,像这样的序列

a

永远无法调用属性重载,不管其他什么东西可能等于a

撰写回答