__init__の外部に新しい属性を作成しないようにする


82

で初期化されると__init__、新しい属性を受け入れないが、既存の属性の変更を受け入れるクラスを(Pythonで)作成できるようにしたい。これを行うには、ハックっぽい方法がいくつかあります。たとえば、次の__setattr__ような方法があります。

def __setattr__(self, attribute, value):
    if not attribute in self.__dict__:
        print "Cannot set %s" % attribute
    else:
        self.__dict__[attribute] = value

次に、__dict__内部__init__で直接編集しますが、これを行うための「適切な」方法があるかどうか疑問に思いました。


1
katrielalexは良い点をもたらします。それについてハッキーなことは何もありません。使用を避けることもできますが__setattr__、それはおそらくハッキーでしょう。
aaronasterling 2010

なぜこれがハッキーなのかわかりませんか?それは私が思いつくことができる最良の解決策であり、他のいくつかが提案したよりもはるかに簡潔です。
クリスB

回答:


81

__dict__直接使用することはありませんが、インスタンスを明示的に「フリーズ」する関数を追加できます。

class FrozenClass(object):
    __isfrozen = False
    def __setattr__(self, key, value):
        if self.__isfrozen and not hasattr(self, key):
            raise TypeError( "%r is a frozen class" % self )
        object.__setattr__(self, key, value)

    def _freeze(self):
        self.__isfrozen = True

class Test(FrozenClass):
    def __init__(self):
        self.x = 42#
        self.y = 2**3

        self._freeze() # no new attributes after this point.

a,b = Test(), Test()
a.x = 10
b.z = 10 # fails

とてもかっこいい!私はそのコードをつかんで使い始めると思います。(うーん、デコレータとして実行できるのか、それともそれは良い考えではないのだろうか...)
weronika 2011

5
遅いコメント:属性をプロパティに変更するまで、このレシピをしばらくの間正常に使用していました。プロパティに、ゲッターがNotImplementedErrorを発生させていました。これは、実際にhasattr呼び出しgetattr、結果を破棄し、エラーが発生した場合にFalseを返すという事実が原因であることがわかるまでに長い時間がかかりました。このブログを参照してください。に置き換えるnot hasattr(self, key)ことで回避策を見つけましたkey not in dir(self)。これは遅いかもしれませんが、私にとっては問題を解決しました。
バススウィンケルズ2016

31

誰かがデコレータでそれを行うことに興味があるなら、ここに実用的な解決策があります:

from functools import wraps

def froze_it(cls):
    cls.__frozen = False

    def frozensetattr(self, key, value):
        if self.__frozen and not hasattr(self, key):
            print("Class {} is frozen. Cannot set {} = {}"
                  .format(cls.__name__, key, value))
        else:
            object.__setattr__(self, key, value)

    def init_decorator(func):
        @wraps(func)
        def wrapper(self, *args, **kwargs):
            func(self, *args, **kwargs)
            self.__frozen = True
        return wrapper

    cls.__setattr__ = frozensetattr
    cls.__init__ = init_decorator(cls.__init__)

    return cls

使用するのは非常に簡単です:

@froze_it 
class Foo(object):
    def __init__(self):
        self.bar = 10

foo = Foo()
foo.bar = 42
foo.foobar = "no way"

結果:

>>> Class Foo is frozen. Cannot set foobar = no way

デコレータバージョンの場合は+1。それは私がより大きなプロジェクトに使用するものです、より大きなスクリプトではこれはやり過ぎです(多分彼らが標準ライブラリにそれを持っていたなら...)。今のところ、「IDEスタイルの警告」のみがあります。
Tomasz Gandor 2016

2
このソリューションは遺産とどのように連携しますか?たとえば、Fooの子クラスがある場合、この子はデフォルトで凍結されたクラスですか?
mrgiesel 2016年

このデコレータ用のpypiパッケージはありますか?
winni2k 2017

継承されたクラスで機能するようにデコレータを拡張するにはどうすればよいですか?
IvanNechipayko20年

30

スロットは行く方法です:

pythonicの方法は、をいじる代わりにスロットを使用すること__setter__です。問題は解決するかもしれませんが、パフォーマンスは向上しません。オブジェクトの属性は辞書 " __dict__"に保存されます。これが、これまでに作成したクラスのオブジェクトに属性を動的に追加できる理由です。属性の保存に辞書を使用すると非常に便利ですが、インスタンス変数が少ないオブジェクトのスペースが無駄になる可能性があります。

スロットは、このスペース消費の問題を回避するための優れた方法です。スロットは、オブジェクトに属性を動的に追加できる動的な辞書を持つ代わりに、インスタンスの作成後に追加を禁止する静的な構造を提供します。

クラスを設計するとき、スロットを使用して属性の動的な作成を防ぐことができます。スロットを定義するには、という名前のリストを定義する必要があります__slots__。リストには、使用するすべての属性が含まれている必要があります。これを次のクラスで示します。このクラスでは、スロットリストに属性「val」の名前のみが含まれています。

class S(object):

    __slots__ = ['val']

    def __init__(self, v):
        self.val = v


x = S(42)
print(x.val)

x.new = "not possible"

=>属性「new」の作成に失敗しました:

42 
Traceback (most recent call last):
  File "slots_ex.py", line 12, in <module>
    x.new = "not possible"
AttributeError: 'S' object has no attribute 'new'

注意:

  1. Python 3.3以降、スペース消費を最適化する利点はそれほど印象的ではなくなりました。Python 3.3では、キー共有辞書がオブジェクトの保存に使用されます。インスタンスの属性は、内部ストレージの一部、つまりキーとそれに対応するハッシュを格納する部分を相互に共有できます。これは、非組み込み型のインスタンスを多数作成するプログラムのメモリ消費を削減するのに役立ちます。しかし、それでも動的に作成された属性を回避する方法です。
  1. スロットの使用には、それ自体のコストも伴います。シリアル化が壊れます(例:ピクルス)。また、多重継承を壊します。クラスは、スロットを定義するか、Cコードで定義されたインスタンスレイアウト(list、tuple、intなど)を持つ複数のクラスから継承することはできません。

20

実際には、あなたは望ん__setattr__でいません、あなたは望んでいます__slots____slots__ = ('foo', 'bar', 'baz')クラス本体に追加すると、Pythonは、どのインスタンスにもfoo、bar、bazのみが存在することを確認します。ただし、ドキュメントリストの注意事項をお読みください。


12
動作を使用し__slots__ますが、特にシリアル化(ピクルスなど)が壊れます...とにかく、メモリのオーバーヘッドを減らすのではなく、スロットを使用して属性の作成を制御することは通常悪い考えです...
Joe Kington

私は知っています、そして私はそれを自分で使うことを躊躇します-しかし、新しい属性を禁止するために余分な仕事をすることも通常悪い考えです;)

2
を使用すると__slots__、多重継承も中断されます。クラスは、複数のクラスから継承することができないのいずれかで定義のスロットまたは帽子インスタンスのCコードで定義されたレイアウト(のようなlisttupleまたはint)。
Feuermurmel 2012年

__slots__ピクルスが壊れた場合は、古代のピクルスプロトコルを使用しています。protocol=-1利用可能な最新のプロトコル(Python 2では2)(2003年に導入)のpickleメソッドに渡します。Python 3のデフォルトプロトコルと最新プロトコル(それぞれ3と4)は両方ともを処理し__slots__ます。
ニック・マッテオ

まあ、ほとんどの場合、私はピクルスを使って後悔することになります:benfrederickson.com/dont-pickle-your-data
ErikAronesty19年

7

適切な方法は、をオーバーライドすること__setattr__です。それがその目的です。


では、変数を設定する適切な方法は何__init__ですか?それらを__dict__直接設定するのですか?
astrofrog 2010

1
私はオーバーライドするでしょう__setattr____init__で、self.__setattr__ = <new-function-that-you-just-defined>
カトリエル2010

6
@katrielalex:__xxx__メソッドはインスタンスではなくクラスでのみ検索されるため、新しいスタイル のクラスでは機能しません。
イーサンファーマン2011年

6

デコレータを使用するソリューションは、プロジェクト全体の多くのクラスで簡単に使用でき、クラスごとに最小限の追加で使用できるため、非常に気に入っています。ただし、継承ではうまく機能しません。これが私のバージョンです。__setattr__関数をオーバーライドするだけです。属性が存在せず、呼び出し元の関数が__init__でない場合は、エラーメッセージが出力されます。

import inspect                                                                                                                             

def froze_it(cls):                                                                                                                      

    def frozensetattr(self, key, value):                                                                                                   
        if not hasattr(self, key) and inspect.stack()[1][3] != "__init__":                                                                 
            print("Class {} is frozen. Cannot set {} = {}"                                                                                 
                  .format(cls.__name__, key, value))                                                                                       
        else:                                                                                                                              
            self.__dict__[key] = value                                                                                                     

    cls.__setattr__ = frozensetattr                                                                                                        
    return cls                                                                                                                             

@froze_it                                                                                                                                  
class A:                                                                                                                                   
    def __init__(self):                                                                                                                    
        self._a = 0                                                                                                                        

a = A()                                                                                                                                    
a._a = 1                                                                                                                                   
a._b = 2 # error

4

これはどうですか:

class A():
    __allowed_attr=('_x', '_y')

    def __init__(self,x=0,y=0):
        self._x=x
        self._y=y

    def __setattr__(self,attribute,value):
        if not attribute in self.__class__.__allowed_attr:
            raise AttributeError
        else:
            super().__setattr__(attribute,value)

2

これが私が思いついたアプローチで、initでfreeze()するために_frozen属性やメソッドを必要としません。

初期化中に、すべてのクラス属性をインスタンスに追加するだけです。

_frozen、freeze()がなく、_frozenもvars(instance)出力に表示されないため、これが好きです。

class MetaModel(type):
    def __setattr__(self, name, value):
        raise AttributeError("Model classes do not accept arbitrary attributes")

class Model(object):
    __metaclass__ = MetaModel

    # init will take all CLASS attributes, and add them as SELF/INSTANCE attributes
    def __init__(self):
        for k, v in self.__class__.__dict__.iteritems():
            if not k.startswith("_"):
                self.__setattr__(k, v)

    # setattr, won't allow any attributes to be set on the SELF/INSTANCE that don't already exist
    def __setattr__(self, name, value):
        if not hasattr(self, name):
            raise AttributeError("Model instances do not accept arbitrary attributes")
        else:
            object.__setattr__(self, name, value)


# Example using            
class Dog(Model):
    name = ''
    kind = 'canine'

d, e = Dog(), Dog()
print vars(d)
print vars(e)
e.junk = 'stuff' # fails

フィールドの1つがリストの場合、これは機能しないようです。としましょうnames=[]。そして、d.names.append['Fido']挿入されます'Fido'両方にd.namese.names。理由を理解するのに十分なPythonの知識がありません。
Reinier Torenbeek 2017年

2

pystrictこのスタックオーバーフローの質問に触発されたpypiのインストール可能なデコレータであり、クラスで使用してフリーズすることができます。プロジェクトでmypyとpylintを実行している場合でも、このようなデコレータが必要な理由を示すREADMEの例があります。

pip install pystrict

次に、@ strictデコレータを使用します。

from pystrict import strict

@strict
class Blah
  def __init__(self):
     self.attr = 1

1

JochenRitzelの「Frozen」が好きです。不便なのは、Class .__ dict を出力するときにisfrozen変数が表示されることです。許可された属性のリストを作成することで、この問題を回避しました(スロットと同様)。

class Frozen(object):
    __List = []
    def __setattr__(self, key, value):
        setIsOK = False
        for item in self.__List:
            if key == item:
                setIsOK = True

        if setIsOK == True:
            object.__setattr__(self, key, value)
        else:
            raise TypeError( "%r has no attributes %r" % (self, key) )

class Test(Frozen):
    _Frozen__List = ["attr1","attr2"]
    def __init__(self):
        self.attr1   =  1
        self.attr2   =  1

1

FrozenClassヨッヘンRitzelによってはクールですが、呼び出す_frozen()たびはとてもクールされていないクラスを仮調印(そしてあなたがそれを忘れるのリスクを取る必要がある)とき。__init_slots__関数を追加しました:

class FrozenClass(object):
    __isfrozen = False
    def _freeze(self):
        self.__isfrozen = True
    def __init_slots__(self, slots):
        for key in slots:
            object.__setattr__(self, key, None)
        self._freeze()
    def __setattr__(self, key, value):
        if self.__isfrozen and not hasattr(self, key):
            raise TypeError( "%r is a frozen class" % self )
        object.__setattr__(self, key, value)
class Test(FrozenClass):
    def __init__(self):
        self.__init_slots__(["x", "y"])
        self.x = 42#
        self.y = 2**3


a,b = Test(), Test()
a.x = 10
b.z = 10 # fails
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.