Оператор перегрузки Python = [дубликат]

Если у вас есть базовый класс A и производный класс B, вы можете сделать следующее.

void wantAnA(A myA)
{
   // work with myA
}

B derived;
// work with the object "derived"
wantAnA(derived);

Теперь для метода wantAnA нужна копия derived , Однако объект derived не может быть полностью скопирован, так как класс B может изобретать дополнительные переменные-члены, которые не находятся в его базовом классе A.

Поэтому, чтобы вызвать wantAnA, компилятор будет «срезать» все дополнительные члены производного класса. Результатом может быть объект, который вы не хотите создавать, потому что

  • он может быть неполным,
  • ведет себя как объект A (все особые поведения класс B теряется).
55
задан Eric 2 September 2013 в 00:03
поделиться

10 ответов

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

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

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

2
ответ дан Community 21 August 2018 в 21:14
поделиться
  • 1
    Синглотона недостаточно, так как var = 1 не вызывает одноэлементный механизм. – Caruccio 14 June 2012 в 12:54
  • 2
    Понял. Прошу прощения, если я не понял. Синглтон предотвратит создание дополнительных экземпляров объекта (например, Protect()). Невозможно защитить первоначально назначенное имя (например, var). – jathanism 14 June 2012 в 19:11
  • 3
    – Mad Physicist 18 September 2018 в 15:13

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

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

8
ответ дан Lev Levitsky 21 August 2018 в 21:14
поделиться

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

26
ответ дан msw 21 August 2018 в 21:14
поделиться
  • 1
    Будьте уверены, этого не произойдет в Python 4.x. – Sven Marnach 14 June 2012 в 00:40
  • 2
    Теперь у меня возникает соблазн написать PEP для подклассификации и замены текущей области. – zigg 14 June 2012 в 12:55

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

import copy
global a

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

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


a = MyClass()
a = 1
1
ответ дан Nuno André 21 August 2018 в 21:14
поделиться

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

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

2
ответ дан Perkins 21 August 2018 в 21:14
поделиться
  • 1
    Это темная магия! Я полностью ожидал, что просто найду кучу ответов, где люди предлагают использовать объект явно с переопределенным множеством, не думали о переопределении глобальных и локальных пользователей с помощью настраиваемого объекта, ничего себе. Это должно заставить PyPy плакать. – Joseph Garvin 20 July 2017 в 06:19

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

1
ответ дан Ryan Kung 21 August 2018 в 21:14
поделиться
  • 1
    Я чего-то не понимаю ... Не должно быть print('called with %s' % self)? – zezollo 9 November 2017 в 17:34

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

45
ответ дан steveha 21 August 2018 в 21:14
поделиться
  • 1
    Использование @property с геттером, но нет сеттера, аналогично назначению псевдоперегрузки. – jtpereyda 16 December 2015 в 21:26
  • 2
    getattr(self, "_locked", None) вместо self.__dict__.get("_locked"). – Vedran Šego 9 April 2018 в 13:50
  • 3
    @ VedranŠego Я следовал твоему предложению, но использовал False вместо None. Теперь, если кто-то удалит переменную-член _locked, вызов .get() не вызовет исключения. – steveha 30 May 2018 в 06:09
  • 4
    @steveha Это действительно вызвало для вас исключение? get по умолчанию None, в отличие от getattr, который действительно вызвал бы исключение. – Vedran Šego 30 May 2018 в 09:08
  • 5
    Ах, нет, я не видел, чтобы это вызывало исключение. Почему-то я забыл, что вы предлагали использовать getattr(), а не .__dict__.get(). Думаю, лучше использовать getattr(), вот для чего это. – steveha 7 June 2018 в 04:44

Нет, нет

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

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

2
ответ дан Tim Hoffman 21 August 2018 в 21:14
поделиться
  • 1
    Почти готово! Я попытался перегрузить модуль __dict__.__setattr__, но module.__dict__ сам доступен только для чтения. Кроме того, введите (mymodule) == & lt; type 'module' & gt ;, и он не является экземпляром. – Caruccio 14 June 2012 в 12:53

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

output:

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

output:

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
1
ответ дан Timofey Kazantsev 21 August 2018 в 21:14
поделиться
0
ответ дан Perkins 4 November 2018 в 18:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: