类属性和实例属性有什么区别?

2024-11-25 08:49:00
admin
原创
147
摘要:问题描述:以下两者之间是否存在有意义的区别:class A(object): foo = 5 # some default value 对阵class B(object): def __init__(self, foo=5): self.foo = foo 如果您要创建大量...

问题描述:

以下两者之间是否存在有意义的区别:

class A(object):
    foo = 5   # some default value

对阵

class B(object):
    def __init__(self, foo=5):
        self.foo = foo

如果您要创建大量实例,这两种样式在性能或空间要求方面是否存在差异?当您阅读代码时,您是否认为这两种样式的含义存在显著差异?


解决方案 1:

存在显著的语义差异(超出性能考虑):

  • 当属性在实例上定义时(我们通常这样做),可以引用多个对象。每个对象都会获得该属性的完全独立版本

  • 当在类上定义属性时,只有一个引用的底层对象,因此如果对该类的不同实例的操作都尝试设置/(附加/扩展/插入/等)该属性,则:

    • 如果属性是内置类型(如 int、float、boolean、string),则对一个对象的操作将覆盖(破坏)该值

    • 如果属性是可变类型(如列表或字典),我们将遭受不必要的泄漏。

例如:

>>> class A: foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo
[5]
>>> class A:
...  def __init__(self): self.foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo    
[]

解决方案 2:

不同之处在于类上的属性由所有实例共享。实例上的属性是该实例独有的。

如果来自 C++,类上的属性更像是静态成员变量。

解决方案 3:

这是一篇非常好的文章,总结如下。

class Bar(object):
    ## No need for dot syntax
    class_var = 1

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

## Need dot syntax as we've left scope of class namespace
Bar.class_var
## 1
foo = Bar(2)

## Finds i_var in foo's instance namespace
foo.i_var
## 2

## Doesn't find class_var in instance namespace…
## So look's in class namespace (Bar.__dict__)
foo.class_var
## 1

并以视觉形式呈现

在此处输入图片描述

类属性赋值

  • 如果通过访问类来设置类属性,它将覆盖所有实例的值

  foo = Bar(2)
  foo.class_var
  ## 1
  Bar.class_var = 2
  foo.class_var
  ## 2
  • 如果通过访问实例来设置类变量,它将仅覆盖该实例的值。这本质上会覆盖类变量,并将其变为直观上仅适用于该实例的实例变量。

  foo = Bar(2)
  foo.class_var
  ## 1
  foo.class_var = 2
  foo.class_var
  ## 2
  Bar.class_var
  ## 1

什么时候使用 class 属性?

  • 存储常量。由于类属性可以作为类本身的属性来访问,因此通常最好使用它们来存储类范围的、类特定的常量

  class Circle(object):
       pi = 3.14159

       def __init__(self, radius):
            self.radius = radius   
      def area(self):
           return Circle.pi * self.radius * self.radius

  Circle.pi
  ## 3.14159
  c = Circle(10)
  c.pi
  ## 3.14159
  c.area()
  ## 314.159
  • 定义默认值。举一个简单的例子,我们可以创建一个有界列表(即,只能容纳一定数量或更少元素的列表),并选择默认上限为 10 个项目

  class MyClass(object):
      limit = 10

      def __init__(self):
          self.data = []
      def item(self, i):
          return self.data[i]

      def add(self, e):
          if len(self.data) >= self.limit:
              raise Exception("Too many elements")
          self.data.append(e)

   MyClass.limit
   ## 10

解决方案 4:

由于这里的评论和另外两个标记为重复的问题中的人似乎都以同样的方式对此感到困惑,所以我认为值得在Alex Coventry 的答案之上添加一个额外的答案。

Alex 分配可变类型的值(例如列表)这一事实与事物是否共享无关。我们可以通过id函数或is运算符看到这一点:

>>> class A: foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
True
>>> class A:
...     def __init__(self): self.foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
False

(如果你想知道为什么我使用object()而不是5,那是为了避免遇到两个我不想在这里讨论的另外问题;由于两个不同的原因,完全独立创建的5最终可能成为数字 的同一个实例5。但完全独立创建的object()不能。)


那么,为什么a.foo.append(5)在 Alex 的例子中会影响b.foo,而a.foo = 5在我的例子中却不会呢?好吧,试试Alex 的例子,你会发现它a.foo = 5不会影响。b.foo

a.foo = 5只是将其变成a.foo的名称5。这不会影响b.foo,或过去引用的旧值的任何其他名称a.foo 我们正在创建一个隐藏类属性的实例属性,这有点棘手,* 但是一旦您得到它,这里就不会发生任何复杂的事情。


希望现在你能清楚地了解 Alex 使用列表的原因:你可以改变列表这一事实意味着更容易显示两个变量命名同一个列表,也意味着在实际代码中更重要的是要知道你是否有两个列表或同一个列表有两个名称。


  • 对于使用 C++ 等语言的人来说,令人困惑的是,在 Python 中,值不存储在变量中。值本身存在于值域中,变量只是值的名称,赋值只是为值创建一个新名称。如果有帮助,请将每个 Python 变量视为 而shared_ptr<T>不是T

** 有些人利用这一点,将类属性用作实例属性的“默认值”,而实例属性可以设置也可以不设置。这在某些情况下很有用,但也可能造成混淆,因此要小心使用。

解决方案 5:

还有一种情况。

类和实例属性是Descriptor

# -*- encoding: utf-8 -*-


class RevealAccess(object):
    def __init__(self, initval=None, name='var'):
        self.val = initval
        self.name = name

    def __get__(self, obj, objtype):
        return self.val


class Base(object):
    attr_1 = RevealAccess(10, 'var "x"')

    def __init__(self):
        self.attr_2 = RevealAccess(10, 'var "x"')


def main():
    b = Base()
    print("Access to class attribute, return: ", Base.attr_1)
    print("Access to instance attribute, return: ", b.attr_2)

if __name__ == '__main__':
    main()

以上将输出:

('Access to class attribute, return: ', 10)
('Access to instance attribute, return: ', <__main__.RevealAccess object at 0x10184eb50>)

同一类型的实例通过类或实例访问返回不同的结果!

我在c.PyObject_GenericGetAttr 定义中找到了一篇很棒的文章。

解释

如果在组成对象 MRO 的类的字典中找到该属性,则检查所查找的属性是否指向数据描述符(它只不过是实现__get____set__方法的类)。如果是,则通过调用__get__数据描述符的方法解析属性查找(第 28-33 行)。

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

云端的项目管理软件

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

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

内置subversion和git源码管理

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

免费试用