98

のように代入演算子をオーバーロードできる魔法のメソッドはあり__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()

出来ますか?それは非常識ですか?私は薬を服用する必要がありますか?

4

12 に答える 12

89

あなたがそれを説明する方法は絶対に不可能です。名前への割り当ては 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割り当てを許可するかどうかを制御するメンバー変数 があることに注意してください。ロックを解除して値を更新できます。

于 2012-06-13T23:41:14.907 に答える
32

いいえ、代入は変更フックを持たない言語組み込みであるためです。

于 2012-06-13T23:20:26.733 に答える
9

ありえないと思います。私の見方では、変数への割り当ては、以前に参照したオブジェクトには何もしません。変数が別のオブジェクトを「指している」だけです。

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__()メソッドを使用してラッパー オブジェクトを作成し、「変更不可能な」ものを内部に保持することで、目的の動作を模倣できます。

于 2012-06-13T23:20:10.400 に答える
6

最上位の名前空間を使用すると、これは不可能です。走るとき

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)。これは悪意のあるコードから保護しないことに注意してください。

于 2016-11-17T02:14:58.717 に答える
3

いいえ、ありません

あなたの例では、名前 var を新しい値に再バインドしています。Protect のインスタンスに実際に触れているわけではありません。

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

于 2012-06-13T23:32:31.830 に答える
3

__ilshift__一般的に、私が見つけた最良のアプローチは、setter およびgetter としてオーバーライドし__rlshift__、プロパティ デコレータによって複製されることです。(| & ^) だけで解決されるのはほぼ最後の演算子であり、論理は低くなります。めったに使用されません (__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
于 2018-07-22T21:28:27.913 に答える
2

グローバル名前空間ではこれは不可能ですが、より高度な Python メタプログラミングを利用して、Protectオブジェクトの複数のインスタンスが作成されるのを防ぐことができます。Singleton パターンはこの良い例です。

シングルトンの場合、インスタンス化された後は、インスタンスを参照する元の変数が再割り当てされたとしても、オブジェクトが存続することを保証します。後続のインスタンスは、同じオブジェクトへの参照を返すだけです。

このパターンにもかかわらず、グローバル変数名自体が再割り当てされるのを防ぐことはできません。

于 2012-06-13T23:48:06.690 に答える
1

醜い解決策は、デストラクタで再割り当てすることです。しかし、それは本当の過負荷割り当てではありません。

import copy
global a

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

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


a = MyClass()
a = 1
于 2016-08-26T12:55:23.807 に答える
1

他の方もおっしゃっているように、直接行う方法はありません。ただし、クラス メンバーに対してオーバーライドできるため、多くの場合に適しています。

Ryan Kung が述べたように、割り当てられたクラスが特定のメソッドを実装する場合、すべての割り当てが副作用を持つように、パッケージの AST をインストルメント化できます。オブジェクトの作成と属性の割り当てのケースを処理する彼の作業に基づいて、変更されたコードと完全な説明がここで入手できます。

https://github.com/patgolez10/assignhooks

パッケージは次のようにインストールできます。pip3 install assignhooks

例 <testmod.py>:

class SampleClass():

   name = None

   def __assignpre__(self, lhs_name, rhs_name, rhs):
       print('PRE: assigning %s = %s' % (lhs_name, rhs_name))
       # modify rhs if needed before assignment
       if rhs.name is None:
           rhs.name = lhs_name
       return rhs

   def __assignpost__(self, lhs_name, rhs_name):
       print('POST: lhs', self)
       print('POST: assigning %s = %s' % (lhs_name, rhs_name))


def myfunc(): 
    b = SampleClass()
    c = b
    print('b.name', b.name)

<test.py> のようにインストルメント化します。

import assignhooks

assignhooks.instrument.start()  # instrument from now on

import testmod

assignhooks.instrument.stop()   # stop instrumenting

# ... other imports and code bellow ...

testmod.myfunc()

生産します:

$ python3 ./test.py

POST: lhs <testmod.SampleClass object at 0x1041dcc70>
POST: assigning b = SampleClass
PRE: assigning c = b
POST: lhs <testmod.SampleClass object at 0x1041dcc70>
POST: assigning c = b
b.name b
于 2021-02-18T22:17:59.853 に答える