Подтвердить что ты не робот

Возможно ли перегрузить назначение Python?

Есть ли волшебный метод, который может перегрузить оператор присваивания, например __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()

Возможно ли это? Это безумие? Должен ли я быть на медицине?

4b9b3361

Ответ 1

То, как вы его описываете, абсолютно невозможно. Присвоение имени является фундаментальной особенностью 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, которая контролирует, разрешено ли присвоение. Вы можете разблокировать его, чтобы обновить значение.

Ответ 2

Нет, поскольку назначение language intrinsic, которое не имеет привязки к модификации.

Ответ 3

Я не думаю, что это возможно. То, как я его вижу, присваивание переменной ничего не делает с объектом, о котором он упоминал ранее: это просто, что переменная "указывает" на другой объект.

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__(), которые создают исключение, и сохраняют "неизменяемые" элементы внутри.

Ответ 4

Нет, нет

Подумайте об этом, в вашем примере вы пересобираете имя var на новое значение. Вы на самом деле не касаетесь экземпляра Protect.

Если имя, которое вы хотите восстановить, на самом деле является свойством какого-либо другого объекта i.e myobj.var, то вы можете запретить присваивать значение свойству/атрибуту объекта. Но я предполагаю, что это не то, что вы хотите от вашего примера.

Ответ 5

В глобальном пространстве имен это невозможно, но вы можете воспользоваться более продвинутым метапрограммированием Python, чтобы предотвратить создание нескольких экземпляров объекта Protect. Образ Singleton является хорошим примером этого.

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

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

Ответ 6

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

`var = 1`

Сохраняет ключ var и значение 1 в глобальном словаре. Это примерно эквивалентно вызову globals().__setitem__('var', 1). Проблема в том, что вы не можете заменить глобальный словарь в запущенном script (вы, вероятно, можете, войдя в стек, но это не очень хорошая идея). Однако вы можете выполнять код во вторичном пространстве имен и предоставлять пользовательский словарь для своих глобальных переменных.

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). Обратите внимание, что это не защищает от вредоносного кода.

Ответ 7

Уродливым решением является переназначение деструктора. Но это не реальное переназначение.

import copy
global a

class MyClass():
    def __init__(self):
            a = 1000
            # ...

    def __del__(self):
            a = copy.copy(self)


a = MyClass()
a = 1

Ответ 8

Да, возможно, вы можете обрабатывать __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

Ответ 9

Как правило, лучший подход, который я нашел, переопределяет __ilshift__ как сеттер и __rlshift__ как геттер, дублируется декоратором свойств. Это почти последний оператор, разрешаемый только (| & ^), а логический - ниже. Он редко используется (__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

вывод:

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'

То же самое со сдвигом:

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