web-dev-qa-db-ja.com

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

出来ますか?狂ってる?薬を飲むべきですか?

64
Caruccio

あなたがそれを記述する方法は絶対に不可能です。名前への割り当てはPythonの基本的な機能であり、その動作を変更するためのフックは提供されていません。

ただし、クラスインスタンスのメンバーへの割り当てcanは、.__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があることに注意してください。ロックを解除して値を更新できます。

55
steveha

いいえ、割り当ては 言語組み込み であるため、変更フックはありません。

26
msw

可能だとは思いません。私が見ているように、変数への代入は、以前に参照したオブジェクトには何もしません。変数が現在別のオブジェクトを「指している」だけです。

_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

トップレベルの名前空間を使用すると、これは不可能です。走るとき

_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)を使用することもできます。悪意のあるコードから保護しません。

3
Perkins

一般的に、私が見つけた最良のアプローチは、__ilshift__セッターとして__rlshift__ getterとして、プロパティデコレータによって複製されます。これはほとんど最後に解決される演算子(|&^)であり、論理値は低くなります。めったに使用されない(__lrshift__は少ないですが、考慮に入れることができます。

PyPi assignパッケージを使用すると、前方割り当てのみを制御できるため、オペレーターの実際の「強度」は低くなります。 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
2

いいえ、ありません

考えてみてください。この例では、varという名前を新しい値に再バインドしています。実際には保護のインスタンスに触れていません。

再バインドする名前が実際には他のエンティティ(myobj.varなど)のプロパティである場合、エンティティのプロパティ/属性に値を割り当てないようにすることができます。しかし、それはあなたがあなたの例から望んでいるものではないと思います。

2
Tim Hoffman

モジュール内部では、これは少しの暗黒魔法によって絶対に可能です。

import sys
tst = sys.modules['tst']

class Protect():
  def __assign__(self, value):
    raise Exception("This is an ex-parrot")

var = Protect()  # once assigned...

Module = type(tst)
class ProtectedModule(Module):
  def __setattr__(self, attr, val):
    exists = getattr(self, attr, None)
    if exists is not None and hasattr(exists, '__assign__'):
      exists.__assign__(val)
    super().__setattr__(attr, val)

tst.__class__ = ProtectedModule

モジュール内からであっても、クラスの変更が発生すると、保護された変数に書き込むことができないことに注意してください。上記の例では、コードがtstという名前のモジュールにあると想定しています。 repltst__main__に変更することでこれを行うことができます。

1
Perkins

い解決策は、デストラクタに再割り当てすることです。しかし、それは本当のオーバーロードの割り当てではありません。

import copy
global a

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

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


a = MyClass()
a = 1
1
ecn

はい、可能です。__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