Можно ли динамически добавлять атрибуты к встроенным объектам Python в Python? - PullRequest
0 голосов
/ 31 января 2019

Мне нужно динамически добавлять атрибут (содержащий кортеж или объект) к объектам 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'

Теперь, причина найдена из этих вопросов (например, Назначение атрибута)для встроенного объекта , Невозможно установить атрибуты класса объекта , python: динамическое добавление атрибутов во встроенный класс ) - это Python, не позволяющий динамически добавлять атрибутык встроенным объектам.

Выдержка из ответа: https://stackoverflow.com/a/22103924/8413477

Запрещено намеренно предотвращать случайные фатальные изменения во встроенных типах (фатальные для частей кода, которые вы никогда не использовалихотя из).Кроме того, это сделано для того, чтобы изменения не влияли на различные интерпретаторы, находящиеся в адресном пространстве, поскольку встроенные типы (в отличие от пользовательских классов) совместно используются всеми такими интерпретаторами.

Однако всеответы довольно старые, и мне очень нужно сделать это для моего исследовательского проекта.

Существует модуль, который позволяет добавлять методы во встроенный класс, хотя: https://pypi.org/project/forbiddenfruit/

Однако он не позволяет добавлять объекты / атрибуты к каждому объекту.

Любая помощь?

Ответы [ 3 ]

0 голосов
/ 31 января 2019

Вы, вероятно, хотите weakref.WeakKeyDictionary.Из документа

Это можно использовать для связывания дополнительных данных с объектом, принадлежащим другим частям приложения, без добавления атрибутов к этим объектам.

Как атрибути, в отличие от простого указания, это позволяет объектам собирать мусор, когда на него нет других ссылок.

Вы бы искали поле с помощью

my_hidden_field[X]

вместо

X._my_hidden_field

Два предостережения: во-первых, поскольку слабый ключ может быть удален в любое время без предупреждения, вы не должны выполнять итерации по WeakKeyDictionary.Хотя поиск объекта, на который у вас есть ссылка, вполне подойдет.И, во-вторых, вы не можете сделать слабую ссылку на тип объекта, написанный на C, который не имеет слота для него (верно для многих встроенных функций), или тип, написанный на Python, который не допускает атрибут __weakref__ (обычно из-за __slots__).

Если это проблема, вы можете просто использовать обычный dict для этих типов, но вам придется убирать его самостоятельно.

0 голосов
/ 31 января 2019

Быстрый ответ

Возможно ли динамическое добавление атрибутов к встроенным объектам Python в Python?

Нет, причины, по которым вы читаете ссылки, которые вы опубликовали, совпадают сейчас дни.Но я вышел с рецептом, который, как мне кажется, может стать отправной точкой вашего трейсера.

Инструментарий с использованием подклассов в сочетании с AST

После прочтения этого я получил рецепт, которыйможет быть не полным решением, но похоже, что вы можете начать отсюда.

Хорошая особенность этого рецепта в том, что он не использует сторонние библиотеки, все достигается с помощью стандарта (Python3.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

Соберите все вместе.

С помощью этих инструментов вы можете написать скрипт, который:

  1. Считать целевой код.
  2. Разобрать программу.
  3. Применить изменения AST.
  4. Скомпилируйте его.
  5. И выполните его.
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)

Рабочий пример

Вы можете клонировать рабочий пример здесь .

0 голосов
/ 31 января 2019

Да, это возможно, это одна из самых крутых вещей Python, в Python все классы создаются с помощью type class

Подробнее вы можете прочитать здесь , но вам нужно сделать следующее:

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]])



* Отредактировано из-за отсутствия наследования, вам нужно передать исходный класс в качестве второго аргумента (в кортеже), чтобы он обновлялся, в противном случае он просто перезаписывается.- пишет класс)

...