如何创建类属性?[重复]

2025-01-03 08:40:00
admin
原创
93
摘要:问题描述:在 Python 中,我可以使用装饰器向类添加方法@classmethod。是否有类似的装饰器可以向类添加属性?我可以更好地展示我在说什么。class Example(object): the_I = 10 def __init__( self ): self.an_i = ...

问题描述:

在 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

我上面使用的语法是否可行或者是否需要更多的东西?

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


解决方案 1:

以下是我的做法:

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

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

现在一切都会好起来的。

解决方案 2:

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

解决方案 3:

[答案基于 python 3.4;元类语法在 2 中有所不同,但我认为该技术仍然有效]

您可以使用元类来实现这一点...大多数情况下。Dappawit 几乎可以实现这一点,但我认为它有一个缺陷:

class MetaFoo(type):
    @property
    def thingy(cls):
        return cls._thingy

class Foo(object, metaclass=MetaFoo):
    _thingy = 23

这会让你获得 Foo 上的 classproperty,但是有一个问题......

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 原则。但后者只是一个单行调度程序;我基本可以接受它的存在,如果你真的想要的话,你可以将它压缩为 lambda 或其他东西。

解决方案 4:

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

from django.utils.decorators import classproperty

对于 Django 4,使用:

from django.utils.functional import classproperty

解决方案 5:

我认为您可以使用元类来实现这一点。因为元类可以像类的类一样(如果这有意义的话)。我知道您可以__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__,其余操作相同。

解决方案 6:

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

我发现以下方法有效。定义一个元类,其中包含您想要的所有类属性和设置器。例如,我想要一个带有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 样式,您实际上可以在类主体内定义元类。这样它就不会在模块范围内定义。

解决方案 7:

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

解决方案 8:

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

这是最好的答案:

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

  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)

解决方案 9:

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

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设计模式。这里有一个关于在 Python 中实现共享状态的不错的 SO QA 。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用