当前位置: 首页 > 知识库问答 >
问题:

python - Python类属性与实例属性自增行为差异?

羊渝
2024-12-11

我在编写时遇到下面情况

>>> class A:  
...     a = 0
...     def __init__(self):
...         self.a += 1
...         print(self.a)
... 
>>> a1 = A()  
1
>>> a1.a                    
1
>>> a2 = A() 
1
>>> a2.a
1
>>> id(a1.a)
140725769696040
>>> id(a2.a)
140725769696040

明明a1.a和a2.a指向的是同一个对象,且都执行了+1操作,为什么结果没变

下面代码完成了我预期的功能,但上面是什么情况

>>> class A:  
...     a = 0
...     def __init__(self):
...         A.a += 1    
...         print(self.a)
...                       
>>> a1 = A()
1
>>> a1.a
1
>>> a2 = A()
2
>>> a2.a
2
>>> id(a1.a)
140725769696072
>>> id(a2.a)
140725769696072

环境版本

python: 3.11.9
系统: windows10

共有3个答案

杜嘉慕
2024-12-11

补充一下,关于 id

在CPython 里(就是我们通常用的 python) id 输出的不是变量的地址,而是对象的地址。

对于小整数,相同值的对象是共享的。就是整个 python 中,只有一个 1 对象。你也可以打印 id(1)

所以,虽然 a1.a a2.a 是两个不同的类里的两个不同的实例变量,但是它们都引用了同一个 1 对象,所以 id 是一样的。

class A:
    a = 0
    def __init__(self):
        self.a += 1 # 这里会创建一个新变量 self.a ,覆盖了 A.a 
        print(self.a)

a1 = A()
a2 = A()
print(A.a, a1.a, a2.a, id(A.a), id(a1.a), id(a2.a), id(0), id(1))
# 0 1 1 140711496422656 140711496422688 140711496422688 140711496422656 140711496422688
A.a = 1
a1.a = 2
a2.a = 3
print(A.a, a1.a, a2.a, id(A.a), id(a1.a), id(a2.a), id(1), id(2), id(3))
# 1 2 3 140711496422688 140711496422720 140711496422752 140711496422688 140711496422720 140711496422752
荀俊迈
2024-12-11

你遇到的问题是因为你在类 A 中定义了一个类变量 a,而在 init 方法中,你使用了 self.a,这会创建一个实例变量 a,覆盖了类变量 a。
当你创建实例 a1 和 a2 时,每个实例都有自己的 a 实例变量,而不是共享类变量 a。因此,每次实例化时,self.a 都是从 0 开始,并且 self.a += 1 只影响当前实例的 a。

你新写的代码里的A.a 是一个类变量,所有实例共享它,因此每次实例化时,A.a 都会增加。当你打印 self.a 时,self.a 实际上是访问类变量 A.a,因为实例 a1 和 a2 没有自己的 a 实例变量。因此,print(self.a) 打印的是类变量 A.a 的值。

补充

使用 cls 参数来实现这一点,这样在继承时可以修改子类的属性,而不是父类的。可以通过在方法中使用 @classmethod 装饰器来实现。

class A:
    a = 0

    @classmethod
    def increment_a(cls):
        cls.a += 1
        print(cls.a)

    def __init__(self):
        self.increment_a()

class B(A):
    pass

a1 = A()  # 输出 1
a2 = A()  # 输出 2
b1 = B()  # 输出 1
b2 = B()  # 输出 2

print(A.a)  # 输出 2
print(B.a)  # 输出 2
松和泰
2024-12-11
### 回答

在第一个代码示例中,类属性 `a` 和实例属性 `self.a` 之间的行为差异导致了意外的结果。

1. **类属性 `a` 初始化为 0**:
   - `a` 是类 `A` 的一个类属性,所有实例共享这个属性。

2. **在 `__init__` 方法中**:
   - `self.a += 1` 实际上是在尝试修改实例属性 `a`,但是因为它之前没有在实例中定义,所以 Python 会首先查找类属性 `a`(值为 0),然后将其值赋给实例(即 `self.a = 0`),接着执行 `+= 1` 操作。
   - 这一步会在实例中创建一个新的属性 `a`,其值为 1,但不会影响类属性 `A.a` 的值,它仍然是 0。
   - 因此,每次创建新实例时,都会重复上述过程,类属性 `A.a` 始终为 0,而每个实例都会有自己的 `a` 属性,其值为 1。

3. **输出和 ID**:
   - 由于每个实例都有自己的 `a` 属性(值为 1),所以 `a1.a` 和 `a2.a` 看起来指向同一个值(1),但实际上它们是不同的属性,只是值相同。
   - `id(a1.a)` 和 `id(a2.a)` 相同(在你的环境中)是一个巧合,这取决于 Python 的内存管理,但不意味着它们指向同一个内存地址在整个程序生命周期中都有效或期望。

在第二个代码示例中:

1. **直接修改类属性**:
   - `A.a += 1` 直接修改类属性 `a` 的值。
   - 因此,每次创建新实例时,类属性 `A.a` 的值都会递增。

2. **实例属性 `self.a`**:
   - 由于在 `__init__` 方法中没有对 `self.a` 进行赋值操作(只是打印),所以它会默认引用类属性 `A.a` 的当前值(即递增后的值)。
   - 但是,这里有一个重要的点:虽然 `self.a` 访问的是类属性 `A.a` 的当前值,但 `self.a` 并不等同于 `A.a`。它只是读取类属性的值,并不创建一个新的实例属性。

3. **输出和 ID**:
   - `a1.a` 和 `a2.a` 实际上访问的是同一个类属性 `A.a` 的值,因此它们的值会递增,并且 `id` 也相同(因为它们确实是同一个对象)。

### 总结
- 在第一个示例中,每次创建实例时,都会创建一个新的实例属性 `a`,其值独立于类属性 `A.a`。
- 在第二个示例中,直接修改类属性 `A.a`,所有实例访问 `self.a` 时都会看到更新后的值。
 类似资料:
  • 主要内容:类变量(类属性),实例变量(实例属性),局部变量无论是类属性还是类方法,都无法像普通变量或者函数那样,在类的外部直接使用它们。我们可以将类看做一个独立的空间,则类属性其实就是在类体中定义的变量,类方法是在类体中定义的函数。 前面章节提到过,在类体中,根据变量定义的位置不同,以及定义的方式不同,类属性又可细分为以下 3 种类型: 类体中、所有函数之外:此范围定义的变量,称为类属性或类变量; 类体中,所有函数内部:以“self.变量名”的方式定义的

  • 问题内容: 我是python的新手,了解到类属性就像C ++中的静态数据成员一样。但是,尝试以下代码后,我感到困惑: f2.a是否也等于5? 如果将a定义为列表而不是整数,则预期行为: 我研究了 Python:类和实例属性之间的区别,但是它不能回答我的问题。 谁能解释为什么会有所不同?谢谢 问题答案: 在第二个示例中,您没有做相同的事情。在第一个示例中,您要分配一个新值: 在第二个示例中,您只是在

  • 下表列出了 4 个常见的明星人物: 姓名 年龄 周润发 58 成龙 55 刘德华 53 周星驰 54 进行归纳总结: 这些人物具有较高的知名度,把这些人物归类为明星; 每个明星两个属性:姓名和年龄。明星这个群体具有一个属性:数量,在这张表格中,明星的数量是 4; 姓名和年龄等属性是用于描述具体的一个对象(例如:周润发),而人物的数量是用于描述明星这个类别。 如果使用面向对象技术对以上实体进行描述,

  • 上面的代码准备好了两个类,请看表演 接着来

  • 由于Python是动态语言,根据类创建的实例可以任意绑定属性。 给实例绑定属性的方法是通过实例变量,或者通过self变量: class Student(object): def __init__(self, name): self.name = name s = Student('Bob') s.score = 90 但是,如果Student类本身需要绑定一个属性呢?可

  • 本文向大家介绍Python属性和内建属性实例解析,包括了Python属性和内建属性实例解析的使用技巧和注意事项,需要的朋友参考一下 这篇文章主要介绍了Python属性和内建属性实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1. 私有属性添加getter和setter方法 2. 使用property升级getter和setter方法 运行

  • 问题内容: 我需要一种检查类的方法,以便可以安全地标识哪些属性是用户定义的类属性。问题是,像DIR功能(),inspect.getmembers()和朋友返回所有类的属性包括预定义的像:,,,。这当然是可以理解的,并且可以说我可以列出一个要忽略的命名成员列表,但是不幸的是,这些预定义属性必定会随着不同版本的Python发生变化,因此使我的项目在python项目中容易发生变化- 我不喜欢那样。 例:

  • 我有以上代码,为什么修改类属性影响实例属性?有人会说这是由于实例共享类属性,根据mro属性解析顺序是如此,但是这是在实例命名空间没有要搜索的属性时才会访问类的命名空间,在我的代码中,实例(shili)有自己的name属性,但为什么还是共享类属性呢?或者说这是其他什么问题,求大佬解答