是否可以在Python中动态地将属性添加到内置python对象中?

时间:2019-01-31 04:16:17

标签: python python-3.x python-2.7 built-in built-in-types

我需要向python对象动态添加属性(包含元组或对象)。这适用于我编写的Python类,但不适用于内置类。

考虑以下程序:

import numpy as np

class My_Class():
    pass

my_obj = My_Class()
my_obj2 = My_Class()

my_obj.__my_hidden_field = (1,1)
my_obj2.__my_hidden_field = (2,1)

print(my_obj.__my_hidden_field, my_obj2.__my_hidden_field)

这将正确打印(1, 1) (2, 1)。但是,以下程序无效。

X  = np.random.random(size=(2,3))


X.__my_hidden_field = (3,1) 
setattr(X, '__my_hidden_field', (3,1))

以上两行均引发以下错误# AttributeError: 'numpy.ndarray' object has no attribute '__my_hidden_field'

现在,从这些问题(即Attribute assignment to built-in objectCan't set attributes of object classpython: dynamically adding attributes to a built-in class)中发现的原因是Python不允许将属性动态添加到内置对象。

答案摘录:https://stackoverflow.com/a/22103924/8413477

  

这是有意禁止的,以防止对内置类型进行意外的致命更改(对您从未使用过的部分代码致命)。另外,这样做是为了防止更改影响驻留在地址空间中的不同解释器,因为内置类型(不同于用户定义的类)在所有此类解释器之间共享。

但是,所有答案都很老,我非常需要在我的研究项目中这样做。

有一个模块可以通过以下方法将方法添加到内置的Class中: https://pypi.org/project/forbiddenfruit/

但是,它不允许向每个对象添加对象/属性。

有帮助吗?

3 个答案:

答案 0 :(得分:0)

您可能想要weakref.WeakKeyDictionary。在文档中,

  

这可用于将其他数据与应用程序其他部分拥有的对象相关联,而无需向这些对象添加属性。

与属性类似,它不同于普通的dict,它允许对象在没有其他引用时被垃圾回收。

您将使用

查找字段
my_hidden_field[X]

代替

X._my_hidden_field

两个警告:首先,由于弱键可能会在没有警告的情况下随时删除,因此您不应该遍历WeakKeyDictionary。查找对象可以参考。其次,您不能对用C编写的没有插槽的对象类型(对于许多内置插件为true)或用Python编写的不允许__weakref__的对象类型进行弱引用。属性(通常归因于__slots__)。

如果这是一个问题,您可以对这些类型使用常规的dict,但是您必须自己清理它。

答案 1 :(得分:0)

快速解答

是否可以在Python中动态地向内置python对象中添加属性?

不,现在您在发布的链接中阅读的原因相同。但是我想出了一个食谱,我认为这可能是您的示踪剂的起点。

结合使用子类和AST的乐器

在阅读了很多有关此内容的内容之后,我提出了一个可能不是完整解决方案的食谱,但可以肯定,您可以从这里开始。

此菜谱的优点是它不使用第三方库,所有这些都是通过标准(Python 3.5、3.6、3.7)库实现的。

目标代码。

此食谱将对这样的代码进行检测(在这里执行简单的检测,这只是概念上的问题)并执行。

# target/target.py

d = {1: 2}
d.update({3: 4})
print(d)                 # Should print "{1: 2, 3: 4}"
print(d.hidden_field)    # Should print "(0, 0)"

子类化

首先,我们必须在要添加的内容中添加hidden_field(此食谱仅通过字典进行过测试)。

以下代码接收一个值,找出其类型/类并将其子类化,以添加所提到的hidden_field

def instrument_node(value):
    VarType = type(value)
    class AnalyserHelper(VarType):
        def __init__(self, *args, **kwargs):
            self.hidden_field = (0, 0)
            super(AnalyserHelper, self).__init__(*args, **kwargs)
    return AnalyserHelper(value)

有了这个,您就可以:

d = {1: 2}
d = instrument_node(d) 
d.update({3: 4})
print(d)                 # Do print "{1: 2, 3: 4}"
print(d.hidden_field)    # Do print "(0, 0)"

在这一点上,我们已经知道“向内置词典中添加工具”的方法 ,但是这里没有透明性

修改AST。

下一步是“隐藏” instrument_node调用,我们将使用ast Python模块进行此操作。

以下是AST节点转换器,它将使用它找到的任何词典并将其包装在instrument_node调用中:

class AnalyserNodeTransformer(ast.NodeTransformer):
    """Wraps all dicts in a call to instrument_node()"""
    def visit_Dict(self, node):
        return ast.Call(func=ast.Name(id='instrument_node', ctx=ast.Load()),
                        args=[node], keywords=[])
        return node

放在一起。

使用thats工具,您可以编写一个脚本,该脚本:

  
      
  1. 读取目标代码。
  2.   
  3. 解析程序。
  4.   
  5. 应用AST更改。
  6.   
  7. 编译它。
  8.   
  9. 执行它。
  10.   
import ast
import os
from ast_transformer import AnalyserNodeTransformer

# instrument_node need to be in the namespace here.
from ast_transformer import instrument_node

if __name__ == "__main__":

    target_path = os.path.join(os.path.dirname(__file__), 'target/target.py')

    with open(target_path, 'r') as program:
        # Read and parse the target script.
        tree = ast.parse(program.read())
        # Make transformations.
        tree = AnalyserNodeTransformer().visit(tree)
        # Fix locations.
        ast.fix_missing_locations(tree)
        # Compile and execute.
        compiled = compile(tree, filename='target.py', mode='exec')
        exec(compiled)

这将获取我们的目标代码,并用instrument_node()包装每个字典并执行这种更改的结果。

针对我们的目标代码运行此命令的输出

# target/target.py

d = {1: 2}
d.update({3: 4})
print(d)                 # Will print "{1: 2, 3: 4}"
print(d.hidden_field)    # Will print "(0, 0)"

是:

>>> {1: 2, 3: 4} 
>>> (0, 0)

工作示例

您可以克隆一个有效的示例here

答案 2 :(得分:-2)

是的,这可能是python最酷的东西之一,在Python中,所有类都是由type

创建的

您可以详细阅读here,但这是您要做的

In [58]: My_Class = type("My_Class", (My_Class,), {"__my_hidden_field__": X})

In [59]: My_Class.__my_hidden_field__
Out[59]:
array([[0.73998002, 0.68213825, 0.41621582],
       [0.05936479, 0.14348496, 0.61119082]])



*由于缺少继承而进行了编辑,因此您需要将原始类作为第二个参数(以元组形式)传递,以便其进行更新,否则它将仅重新编写该类)