详解Python 类变量与实例变量的陷阱

Python中的类变量和实例变量是常见的面向对象编程的概念。类变量是定义在类中,并且被所有实例共享的变量。实例变量是定义在实例中,并且每个实例有它们自己的独立变量副本。

然而,在使用类变量和实例变量时,有一些陷阱需要注意,下面我们就来详细讲解这些问题以及如何正确使用类变量和实例变量。

类变量与实例变量的区别

类变量是所有实例共享的变量,关键字 class 定义的变量是类变量。实例变量是每个实例独立的变量,关键字 self、或者其他实例本身定义的变量即为实例变量。

下面我们来使用示例代码演示这个区别:

class Person:
    count = 0

    def __init__(self, name):
        self.name = name
        Person.count += 1

p1 = Person("Tom")
p2 = Person("Jerry")

print(p1.count)  # 输出 2
print(p2.count)  # 输出 2

在这个例子中,我们定义了一个 Person 类,并定义了一个类变量 count。每当一个 Person 实例被创建时,count 的值都会增加1。由于 count 是类变量,因此被所有 Person 实例共享。因此,我们可以看到 p1.countp2.count 的值都为 2。

现在,我们尝试创建一个实例变量:

class Person:
    def __init__(self, name):
        self.name = name
        self.count = 0

    def add(self):
        self.count += 1

p1 = Person("Tom")
p2 = Person("Jerry")

p1.add()
p1.add()

print(p1.count)  # 输出 2
print(p2.count)  # 输出 0

在这个例子中,我们定义了一个 Person 类,并在 __init__ 方法中定义了一个实例变量 count。我们还定义了一个 add 方法,该方法每次被调用时会增加 count 的值。

在这个例子中,p1 调用了两次 add() 方法,因此 p1.count 的值为 2。但是,由于实例变量 count 是每个实例独立的,因此 p2 的值为 0。

陷阱及解决方法

下面我们来介绍一些使用类变量和实例变量时可能发生的陷阱以及如何避免它们。

陷阱1:类变量被多个实例修改

由于类变量是所有实例共享的,当多个实例同时修改类变量时可能会导致错误的结果。

例如,我们假设 Person 类的每个实例都有一个年龄,并且 Person 类中有一个 default_age 的类变量,用于指定所有实例的默认年龄:

class Person:
    default_age = 20

    def __init__(self, name, age=None):
        self.name = name
        if age is None:
            self.age = Person.default_age
        else:
            self.age = age

在这个例子中,如果一个 Person 实例没有指定年龄,它将使用 default_age 值。然而,这个方案有一个缺点,当 Person.default_age 的值改变时,所有使用默认值的实例年龄也会改变:

p1 = Person("Tom")
p2 = Person("Jerry")
print(p1.age)  # 输出 20
print(p2.age)  # 输出 20

Person.default_age = 30

p3 = Person("Lucy")
print(p1.age)  # 输出 30!
print(p2.age)  # 输出 30!
print(p3.age)  # 输出 30

解决方法:

为了避免这种问题,我们可以遵循以下两种方法之一:

  1. 使用实例变量而不是类变量来保存默认值:
class Person:
    def __init__(self, name, age=None):
        self.name = name
        self.default_age = 20
        if age is None:
            self.age = self.default_age
        else:
            self.age = age

p1 = Person("Tom")
p2 = Person("Jerry")
print(p1.age)  # 输出 20
print(p2.age)  # 输出 20

p3 = Person("Lucy", age=30)
print(p3.age)  # 输出 30

在这种情况下,每个实例都有自己的默认年龄,因此它们不会相互干扰。

  1. default_age 定义为不可更改的对象,例如元组:
class Person:
    default_age = (20,)

    def __init__(self, name, age=None):
        self.name = name
        if age is None:
            self.age = Person.default_age[0]
        else:
            self.age = age

p1 = Person("Tom")
p2 = Person("Jerry")
print(p1.age)  # 输出 20
print(p2.age)  # 输出 20

Person.default_age = (30,)

p3 = Person("Lucy")
print(p1.age)  # 输出 20
print(p2.age)  # 输出 20
print(p3.age)  # 输出 30

在这个例子中,我们将 default_age 定义为只有一个元素的元组。由于元组是不可更改的,因此当我们尝试修改 Person.default_age 时,会引发一个 TypeError。

陷阱2:类变量和实例变量同名

如果一个实例变量和一个类变量同名,那么在访问这个变量时会发生什么呢?

例如,考虑以下代码:

class Person:
    count = 0

    def __init__(self, name):
        self.name = name
        self.count = 10

p1 = Person("Tom")
print(p1.count)  # 输出 10
print(Person.count)  # 输出 0

在这个例子中,我们定义了一个 Person 类,并定义了一个类变量 count。我们还定义了一个实例变量 count,并在 __init__ 方法中将其值设置为 10。当我们打印 p1.count 时,输出的结果为 10。但是,当我们打印 Person.count 时,输出结果为 0。

这是因为在实例方法中,Python首先查找实例变量。如果找不到,它将继续查找类变量。因此,在这个例子中,当我们使用 p1.count 时,Python首先找到了实例变量,因此返回的是 self.count 的值,即 10. 而在访问 Person.count 时,Python找到了类变量,因此返回的是类变量的默认值,即 0.

解决方法:

为了避免这种问题,我们应该尽可能使用不同的变量名称。如果变量名称一定要相同,那么我们可以使用类名来引用类变量:

class Person:
    count = 0

    def __init__(self, name):
        self.name = name
        Person.count = 10

p1 = Person("Tom")
print(p1.count)  # 输出 10
print(Person.count)  # 输出 10

在这个例子中,我们在 __init__ 方法中使用了 Person.count 来设置类变量的值,以避免与实例变量发生冲突。

这就是关于Python中类变量与实例变量的陷阱及解决方法的详细讲解。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解Python 类变量与实例变量的陷阱 - Python技术站

(0)
上一篇 2023年3月25日
下一篇 2023年3月25日

相关文章

  • pygame实现井字棋之第三步逻辑优化

    让我来详细讲解“pygame实现井字棋之第三步逻辑优化”的完整攻略。 1. 实现功能 在实现“pygame实现井字棋之第三步逻辑优化”之前,我们首先要了解这个游戏需要实现哪些功能。在之前的第二步代码(https://www.jianshu.com/p/e0f0c430f5fe)中,我们已经实现了一个可以在窗口中显示的井字棋游戏,用户可以通过鼠标点击的方式在窗…

    python 2023年5月14日
    00
  • 如何利用Python+OpenCV实现简易图像边缘轮廓检测(零基础)

    下面就来详细讲解如何利用Python+OpenCV实现简易图像边缘轮廓检测(零基础)的完整攻略。 1. 准备工作 在开始实现简易图像边缘轮廓检测之前,需要先进行一些准备工作,包括安装Python、OpenCV等。 1.1 安装Python Python是一种通用的编程语言,针对各种不同的工作领域都有不同的应用场景。在这里,我们需要使用Python编写代码实现…

    python 2023年5月14日
    00
  • Python的加密模块之hashlib 与 base64详解及常用加密方法

    Python的加密模块之hashlib与base64详解及常用加密方法 什么是加密模块? 加密模块是Python中用来实现加密的工具包,其主要包含以下几种类型: 哈希(Hash)加密:将任意长度的消息压缩到某一固定长度,且不可逆。 对称加密(Symmetric-Key):通过同一个秘钥同时对明文和密文进行加密和解密,常用算法有AES、DES等。 非对称加密(…

    python 2023年5月20日
    00
  • Python下使用Scrapy爬取网页内容的实例

    下面就来讲解一下使用Scrapy爬取网页内容的完整攻略: 确定目标网站和爬取页面 首先,我们需要确定要爬取的目标网站和具体的爬取页面。在确定目标网站时需要注意网站的robots协议,避免不必要的麻烦。在确定爬取页面时也需要注意规避反爬虫机制。 假设我们要爬取的是豆瓣读书的畅销书排行榜,页面链接为:https://book.douban.com/chart?s…

    python 2023年5月14日
    00
  • Python实现在某个数组中查找一个值的算法示例

    这里我来详细讲解一下“Python实现在某个数组中查找一个值的算法示例”的完整攻略。 算法背景 在编程中,我们常常需要在一个数组中查找某个特定的值,并且判断该值是否在数组中存在。这种查找操作涉及到一些常用的算法,例如顺序查找、二分查找、哈希表等,可以根据实际的场景选择不同的算法实现。 顺序查找算法 顺序查找算法,也称为线性查找算法,是一种简单直接的查找算法。…

    python 2023年6月5日
    00
  • 详解pyqt中解决国际化tr()函数不起作用的问题

    下面我将详细讲解如何解决 PyQt 中 tr() 函数不起作用的问题。 问题描述 PyQt 中的 tr() 函数是用于实现国际化的函数,但有时候在程序中使用 tr() 函数时,它却不起作用,导致界面不能实现国际化。 解决方案 解决这个问题的方法是需要使用 PyQt 中提供的 QTranslator 类来加载翻译文件。具体步骤如下: 创建一个翻译器 在 PyQ…

    python 2023年6月6日
    00
  • Python控制台输出时刷新当前行内容而不是输出新行的实现

    为了实现Python控制台输出时刷新当前行内容而不是输出新行,我们需要用到sys模块以及对应的stdout和flush方法。 具体步骤如下: 导入sys模块 首先,在Python文件或控制台中导入sys模块,以便使用相关方法。可以使用以下命令导入sys模块: import sys 使用stdout方法替换输出 将标准输出(一般指print函数输出)替换成sy…

    python 2023年6月3日
    00
  • Python取读csv文件做dbscan分析

    下面是Python取读csv文件做dbscan分析的完整攻略。 1. 确定分析目的 在进行数据分析前,我们需要确定分析的目的和问题,以确保分析结果的准确性和实用性。在本文中,我们假设已经明确了分析目的为对csv文件中的数据进行聚类,找出其中相似的数据点,以便进一步的分析和应用。 2. 准备工作 在进行数据分析前,我们需要进行一些必要的准备工作,主要包括以下几…

    python 2023年6月3日
    00
合作推广
合作推广
分享本页
返回顶部