__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__
内で直接編集しますが、これを行う「適切な」方法があるかどうか疑問に思っていましたか?
私は直接__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のみが存在することが確認されます。ただし、ドキュメントリストの警告を読んでください!
Pythonの方法は、__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:
Python 3.3以降、スペース消費を最適化する利点はそれほど印象的ではありません。Python 3.3 Key-Sharing 辞書を使用するとオブジェクトのストレージ用。インスタンスの属性は、内部ストレージの一部、つまりキーとそれに対応するハッシュを格納する部分を相互に共有できます。これにより、多くのインスタンスを作成するプログラムのメモリ消費を削減できます。非組み込み型のタイプですが、動的に作成される属性を回避する方法はまだあります。
スロットの使用にはそれ自身の費用も伴います。シリアル化が失敗します(例:pickle)。また、多重継承も解除されます。クラスは、スロットを定義する、またはCコードで定義されたインスタンスレイアウト(list、Tuple、または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
これはどうですか:
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)
ここで私が思いついたアプローチは、_frozen属性またはinitのfreeze()メソッドを必要としません。
initの間、すべてのクラス属性をインスタンスに追加します。
_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
Jochen Ritzelの "Frozen"が好きです。不便なのは、isfrozen変数がClass .__ dictの出力時に表示される許可された属性のリストを作成することで、この問題を回避した(slotsと同様):
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
Jochen RitzelによるFrozenClass
はかっこいいですが、毎回クラスを初期化するときに_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