質問

一度初期化されたクラス(Python)を作成できるようになりたい __init__, 、新しい属性を受け入れませんが、既存の属性の変更を受け入れます。私がこれを行うために私が見ることができるいくつかのハック風の方法があります。 __setattr__ などの方法

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

そして、編集 __dict__ 内側に直接 __init__, 、しかし、私はこれを行う「適切な」方法があるかどうか疑問に思っていましたか?

役に立ちましたか?

解決

私は使用しません __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

他のヒント

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

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

実際、あなたは望んでいません __setattr__, 、あなたが欲しい __slots__. 。追加 __slots__ = ('foo', 'bar', 'baz') クラスの本体には、Pythonは、任意のインスタンスにFoo、bar、bazのみがあることを確認します。しかし、ドキュメントリストの警告を読んでください!

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

Pythonicの方法は、 __setter__. 。問題を解決するかもしれませんが、パフォーマンスの改善は得られません。オブジェクトの属性は辞書に保存されます」__dict__「これが、これまでに作成したクラスのオブジェクトに属性を動的に追加できる理由です。属性ストレージに辞書を使用することは非常に便利ですが、それは小さなものしかないオブジェクトのスペースの無駄を意味します。インスタンス変数の量。

スロット このスペース消費の問題を回避する良い方法です。オブジェクトに属性を動的に追加できる動的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'

NB:

  1. Python 3.3以来、スペース消費を最適化する利点はこれ以上印象的ではありません。 Python 3.3で キー共有 辞書は、オブジェクトの保存に使用されます。インスタンスの属性は、内部ストレージの一部を互いに共有できます。つまり、キーと対応するハッシュを保存する部分です。これにより、プログラムのメモリ消費を削減するのに役立ちます。これにより、非ビルチンタイプの多くのインスタンスが作成されます。しかし、動的に作成された属性を避けるために行く方法です。

  2. スロットを使用すると、独自のコストも付いています。シリアル化(ピクルスなど)が壊れます。また、複数の継承を破ります。クラスは、スロットを定義するか、Cコード(リスト、タプル、INTなど)で定義されているインスタンスレイアウトを帽子をかぶっている複数のクラスから継承することはできません。

適切な方法は、オーバーライドすることです __setattr__. 。それがそこにあるものです。

デコレーターを使用するソリューションは非常に気に入っています。プロジェクト全体で多くのクラスに簡単に使用できるため、各クラスに最小限の追加を加えることができます。しかし、それは継承とはうまくいきません。だから私のバージョンは次のとおりです。__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

ここに私が思いついたアプローチは、init()でフリーズ()に_frozen属性またはメソッドを必要としないことです。

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

_frozen、freeze()、および_frozenもVARS(インスタンス)出力には表示されないため、私はこれが好きです。

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

これはどうですか:

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)

私はジョッチェン・リッツェルの「冷凍」が好きです。不便なのは、 IsFrozen変数は、クラスを印刷するときに表示されます。__DICTこの方法でこの問題を回避し、承認された属性のリストを作成しました(に類似しています スロット):

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

FrozenClass ジョッチェン・リッツェルはクールですが、電話してください _frozen() 毎回クラスを初期化する場合は、それほどクールではありません(そして、それを忘れるリスクを取る必要があります)。 Aを追加しました __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

pystrictPYPIインストール可能なデコレーター クラスをフリーズするためにクラスで使用できるこのStackOverFlowの質問に触発されました。 Readmeには、プロジェクトでMypyとPylintが実行されている場合でも、このような装飾器が必要な理由を示す例があります。

pip install pystrict

次に、@Strictデコレーターを使用してください。

from pystrict import strict

@strict
class Blah
  def __init__(self):
     self.attr = 1
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top