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

160 投票
5 回答
75631 浏览
提问于 2025-04-11 09:34

这两种写法有什么实质性的区别吗:

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

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

如果你要创建很多实例,这两种写法在性能或占用空间上有什么不同吗?当你阅读代码时,你觉得这两种写法的含义有很大区别吗?

5 个回答

44

这里有一篇很不错的文章,下面是它的总结。

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 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
    
45

区别在于,类上的属性是所有实例共享的,而实例上的属性是独一无二的,只属于那个特定的实例。

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

171

这里有一个重要的语义区别(不仅仅是性能方面的考虑):

  • 当属性是在实例上定义(这通常是我们做的),那么可以有多个对象被引用。每个对象都有自己独立的属性版本
  • 当属性是在类上定义,那么只有一个基础对象被引用,所以如果这个类的不同实例都试图设置(添加/扩展/插入等)这个属性,那么:
    • 如果这个属性是内置类型(比如整数、浮点数、布尔值、字符串),那么一个对象上的操作会覆盖(冲掉)另一个对象的值。
    • 如果这个属性是可变类型(比如列表或字典),那么我们会遇到不想要的“泄漏”问题。

举个例子:

>>> 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    
[]

撰写回答