pythonでは、@classmethod
デコレーターを使用してクラスにメソッドを追加できます。クラスにプロパティを追加する同様のデコレータはありますか?私が話していることをよりよく示すことができます。
class Example(object):
the_I = 10
def __init__( self ):
self.an_i = 20
@property
def i( self ):
return self.an_i
def inc_i( self ):
self.an_i += 1
# is this even possible?
@classproperty
def I( cls ):
return cls.the_I
@classmethod
def inc_I( cls ):
cls.the_I += 1
e = Example()
assert e.i == 20
e.inc_i()
assert e.i == 21
assert Example.I == 10
Example.inc_I()
assert Example.I == 11
上記で使用した構文は可能ですか、それとももっと必要ですか?
クラスプロパティが必要な理由は、クラス属性を遅延ロードできるからです。
これは私がこれを行う方法です:
class ClassPropertyDescriptor(object):
def __init__(self, fget, fset=None):
self.fget = fget
self.fset = fset
def __get__(self, obj, klass=None):
if klass is None:
klass = type(obj)
return self.fget.__get__(obj, klass)()
def __set__(self, obj, value):
if not self.fset:
raise AttributeError("can't set attribute")
type_ = type(obj)
return self.fset.__get__(obj, type_)(value)
def setter(self, func):
if not isinstance(func, (classmethod, staticmethod)):
func = classmethod(func)
self.fset = func
return self
def classproperty(func):
if not isinstance(func, (classmethod, staticmethod)):
func = classmethod(func)
return ClassPropertyDescriptor(func)
class Bar(object):
_bar = 1
@classproperty
def bar(cls):
return cls._bar
@bar.setter
def bar(cls, value):
cls._bar = value
# test instance instantiation
foo = Bar()
assert foo.bar == 1
baz = Bar()
assert baz.bar == 1
# test static variable
baz.bar = 5
assert foo.bar == 5
# test setting variable on the class
Bar.bar = 50
assert baz.bar == 50
assert foo.bar == 50
Bar.bar
ではないTypeOfBar.bar.__set__
を呼び出しているため、Bar.bar.__set__
を呼び出すときにセッターが機能しませんでした。
メタクラス定義を追加すると、これが解決されます。
class ClassPropertyMetaClass(type):
def __setattr__(self, key, value):
if key in self.__dict__:
obj = self.__dict__.get(key)
if obj and type(obj) is ClassPropertyDescriptor:
return obj.__set__(self, value)
return super(ClassPropertyMetaClass, self).__setattr__(key, value)
# and update class define:
# class Bar(object):
# __metaclass__ = ClassPropertyMetaClass
# _bar = 1
# and update ClassPropertyDescriptor.__set__
# def __set__(self, obj, value):
# if not self.fset:
# raise AttributeError("can't set attribute")
# if inspect.isclass(obj):
# type_ = obj
# obj = None
# else:
# type_ = type(obj)
# return self.fset.__get__(obj, type_)(value)
これですべてが正常になります。
classproperty
を次のように定義すると、例は要求どおりに機能します。
class classproperty(object):
def __init__(self, f):
self.f = f
def __get__(self, obj, owner):
return self.f(owner)
注意点は、書き込み可能なプロパティにはこれを使用できないことです。 e.I = 20
はAttributeError
を発生させますが、Example.I = 20
はプロパティオブジェクト自体を上書きします。
メタクラスでこれを行うことができると思います。メタクラスはクラスのクラスのようなものになる可能性があるため(それが理にかなっている場合)。 __call__()
メソッドをメタクラスに割り当てて、クラスMyClass()
の呼び出しをオーバーライドできることを知っています。メタクラスでproperty
デコレータを使用しても同様に動作するかどうか疑問に思います。 (これを試したことはありませんが、今は興味があります...)
[更新:]
うわー、それは動作します:
class MetaClass(type):
def getfoo(self):
return self._foo
foo = property(getfoo)
@property
def bar(self):
return self._bar
class MyClass(object):
__metaclass__ = MetaClass
_foo = 'abc'
_bar = 'def'
print MyClass.foo
print MyClass.bar
注:これはPython 2.7にあります。 Python 3+は、異なる手法を使用してメタクラスを宣言します。使用:class MyClass(metaclass=MetaClass):
、削除__metaclass__
、その他は同じです。
[python 3.4に基づいて書かれた回答;メタクラスの構文は2で異なりますが、この手法は引き続き機能すると思います]
メタクラスを使用してこれを行うことができます...ほとんど。 Dappawitはほとんど動作しますが、欠陥があると思います。
class MetaFoo(type):
@property
def thingy(cls):
return cls._thingy
class Foo(object, metaclass=MetaFoo):
_thingy = 23
これにより、Fooのクラスプロパティが取得されますが、問題があります...
print("Foo.thingy is {}".format(Foo.thingy))
# Foo.thingy is 23
# Yay, the classmethod-property is working as intended!
foo = Foo()
if hasattr(foo, "thingy"):
print("Foo().thingy is {}".format(foo.thingy))
else:
print("Foo instance has no attribute 'thingy'")
# Foo instance has no attribute 'thingy'
# Wha....?
ここで何が起こっているのですか?インスタンスからクラスプロパティにアクセスできないのはなぜですか?
私が答えだと思うものを見つける前に、私はこれについてかなり頭を痛めていました。 Python @propertiesは記述子のサブセットであり、 descriptor documentation (emphasis mine)から:
属性アクセスのデフォルトの動作は、オブジェクトの辞書から属性を取得、設定、または削除することですたとえば、
a.x
には、a.__dict__['x']
で始まりtype(a).__dict__['x']
で始まり、type(a)
の基本クラスを含むメタクラスを除くルックアップチェーンがあります。 。
そのため、メソッドの解決順序には、クラスプロパティ(またはメタクラスで定義された他のもの)は含まれません。 動作が異なるビルトインプロパティデコレータのサブクラスを作成することは可能ですが、(引用が必要です)開発者には正当な理由があるという印象があります(私は理解していません)その方法でそれを行うため。
だからといって、私たちが運が悪いというわけではありません。クラス自体のプロパティに問題なくアクセスできます...そして、クラス内のtype(self)
から@propertyディスパッチャを作成するために使用できます:
class Foo(object, metaclass=MetaFoo):
_thingy = 23
@property
def thingy(self):
return type(self).thingy
これで、Foo().thingy
はクラスとインスタンスの両方で意図したとおりに動作します!また、派生クラスがその基礎となる_thingy
(元々このハントで私を捕らえたユースケース)を置き換える場合にも正しいことをし続けます。
これは100%満足のいくものではありません。メタクラスとオブジェクトクラスの両方でセットアップを行う必要があるため、DRYの原則に違反しているように感じます。ただし、後者は1行のディスパッチャにすぎません。私はほとんどそれが存在することで大丈夫です、そしてあなたはおそらくあなたが本当に望むならラムダまたは何かにそれを圧縮することができました。
私が知る限り、新しいメタクラスを作成せずにクラスプロパティのセッターを記述する方法はありません。
次の方法が機能することがわかりました。必要なすべてのクラスプロパティとセッターでメタクラスを定義します。 IE、セッターを持つtitle
プロパティを持つクラスが欲しかった。ここに私が書いたものがあります:
class TitleMeta(type):
@property
def title(self):
return getattr(self, '_title', 'Default Title')
@title.setter
def title(self, title):
self._title = title
# Do whatever else you want when the title is set...
上記で作成したメタクラスを使用することを除いて、実際のクラスを通常どおり作成します。
# Python 2 style:
class ClassWithTitle(object):
__metaclass__ = TitleMeta
# The rest of your class definition...
# Python 3 style:
class ClassWithTitle(object, metaclass = TitleMeta):
# Your class definition...
単一クラスでのみ使用する場合、上記のようにこのメタクラスを定義するのは少し奇妙です。その場合、Python 2スタイルを使用している場合、クラス本体内でメタクラスを実際に定義できます。そのように、それはモジュールスコープで定義されていません。
Djangoを使用する場合は、@classproperty
デコレータが組み込まれています。
from Django.utils.decorators import classproperty
遅延読み込みのみが必要な場合は、クラスの初期化メソッドを使用するだけで済みます。
EXAMPLE_SET = False
class Example(object):
@classmethod
def initclass(cls):
global EXAMPLE_SET
if EXAMPLE_SET: return
cls.the_I = 'ok'
EXAMPLE_SET = True
def __init__( self ):
Example.initclass()
self.an_i = 20
try:
print Example.the_I
except AttributeError:
print 'ok class not "loaded"'
foo = Example()
print foo.the_I
print Example.the_I
しかし、メタクラスのアプローチはよりクリーンで、より予測可能な動作を備えています。
おそらくあなたが探しているのは、 シングルトン デザインパターンです。 Pythonでの共有状態の実装について a Nice SO QA があります。
def _create_type(meta, name, attrs):
type_name = f'{name}Type'
type_attrs = {}
for k, v in attrs.items():
if type(v) is _ClassPropertyDescriptor:
type_attrs[k] = v
return type(type_name, (meta,), type_attrs)
class ClassPropertyType(type):
def __new__(meta, name, bases, attrs):
Type = _create_type(meta, name, attrs)
cls = super().__new__(meta, name, bases, attrs)
cls.__class__ = Type
return cls
class _ClassPropertyDescriptor(object):
def __init__(self, fget, fset=None):
self.fget = fget
self.fset = fset
def __get__(self, obj, owner):
if self in obj.__dict__.values():
return self.fget(obj)
return self.fget(owner)
def __set__(self, obj, value):
if not self.fset:
raise AttributeError("can't set attribute")
return self.fset(obj, value)
def setter(self, func):
self.fset = func
return self
def classproperty(func):
return _ClassPropertyDescriptor(func)
class Bar(metaclass=ClassPropertyType):
__bar = 1
@classproperty
def bar(cls):
return cls.__bar
@bar.setter
def bar(cls, value):
cls.__bar = value
私はたまたま@Andrewに非常によく似たソリューションを思いついた。
class MetaFoo(type):
def __new__(mc1, name, bases, nmspc):
nmspc.update({'thingy': MetaFoo.thingy})
return super(MetaFoo, mc1).__new__(mc1, name, bases, nmspc)
@property
def thingy(cls):
if not inspect.isclass(cls):
cls = type(cls)
return cls._thingy
@thingy.setter
def thingy(cls, value):
if not inspect.isclass(cls):
cls = type(cls)
cls._thingy = value
class Foo(metaclass=MetaFoo):
_thingy = 23
class Bar(Foo)
_thingy = 12
これはすべての答えの中で最高です:
「メタプロパティ」がクラスに追加されるため、インスタンスのプロパティのままです。
私の場合、実際に_thingy
をカスタマイズして、各クラスで定義せずに(そしてデフォルト値なしで)子ごとに異なるようにしました:
def __new__(mc1, name, bases, nmspc):
nmspc.update({'thingy': MetaFoo.services, '_thingy': None})
return super(MetaFoo, mc1).__new__(mc1, name, bases, nmspc)