クラスメソッドでのproperty()の使用


173

基本的に静的変数を取得および設定するための2つのクラスメソッド(classmethod()関数を使用)を持つクラスがあります。これらでproperty()関数を使用しようとしましたが、エラーになります。インタプリタで次のようにしてエラーを再現できました:

class Foo(object):
    _var = 5
    @classmethod
    def getvar(cls):
        return cls._var
    @classmethod
    def setvar(cls, value):
        cls._var = value
    var = property(getvar, setvar)

クラスメソッドを示すことはできますが、プロパティとしては機能しません。

>>> f = Foo()
>>> f.getvar()
5
>>> f.setvar(4)
>>> f.getvar()
4
>>> f.var
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: 'classmethod' object is not callable
>>> f.var=5
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: 'classmethod' object is not callable

classmethodで装飾された関数でproperty()関数を使用することは可能ですか?

回答:


90

プロパティはクラスに作成されますが、インスタンスに影響します。したがって、クラスメソッドプロパティが必要な場合は、メタクラスにプロパティを作成します。

>>> class foo(object):
...     _var = 5
...     class __metaclass__(type):  # Python 2 syntax for metaclasses
...         pass
...     @classmethod
...     def getvar(cls):
...         return cls._var
...     @classmethod
...     def setvar(cls, value):
...         cls._var = value
...     
>>> foo.__metaclass__.var = property(foo.getvar.im_func, foo.setvar.im_func)
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3

しかし、とにかくメタクラスを使用しているので、クラスメソッドをそこに移動するだけで読みやすくなります。

>>> class foo(object):
...     _var = 5
...     class __metaclass__(type):  # Python 2 syntax for metaclasses
...         @property
...         def var(cls):
...             return cls._var
...         @var.setter
...         def var(cls, value):
...             cls._var = value
... 
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3

または、Python 3のmetaclass=...構文と、fooクラス本体の外部で定義されたメタクラス、および初期値の設定を担当するメタクラスを使用します_var

>>> class foo_meta(type):
...     def __init__(cls, *args, **kwargs):
...         cls._var = 5
...     @property
...     def var(cls):
...         return cls._var
...     @var.setter
...     def var(cls, value):
...         cls._var = value
...
>>> class foo(metaclass=foo_meta):
...     pass
...
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3

1
これは、Python 3.2では動作しないようです。foo .__ metaclass __。var = property(foo.getvar.im_func、foo.setvar.im_func)をfoo .__ metaclass __。var = property(foo.getvar .__ func__、foo.setvar .__ func__)に変更すると、「AttributeError:type 「foo.var」を実行すると、オブジェクト「foo」に属性「var」がありません。
マイケルケリー

SIGH二重修正:これはPython 2.7では機能しますが、Python 3.2では機能しません。
Michael Kelley

@MichaelKelley- Python 3.x
Mac

1
私は理解するのがよくわかりません、それを書くためのPython 3.xの方法は何でしょうか?
SylvainD 2014年

8
@Josay:最初にメタクラスを定義してから、新しいclass Foo(metaclass=...)構文を使用してクラスを定義する必要があります。
Kevin

69

Python 2.2のリリースノートを読んだところ、次のことがわかりました。

[プロパティの] getメソッドは、プロパティがインスタンス属性(C()。x)ではなくクラス属性(Cx)としてアクセスされた場合は呼び出されません。クラス属性として使用するときにプロパティの__get__オペレーションをオーバーライドする場合は、プロパティをサブクラス化することができます-これは新しいスタイルのタイプです-その__get__メソッドを拡張するか、または新しいタイプを作成して記述子タイプを最初から定義できます__get __、__ set__、および__delete__メソッドを定義する-styleクラス。

注:以下のメソッドは実際にはセッターでは機能せず、ゲッターでのみ機能します。

したがって、規定された解決策はClassPropertyをプロパティのサブクラスとして作成することだと思います。

class ClassProperty(property):
    def __get__(self, cls, owner):
        return self.fget.__get__(None, owner)()

class foo(object):
    _var=5
    def getvar(cls):
        return cls._var
    getvar=classmethod(getvar)
    def setvar(cls,value):
        cls._var=value
    setvar=classmethod(setvar)
    var=ClassProperty(getvar,setvar)

assert foo.getvar() == 5
foo.setvar(4)
assert foo.getvar() == 4
assert foo.var == 4
foo.var = 3
assert foo.var == 3

ただし、セッターは実際には機能しません。

foo.var = 4
assert foo.var == foo._var # raises AssertionError

foo._var が変更されていない場合は、プロパティを新しい値で上書きしただけです。

ClassPropertyデコレータとして使用することもできます:

class foo(object):
    _var = 5

    @ClassProperty
    @classmethod
    def var(cls):
        return cls._var

    @var.setter
    @classmethod
    def var(cls, value):
        cls._var = value

assert foo.var == 5

20
ClassPropertyのセッター部分が実際に説明どおりに機能するとは思いません。例のアサーションはすべて成功しますが、最後にfoo._var == 4です(暗黙的に3ではありません)。プロパティを設定すると、プロパティ自体が上書きされます。クラスプロパティがpython-dev
Gabriel Grant

4
@Gabriel完全に正しい。2年間、誰もそれを指摘しなかったなんて信じられない。
agf

また、ここでself.fget(owner)aを使用する必要があるだけでなく、使用する必要がない理由もわかりません@classmethod。(それclassmethod 、仲介者を介して、呼び出しに変換さ.__get__(instance, owner)(*args, **kwargs)function(owner, *args, **kwargs)ます。プロパティは仲介者を必要としません)。
Martijn Pieters

デモンストレーションでは、ゲッターまたはセッターのいずれにも実際の変換が欠けており、foo.var = 3割り当てが実際にはpropertyを通過せず、代わりにプロパティオブジェクトがfoo整数に置き換えられているだけです。assert isinstance(foo.__dict__['var'], ClassProperty)アサーションの間に呼び出しを追加した場合、foo.var = 3が実行された後に失敗することがわかります。
Martijn Pieters

1
Pythonのクラスは、設定上のバインディング記述子をサポートしていないクラス自体にのみ得る上で、(そうinstance.attrinstance.attr = valueおよびdel instance.attrすべてのバインド記述子が上の発見しますtype(instance)が、しばらくclassobj.attrバインド、classobj.attr = valueおよびdel classobj.attrないいない記述子オブジェクト自体を交換するか、削除し、代わりに)。設定と削除をサポートするにはメタクラスが必要です(クラスオブジェクトをインスタンスにし、メタクラスをタイプにします)。
Martijn Pieters

56

この@classproperty完全にシンプルな読み取り専用のデコレータが、クラスプロパティを探している誰かを助けることを願っています。

class classproperty(object):

    def __init__(self, fget):
        self.fget = fget

    def __get__(self, owner_self, owner_cls):
        return self.fget(owner_cls)

class C(object):

    @classproperty
    def x(cls):
        return 1

assert C.x == 1
assert C().x == 1

2
これはサブクラスで機能しますか?(サブクラスはclasspropertiesをオーバーライドできますか?)
ザクダンス2013

1
うんうん?class D(C): x = 2; assert D.x == 2
dtheodor 2014

私はそれを次の.formatように使用したときにこれがうまくいくことを願っています"{x}".format(**dict(self.__class__.__dict__, **self.__dict__)):(
2rs2ts

@Nathanだけでなく...設定するとx、によるすべてのアクセスが上書きされ 10ます。きちんとシンプルですが、アンチパターンのように聞こえるので、このアプローチが好きです
Michele d'Amico

簡単に修正:オーバーライド__set__するのValueErrorを防ぐためにを発生させるを追加します。
キランジョナラガッダ2018

30

classmethodで装飾された関数でproperty()関数を使用することは可能ですか?

番号。

ただし、classmethodは、そのクラスのインスタンスからアクセス可能なクラスのバインドされたメソッド(部分関数)にすぎません。

インスタンスはクラスの関数であり、インスタンスからクラスを派生させることができるため、次のコマンドを使用すると、クラスプロパティから必要な動作を得ることができますproperty

class Example(object):
    _class_property = None
    @property
    def class_property(self):
        return self._class_property
    @class_property.setter
    def class_property(self, value):
        type(self)._class_property = value
    @class_property.deleter
    def class_property(self):
        del type(self)._class_property

このコードはテストに使用できます-エラーを発生させることなくパスするはずです:

ex1 = Example()
ex2 = Example()
ex1.class_property = None
ex2.class_property = 'Example'
assert ex1.class_property is ex2.class_property
del ex2.class_property
assert not hasattr(ex1, 'class_property')

また、メタクラスはまったく必要ないことに注意してください。クラスのインスタンスを通じてメタクラスに直接アクセスすることはありません。

@classpropertyデコレータを書く

実際にclasspropertyは、サブクラス化propertyすることで、ほんの数行のコードでデコレーターを作成できます(Cで実装されていますが、同等のPythonがここにあります)。

class classproperty(property):
    def __get__(self, obj, objtype=None):
        return super(classproperty, self).__get__(objtype)
    def __set__(self, obj, value):
        super(classproperty, self).__set__(type(obj), value)
    def __delete__(self, obj):
        super(classproperty, self).__delete__(type(obj))

次に、デコレータをプロパティと組み合わせたクラスメソッドであるかのように扱います。

class Foo(object):
    _bar = 5
    @classproperty
    def bar(cls):
        """this is the bar attribute - each subclass of Foo gets its own.
        Lookups should follow the method resolution order.
        """
        return cls._bar
    @bar.setter
    def bar(cls, value):
        cls._bar = value
    @bar.deleter
    def bar(cls):
        del cls._bar

そして、このコードはエラーなしで機能するはずです:

def main():
    f = Foo()
    print(f.bar)
    f.bar = 4
    print(f.bar)
    del f.bar
    try:
        f.bar
    except AttributeError:
        pass
    else:
        raise RuntimeError('f.bar must have worked - inconceivable!')
    help(f)  # includes the Foo.bar help.
    f.bar = 5

    class Bar(Foo):
        "a subclass of Foo, nothing more"
    help(Bar) # includes the Foo.bar help!
    b = Bar()
    b.bar = 'baz'
    print(b.bar) # prints baz
    del b.bar
    print(b.bar) # prints 5 - looked up from Foo!

    
if __name__ == '__main__':
    main()

しかし、これがどれだけ適切にアドバイスされるかはわかりません。古いメーリングリストの記事は、それが機能してはならないことを示唆しています。

プロパティをクラスで機能させる:

上記の欠点は、「クラスプロパティ」がクラスからアクセスできないことです。これは、単にクラスのデータ記述子を上書きするだけだから__dict__です。

ただし、メタクラスで定義されたプロパティでこれをオーバーライドできます__dict__。例えば:

class MetaWithFooClassProperty(type):
    @property
    def foo(cls):
        """The foo property is a function of the class -
        in this case, the trivial case of the identity function.
        """
        return cls

そして、メタクラスのクラスインスタンスには、前のセクションですでに説明した原則を使用してクラスのプロパティにアクセスするプロパティを含めることができます。

class FooClassProperty(metaclass=MetaWithFooClassProperty):
    @property
    def foo(self):
        """access the class's property"""
        return type(self).foo

そして今、両方のインスタンスが表示されます

>>> FooClassProperty().foo
<class '__main__.FooClassProperty'>

そしてクラス

>>> FooClassProperty.foo
<class '__main__.FooClassProperty'>

クラスプロパティにアクセスできます。


3
明確、簡潔、完全:これは受け入れられる答えであるはずです。
pfabri

25

Python 3!

古い質問、たくさんのビュー、本当に真のPython 3の方法が必要です。

幸い、metaclasskwarg を使用すると簡単です。

class FooProperties(type):

    @property
    def var(cls):
        return cls._var

class Foo(object, metaclass=FooProperties):
    _var = 'FOO!'

そして、 >>> Foo.var

「FOO!」


1
つまり、箱から出して簡単な方法はないということです
mehmet

@mehmetこれは簡単ではありませんか?Fooはそのメタクラスのインスタンスであり、のインスタンスの場合と@property同様に、そのメソッドに使用できますFoo
OJFord 2018

2
クラスに別のクラスを定義する必要がありました。これは、メタクラスが再利用可能でないと仮定すると、複雑さが2倍になります。
mehmet

クラスメソッドは、クラスとインスタンスの両方から機能します。このプロパティは、クラスからのみ機能します。これは求められていることではないと思います。
アーロンホール

1
@AaronHallそれが重要な場合は、に簡単に追加できFoo.__new__ます。その時点では、代わりにgetattributeを使用するか、言語機能のふりをするかどうかを質問することは、実際に実行したいアプローチです。
OJFord

16

この「クラスプロパティ」システムをPythonで動作させるための合理的な方法はありません。

これを機能させるための不合理な方法の1つを次に示します。メタクラスマジックの量を増やすことで、よりシームレスにすることができます。

class ClassProperty(object):
    def __init__(self, getter, setter):
        self.getter = getter
        self.setter = setter
    def __get__(self, cls, owner):
        return getattr(cls, self.getter)()
    def __set__(self, cls, value):
        getattr(cls, self.setter)(value)

class MetaFoo(type):
    var = ClassProperty('getvar', 'setvar')

class Foo(object):
    __metaclass__ = MetaFoo
    _var = 5
    @classmethod
    def getvar(cls):
        print "Getting var =", cls._var
        return cls._var
    @classmethod
    def setvar(cls, value):
        print "Setting var =", value
        cls._var = value

x = Foo.var
print "Foo.var = ", x
Foo.var = 42
x = Foo.var
print "Foo.var = ", x

問題の結び目は、プロパティがPythonが「記述子」と呼ぶものであることです。この種のメタプログラミングがどのように機能するかを説明する短くて簡単な方法はないので、記述子howtoを紹介する必要があります。

この種のことを理解する必要があるのは、かなり高度なフレームワークを実装している場合だけです。透過的なオブジェクトの永続化やRPCシステム、または一種のドメイン固有の言語のようなものです。

ただし、以前の回答に対するコメントでは、

クラスのすべてのインスタンスで認識されるような方法で、およびこれらのクラスメソッドの呼び出し元のスコープで、クラスのすべてのインスタンスへの参照を持たない属性を変更する必要があります。

私には、あなたが本当に望んでいるのはオブザーバーのデザインパターンです。


コード例のアイデアは気に入っていますが、実際には少し扱いに​​くいようです。
マークロディ

私が達成しようとしているのは、非常に単純な設定と、すべてのインスタンスの動作を変更するためのフラグとして使用される単一の属性を取得することです。問題の属性が複数ある場合、私はもっと傾倒します。
マークロディ

関数を公開して直接呼び出すだけのシンプルなソリューションだったようです。私が何か間違ったことをしているのか、それをしようとしていたのかが不可能であるのか、私は興味を持ちました。ちなみに複数のコメントについては申し訳ありません。300文字の制限は最悪です。
マークロディ

コード例の気の利いたことは、すべての不格好なビットを一度実装してから継承できることです。_varを派生クラスに移動するだけです。クラスD1(Foo):_var = 21クラスD2(Foo)_var = "Hello" D1.var 21 D2.var Hello
Thomas L

6

インスタンス化されたオブジェクトを介してクラスプロパティにアクセスする場合、メタクラスでのみ設定しても効果がありません。この場合、オブジェクトに通常のプロパティもインストールする必要があります(クラスプロパティにディスパッチします)。以下はもう少し明確だと思います:

#!/usr/bin/python

class classproperty(property):
    def __get__(self, obj, type_):
        return self.fget.__get__(None, type_)()

    def __set__(self, obj, value):
        cls = type(obj)
        return self.fset.__get__(None, cls)(value)

class A (object):

    _foo = 1

    @classproperty
    @classmethod
    def foo(cls):
        return cls._foo

    @foo.setter
    @classmethod
    def foo(cls, value):
        cls.foo = value

a = A()

print a.foo

b = A()

print b.foo

b.foo = 5

print a.foo

A.foo = 10

print b.foo

print A.foo

3

ソリューションの半分、クラスの__set__はまだ機能しません。ソリューションは、プロパティと静的メソッドの両方を実装するカスタムプロパティクラスです。

class ClassProperty(object):
    def __init__(self, fget, fset):
        self.fget = fget
        self.fset = fset

    def __get__(self, instance, owner):
        return self.fget()

    def __set__(self, instance, value):
        self.fset(value)

class Foo(object):
    _bar = 1
    def get_bar():
        print 'getting'
        return Foo._bar

    def set_bar(value):
        print 'setting'
        Foo._bar = value

    bar = ClassProperty(get_bar, set_bar)

f = Foo()
#__get__ works
f.bar
Foo.bar

f.bar = 2
Foo.bar = 3 #__set__ does not

3

属性を変更する必要があるのは、クラスのすべてのインスタンスで認識されるような方法で、これらのクラスメソッドの呼び出し元のスコープで、クラスのすべてのインスタンスへの参照を持たないためです。

クラスの少なくとも1つのインスタンスにアクセスできますか?私はそれを行う方法を考えることができます:

class MyClass (object):
    __var = None

    def _set_var (self, value):
        type (self).__var = value

    def _get_var (self):
        return self.__var

    var = property (_get_var, _set_var)

a = MyClass ()
b = MyClass ()
a.var = "foo"
print b.var

2

これを試してみてください、それは多くの既存のコードを変更/追加する必要なしに仕事を成し遂げます。

>>> class foo(object):
...     _var = 5
...     def getvar(cls):
...         return cls._var
...     getvar = classmethod(getvar)
...     def setvar(cls, value):
...         cls._var = value
...     setvar = classmethod(setvar)
...     var = property(lambda self: self.getvar(), lambda self, val: self.setvar(val))
...
>>> f = foo()
>>> f.var
5
>>> f.var = 3
>>> f.var
3

property関数は二つの必要callableな引数を。ラムダラッパー(インスタンスを最初の引数として渡すラッパー)を渡せば問題ありません。


FlorianBöschが指摘するように、(サードパーティのライブラリまたはレガシーコードで)必要な構文はfoo.varです。
Thomas L Holaday、

2

これは、クラス経由のアクセスと、メタクラスを使用するインスタンス経由のアクセスの両方で機能するソリューションです。

In [1]: class ClassPropertyMeta(type):
   ...:     @property
   ...:     def prop(cls):
   ...:         return cls._prop
   ...:     def __new__(cls, name, parents, dct):
   ...:         # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
   ...:         dct['__getattr__'] = classmethod(lambda cls, attr: getattr(cls, attr))
   ...:         dct['__setattr__'] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
   ...:         return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)
   ...:

In [2]: class ClassProperty(object):
   ...:     __metaclass__ = ClassPropertyMeta
   ...:     _prop = 42
   ...:     def __getattr__(self, attr):
   ...:         raise Exception('Never gets called')
   ...:

In [3]: ClassProperty.prop
Out[3]: 42

In [4]: ClassProperty.prop = 1
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-4-e2e8b423818a> in <module>()
----> 1 ClassProperty.prop = 1

AttributeError: can't set attribute

In [5]: cp = ClassProperty()

In [6]: cp.prop
Out[6]: 42

In [7]: cp.prop = 1
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-7-e8284a3ee950> in <module>()
----> 1 cp.prop = 1

<ipython-input-1-16b7c320d521> in <lambda>(cls, attr, val)
      6         # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
      7         dct['__getattr__'] = classmethod(lambda cls, attr: getattr(cls, attr))
----> 8         dct['__setattr__'] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
      9         return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)

AttributeError: can't set attribute

これは、メタクラスで定義されたセッターでも機能します。


1

さまざまな場所を検索した後、Python 2および3で有効なクラスプロパティを定義する方法を見つけました。

from future.utils import with_metaclass

class BuilderMetaClass(type):
    @property
    def load_namespaces(self):
        return (self.__sourcepath__)

class BuilderMixin(with_metaclass(BuilderMetaClass, object)):
    __sourcepath__ = 'sp'        

print(BuilderMixin.load_namespaces)

これが誰かを助けることができることを願っています:)


1
この方法がどこかで見つけたものである場合、リンクを提供することをお勧めします(他のユーザーが作成した資料を参照する方法を参照
Andrew Myers

-27

これが私の提案です。クラスメソッドを使用しないでください。

真剣に。

この場合にクラスメソッドを使用する理由は何ですか?普通のクラスの普通のオブジェクトを持っていないのはなぜですか?


値を変更したいだけの場合、プロパティはあまり役に立ちませんか?属性値を設定し、それで完了です。

プロパティは、隠蔽するものがある場合にのみ使用する必要があります-将来の実装で変更される可能性があるもの。

たぶんあなたの例はすっかり取り除かれ、あなたがやめたいくつかの地獄の計算があります。ただし、このプロパティが重要な価値を追加するようには見えません。

Javaの影響を受けた「プライバシー」手法(Pythonでは、_で始まる属性名)はあまり役に立ちません。誰からのプライベート?(Pythonで行うように)ソースがある場合、プライベートのポイントは少し曖昧です。

Javaの影響を受けたEJBスタイルのゲッターとセッター(多くの場合、Pythonではプロパティとして行われます)は、Javaのプリミティブイントロスペクションを容易にし、静的言語コンパイラでマスターを渡すためにあります。これらのゲッターとセッターはすべて、Pythonではそれほど役に立ちません。


14
属性を変更する必要があるのは、クラスのすべてのインスタンスで認識されるような方法で、これらのクラスメソッドの呼び出し元のスコープで、クラスのすべてのインスタンスへの参照を持たないためです。
マークロディ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.