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

发布于 2024-07-06 14:07:21 字数 274 浏览 11 评论 0原文

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

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

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

如果您创建大量实例,两种样式的性能或空间要求是否有任何差异? 当你阅读代码时,你是否认为两种风格的含义有显着不同?

Is there any meaningful distinction between:

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

vs.

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

If you're creating a lot of instances, is there any difference in performance or space requirements for the two styles? When you read the code, do you consider the meaning of the two styles to be significantly different?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

铁轨上的流浪者 2024-07-13 14:07:21

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

  • 当在实例上定义属性时(这是我们通常所做的),可以引用多个对象。 每个属性都有一个完全独立的版本
  • 当在类上定义属性时,仅引用一个底层对象,因此如果对该类的不同实例的操作都尝试设置/(追加/扩展/插入/等)属性,则:
    • 如果属性是内置类型(如 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    
[]

There is a significant semantic difference (beyond performance considerations):

  • when the attribute is defined on the instance (which is what we usually do), there can be multiple objects referred to. Each gets a totally separate version of that attribute.
  • when the attribute is defined on the class, there is only one underlying object referred to, so if operations on different instances of that class both attempt to set/(append/extend/insert/etc.) the attribute, then:
    • if the attribute is a builtin type (like int, float, boolean, string), operations on one object will overwrite (clobber) the value
    • if the attribute is a mutable type (like a list or a dict), we will get unwanted leakage.

For example:

>>> 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    
[]
听你说爱我 2024-07-13 14:07:21

不同之处在于类的属性由所有实例共享。 实例上的属性对于该实例来说是唯一的。

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

The difference is that the attribute on the class is shared by all instances. The attribute on an instance is unique to that instance.

If coming from C++, attributes on the class are more like static member variables.

他是夢罘是命 2024-07-13 14:07:21

这是一篇非常好的帖子,以及摘要如下。

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 = 酒吧(2)
    foo.class_var
    ## 1
    酒吧.class_var = 2
    foo.class_var
    ## 2

  • 如果通过访问实例来设置类变量,则它将仅覆盖所有实例的值那个实例。 这本质上覆盖了类变量,并将其转换为可用的实例变量,直观地,仅适用于该实例

    <前><代码> foo = 酒吧(2)
    foo.class_var
    ## 1
    foo.class_var = 2
    foo.class_var
    ## 2
    Bar.class_var
    ## 1

什么时候会使用类属性?

  • 存储常量。 由于类属性可以作为类本身的属性进行访问,因此使用它们来存储类范围内的、类特定的常量通常是不错的选择

     类 Circle(对象): 
             圆周率 = 3.14159 
    
             def __init__(自身,半径): 
                  self.radius = 半径    
            定义区域(自身): 
                 返回圆.pi * self.radius * self.radius 
    
        圆.pi 
        ## 3.14159 
        c = 圆(10) 
        pi 
        ## 3.14159 
        c.area() 
        ## 314.159 
      
  • 由于类属性可以作为类本身的属性进行访问,因此使用它们来存储类范围内的、类特定的常量

    定义默认值 。 作为一个简单的例子,我们可能会创建一个有界列表(即只能容纳一定数量或更少元素的列表)并选择默认上限为 10 个项目

     类 MyClass(对象): 
            限制 = 10 
    
            def __init__(自身): 
                自我数据 = [] 
            def 项目(自我,我): 
                返回 self.data[i] 
    
            def 添加(自我,e): 
                if len(self.data) >= self.limit: 
                    引发异常(“元素太多”) 
                self.data.append(e) 
    
         MyClass.limit 
         ## 10 
      

Here is a very good post, and summary it as below.

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

And in visual form

enter image description here

Class attribute assignment

  • If a class attribute is set by accessing the class, it will override the value for all instances

      foo = Bar(2)
      foo.class_var
      ## 1
      Bar.class_var = 2
      foo.class_var
      ## 2
    
  • If a class variable is set by accessing an instance, it will override the value only for that instance. This essentially overrides the class variable and turns it into an instance variable available, intuitively, only for that instance.

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

When would you use class attribute?

  • Storing constants. As class attributes can be accessed as attributes of the class itself, it’s often nice to use them for storing Class-wide, Class-specific constants

      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
    
  • Defining default values. As a trivial example, we might create a bounded list (i.e., a list that can only hold a certain number of elements or fewer) and choose to have a default cap of 10 items

      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
    
淡水深流 2024-07-13 14:07:21

由于这里的评论和其他两个标记为重复的问题中的人们似乎都以同样的方式对此感到困惑,我认为值得在 亚历克斯·考文垂的

事实上,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; s 最终可能是数字 5 的同一个实例。但完全单独创建的 object() 却不能。)


那么,为什么会这样 < Alex 示例中的 code>a.foo.append(5) 会影响 b.foo,但我示例中的 a.foo = 5 不会影响? 好吧,在 Alex 的示例中尝试 a.foo = 5,并注意它不会影响那里的 b.foo

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


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


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

** 有些人通过以下方式利用这一点:使用类属性作为实例属性的“默认值”,实例可以设置也可以不设置。 这在某些情况下可能很有用,但也可能会造成混乱,所以要小心。

Since people in the comments here and in two other questions marked as dups all appear to be confused about this in the same way, I think it's worth adding an additional answer on top of Alex Coventry's.

The fact that Alex is assigning a value of a mutable type, like a list, has nothing to do with whether things are shared or not. We can see this with the id function or the is operator:

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

(If you're wondering why I used object() instead of, say, 5, that's to avoid running into two whole other issues which I don't want to get into here; for two different reasons, entirely separately-created 5s can end up being the same instance of the number 5. But entirely separately-created object()s cannot.)


So, why is it that a.foo.append(5) in Alex's example affects b.foo, but a.foo = 5 in my example doesn't? Well, try a.foo = 5 in Alex's example, and notice that it doesn't affect b.foo there either.

a.foo = 5 is just making a.foo into a name for 5. That doesn't affect b.foo, or any other name for the old value that a.foo used to refer to.* It's a little tricky that we're creating an instance attribute that hides a class attribute,** but once you get that, nothing complicated is happening here.


Hopefully it's now obvious why Alex used a list: the fact that you can mutate a list means it's easier to show that two variables name the same list, and also means it's more important in real-life code to know whether you have two lists or two names for the same list.


* The confusion for people coming from a language like C++ is that in Python, values aren't stored in variables. Values live off in value-land, on their own, variables are just names for values, and assignment just creates a new name for a value. If it helps, think of each Python variable as a shared_ptr<T> instead of a T.

** Some people take advantage of this by using a class attribute as a "default value" for an instance attribute that instances may or may not set. This can be useful in some cases, but it can also be confusing, so be careful with it.

没有伤那来痛 2024-07-13 14:07:21

还有一种情况。

类和实例属性是描述符

# -*- 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 行)。

There is one more situation.

Class and instance attributes is 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()

Above will output:

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

The same type of instance access through class or instance return different result!

And i found in c.PyObject_GenericGetAttr definition,and a great post.

Explain

If the attribute is found in the dictionary of the classes which make up.
the objects MRO, then check to see if the attribute being looked up points to a Data Descriptor (which is nothing more that a class implementing both the __get__ and the __set__ methods).
If it does, resolve the attribute lookup by calling the __get__ method of the Data Descriptor (lines 28–33).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文