如何制作类属性? [复制]

Posted

技术标签:

【中文标题】如何制作类属性? [复制]【英文标题】:How to make a class property? [duplicate] 【发布时间】:2011-07-08 13:31:13 【问题描述】:

在 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

我上面使用的语法是可能的还是需要更多的东西?

我想要类属性的原因是我可以延迟加载类属性,这似乎很合理。

【问题讨论】:

我不知道python...这是你要找的那种东西吗? python.org/download/releases/2.2/descrintro/#property 如果我没看错,你可以创建方法来充当 setter 和 getter(就像在其他语言中一样),这样你就可以在第一次 get 时延迟加载属性值......我认为是你想要的? @白龙。您正在查看的属性功能将属性添加到类实例,而不是类本身。我问的是Example.I 而不是e.i 这里是在另一个主题中创建类属性的解决方案:***.com/a/35640842/1113207 复制:Using property() on classmethods 【参考方案1】:

我认为您可以使用元类来做到这一点。因为元类可以像类的类(如果有意义的话)。我知道您可以为元类分配一个__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__,其余同理。

【讨论】:

所以它可以完成,但是可以用方法装饰器完成吗? 没有装饰器我知道你可以直接在你感兴趣的类上使用。但是,元类中的 property 装饰器应该可以工作......我已经编辑了我的答案以包含一个修饰的元类方法。 另见this similar question 及其答案。似乎很相似,所以它可能有一些更有用的信息:) property 这样的描述符需要在类型的字典中才能发挥它们的魔力。所以类定义中的那些主要影响类实例的行为,对类本身的行为影响最小(因为类是实例的类型)。将描述符移动到元类可以让它们在类本身上发挥作用(因为元类是类的类型)。【参考方案2】:

如果你只需要延迟加载,那么你可以有一个类初始化方法。

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

但元类方法似乎更简洁,并且具有更可预测的行为。

也许您正在寻找的是Singleton 设计模式。有 a nice SO QA 关于在 Python 中实现共享状态。

【讨论】:

【参考方案3】:

我会这样做:

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 时,setter 没有工作,因为我们正在调用 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)

现在一切都会好起来的。

【讨论】:

对于那些使用python 3的人,在类定义中,使用class Bar(metaclass= ClassPropertyMetaClass):而不是__metaclass__ = ClassPropertyMetaClass里面。 __set__函数有个小问题:type_ = type(obj)行后面需要跟if type_ == ClassPropertyMetaClass: type_ = obj(写在这行后面)。否则,它在实例级别上无法正常工作。您也可以使用包 classutilities 代替(参见 PyPi.org),它完全实现了这个逻辑。【参考方案4】:

如果您将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 将覆盖属性对象本身。

【讨论】:

【参考方案5】:

据我所知,如果不创建新的元类,就无法为类属性编写 setter。

我发现以下方法有效。使用您想要的所有类属性和设置器定义一个元类。 IE,我想要一个带有title 属性和setter 的类。这是我写的:

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 样式,您实际上可以在类主体中定义元类。这样它就没有在模块范围内定义。

【讨论】:

【参考方案6】:

[基于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(强调我的):

属性访问的默认行为是获取、设置或删除 对象字典中的属性。例如,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 原则。但后者只是一个单行调度器;我对它的存在基本没问题,如果你真的想要的话,你可以将它压缩成一个 lambda 或其他东西。

【讨论】:

这应该是最佳答案,因为它普遍适用,也适用于子类。 谢谢你千百次!现在清楚了!哇!【参考方案7】:

我碰巧想出了一个与@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

这是所有答案中最好的:

“元属性”被添加到类中,因此它仍然是实例的属性

    不需要在任何类中重新定义thingy 该属性在实例和类中都用作“类属性” 您可以灵活地自定义 _thingy 的继承方式

在我的例子中,我实际上将_thingy 定制为对每个孩子都不同,而没有在每个类中定义它(并且没有默认值):

   def __new__(mc1, name, bases, nmspc):
       nmspc.update('thingy': MetaFoo.services, '_thingy': None)
       return super(MetaFoo, mc1).__new__(mc1, name, bases, nmspc)

【讨论】:

【参考方案8】:
def _create_type(meta, name, attrs):
    type_name = f'nameType'
    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

【讨论】:

【参考方案9】:

如果你使用 Django,它有一个内置的 @classproperty 装饰器。

from django.utils.decorators import classproperty

【讨论】:

刚刚看到您添加了导入行。啊,你是对的,它只是没有记录在案!我在这里找不到任何结果:docs.djangoproject.com/en/2.2/search/?q=classproperty 现在看起来不错。 是的,确实,我偶然发现了这个......我想我在库代码库上进行了搜索,认为这应该在某个地方实现。 这看起来像下面~~~ class classproperty: def __init__(self, method=None): self.fget = method def __get__(self, instance, cls=None): return self.fget( cls) def getter(self, method): self.fget = method return self ~~~不确定是否有帮助 这已在 Django 3.1 中被删除,如果你想复制粘贴,可以在此文件底部找到 docs.djangoproject.com/en/3.0/_modules/django/utils/decorators 它没有被移除,而是移动到另一个模块。 docs.djangoproject.com/en/3.1/ref/utils/…

以上是关于如何制作类属性? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

如何删除应用于类属性的程序集? [复制]

如何按整数属性对类列表进行排序? [复制]

如何在 Python 中创建只读类属性? [复制]

如何在类扩展中添加静态(存储)属性以制作单例? (迅速)

如何创建一个新类,我的两个类都将从中继承方法和属性? [复制]

如何获取具有特定类和数据属性的选择标签的值? [复制]