178

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

上記で使用した構文は可能ですか、それとももっと何かが必要ですか?

クラスプロパティが必要な理由は、クラス属性を遅延ロードできるようにするためです。これは十分に合理的と思われます。

4

9 に答える 9

115

これを行う方法は次のとおりです。

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)

今、すべてがうまくいくでしょう。

于 2011-03-04T08:12:12.960 に答える
54

次のように定義するclasspropertyと、例は要求どおりに機能します。

class classproperty(object):
    def __init__(self, f):
        self.f = f
    def __get__(self, obj, owner):
        return self.f(owner)

注意点は、これを書き込み可能なプロパティに使用できないことです。e.I = 20を発生させながらAttributeErrorExample.I = 20プロパティオブジェクト自体を上書きします。

于 2011-03-04T10:13:47.350 に答える
41

[Python3.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は記述子のサブセットであり、記述子のドキュメント(強調私のもの)から:

属性アクセスのデフォルトの動作は、オブジェクトのディクショナリから属性を取得、設定、または削除することです。たとえば、、、、で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行のディスパッチャにすぎません。私はそれが存在することで大体大丈夫です、そしてあなたが本当に望むならおそらくあなたはそれをラムダか何かに圧縮することができます。

于 2016-08-07T03:36:03.730 に答える
28

メタクラスでこれができるかもしれないと思います。メタクラスはクラスのクラスのようにすることができるので(それが理にかなっている場合)。__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

注:これはPython2.7にあります。Python 3+は、別の手法を使用してメタクラスを宣言します。使用:class MyClass(metaclass=MetaClass):、削除__metaclass__、および残りは同じです。

于 2011-03-04T04:44:15.623 に答える
24

Djangoを使用する場合は、@classpropertyデコレータが組み込まれています。

from django.utils.decorators import classproperty
于 2019-07-16T10:30:59.163 に答える
6

私の知る限り、新しいメタクラスを作成せずにクラスプロパティのセッターを作成する方法はありません。

次の方法が機能することがわかりました。必要なすべてのクラスプロパティとセッターを使用してメタクラスを定義します。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スタイルを使用している場合は、実際にクラス本体内でメタクラスを定義できます。そうすれば、モジュールスコープでは定義されません。

于 2016-02-28T20:08:26.380 に答える
2
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

bar = Bar()
assert Bar.bar==1
Bar.bar=2
assert bar.bar==2
nbar = Bar()
assert nbar.bar==2

于 2019-07-05T06:31:51.887 に答える
1

遅延読み込みのみが必要な場合は、クラス初期化メソッドを使用できます。

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で共有状態を実装することについての素晴らしいSOQAがあります。

于 2011-03-04T05:25:26.177 に答える
1

私はたまたま@Andrewと非常によく似た解決策を思いついたが、DRYだけだった。

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

これはすべての答えの中で最高です:

「metaproperty」がクラスに追加されるため、インスタンスのプロパティのままになります

  1. どのクラスでも物事を再定義する必要はありません
  2. プロパティは、インスタンスとクラスの両方で「クラスプロパティ」として機能します
  3. _thingyの継承方法を柔軟にカスタマイズできます

私の場合、実際に_thingyは、各クラスで定義せずに(デフォルト値なしで)、子供ごとに異なるようにカスタマイズしました。

   def __new__(mc1, name, bases, nmspc):
       nmspc.update({'thingy': MetaFoo.services, '_thingy': None})
       return super(MetaFoo, mc1).__new__(mc1, name, bases, nmspc)
于 2019-01-04T19:21:23.203 に答える