类和实例属性有啥区别?

Posted

技术标签:

【中文标题】类和实例属性有啥区别?【英文标题】:What is the difference between class and instance attributes?类和实例属性有什么区别? 【发布时间】:2010-09-17 10:41:48 【问题描述】:

以下之间是否有任何有意义的区别:

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

对比

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

如果您要创建大量实例,那么这两种样式在性能或空间要求方面是否存在差异?看代码的时候,有没有觉得这两种风格的含义有很大的不同?

【问题讨论】:

我刚刚意识到在这里提出并回答了一个类似的问题:***.com/questions/206734/…我应该删除这个问题吗? 这是您的问题,请随时删除。既然是你的,何必问别人的意见呢? 【参考方案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    
[]

【讨论】:

只有可变类型是共享的。就像intstr 一样,它们仍然附加到每个实例而不是类。 @Babu:不,intstr共享的,方式完全相同。您可以使用isid 轻松检查。或者只是查看每个实例的__dict__ 和类的__dict__。不可变类型是否共享通常并不重要。 但是,请注意,如果您执行a.foo = 5,那么在这两种情况下您都会看到b.foo 返回[]。这是因为在第一种情况下,您将使用同名的新实例属性覆盖类属性 a.foo【参考方案2】:

还有一种情况。

类和实例属性是描述符

# -*- 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 definition找到了一个很棒的post。

解释

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

【讨论】:

【参考方案3】:

这里有一个很好的post,总结如下。

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 = MyClass(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

【讨论】:

【参考方案4】:

由于这里的 cmets 和其他两个标记为 dups 的问题中的人似乎都以同样的方式对此感到困惑,我认为值得在 Alex Coventry's 之上添加一个额外的答案。

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,那是为了避免遇到我不想在这里讨论的另外两个问题;对于两个不同的原因,完全单独创建的5s 最终可能是数字5 的同一个实例。但完全单独创建的object()s 不能。)


那么,为什么 Alex 的示例中的 a.foo.append(5) 会影响 b.foo,而我的示例中的 a.foo = 5 不会?好吧,试试 Alex 的示例中的a.foo = 5,并注意它不会影响那里的b.fooeither

a.foo = 5 只是将a.foo 变成5 的名称。这不会影响b.fooa.foo 曾经引用的旧值的任何其他名称。* 我们正在创建一个隐藏类属性的实例属性有点棘手,** 但是一旦你明白了,这里没有什么复杂的事情发生。


希望现在 Alex 使用列表的原因很明显:您可以改变列表这一事实意味着更容易显示两个变量命名同一个列表,也意味着在实际代码中了解您是否拥有两个列表或同一个列表的两个名称。


* 来自像 C++ 这样的语言的人的困惑是,在 Python 中,值不存储在变量中。值本身就存在于值域中,变量只是值的名称,而赋值只是为值创建一个新名称。如果有帮助,请将每个 Python 变量视为 shared_ptr&lt;T&gt; 而不是 T

** 有些人通过使用类属性作为实例可能设置或不设置的实例属性的“默认值”来利用这一点。这在某些情况下可能很有用,但也可能令人困惑,所以要小心。

【讨论】:

【参考方案5】:

区别在于类上的属性是所有实例共享的。实例上的属性对该实例是唯一的。

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

【讨论】:

不只是共享的可变类型吗?接受的答案显示了一个有效的列表,但如果它是一个 int,它似乎与实例 attr 相同:&gt;&gt;&gt; class A(object): foo = 5&gt;&gt;&gt; a, b = A(), A()&gt;&gt;&gt; a.foo = 10&gt;&gt;&gt; b.foo5 @Rafe:不,所有类型都是共享的。您感到困惑的原因是a.foo.append(5) 正在改变a.foo 所指的值,而a.foo = 5 正在使a.foo 成为值5 的新名称。因此,您最终会得到一个隐藏类属性的实例属性。在 Alex 的版本中尝试相同的 a.foo = 5,您会看到 b.foo 没有改变。

以上是关于类和实例属性有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

抽象类和静态类有啥区别?

属性和实例变量有啥区别?

Java:对象数组;本地实例和类级别实例有啥区别? [关闭]

java中接口和类有啥区别java中接口和类有啥区别

java接口与抽象类有啥区别?

使用类和接口有啥区别?