我的编程空间,编程开发者的网络收藏夹
学习永远不晚

使用Python的坑有哪些

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

使用Python的坑有哪些

这篇文章主要讲解了“使用Python的坑有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“使用Python的坑有哪些”吧!

坑01 - 整数比较的坑

在 Python 中一切都是对象,整数也是对象,在比较两个整数时有两个运算符 == 和 is ,它们的区别是:

  • is 比较的是两个整数对象的id值是否相等,也就是比较两个引用是否代表了内存中同一个地址。

  • == 比较的是两个整数对象的内容是否相等,使用==时其实是调用了对象的__eq__()方法。

知道了is和==的区别之后,我们可以来看看下面的代码,了解Python中整数比较有哪些坑:

def main():x = y = -1while True:x += 1y += 1if x is y:print('%d is %d' % (x, y))else:print('Attention! %d is not %d' % (x, y))breakx = y = 0while True:x -= 1y -= 1if x is y:print('%d is %d' % (x, y))else:print('Attention! %d is not %d' % (x, y))breakif __name__ == '__main__':main()

上面代码的部分运行结果如下图所示,出现这个结果的原因是Python出于对性能的考虑所做的一项优化。对于整数对象,Python把一些频繁使用的整数对象缓存起来,保存到一个叫small_ints的链表中,在Python的整个生命周期内,任何需要引用这些整数对象的地方,都不再重新创建新的对象,而是直接引用缓存中的对象。Python把频繁使用的整数对象的值定在[-5, 256]这个区间,如果需要这个范围的整数,就直接从 small_ints 中获取引用而不是临时创建新的对象。因为大于256或小于-5的整数不在该范围之内,所以就算两个整数的值是一样,但它们是不同的对象。

当然仅仅如此这个坑就不值一提了,如果你理解了上面的规则,我们就再看看下面的代码。

import disa = 257def main():b = 257 # 第6行c = 257 # 第7行print(b is c) # Trueprint(a is b) # Falseprint(a is c) # Falseif __name__ == "__main__":main()

程序的执行结果已经用注释写在代码上了。够坑吧!看上去a、b和c的值都是一样的,但是 is运算的结果却不一样。为什么会出现这样的结果,首先我们来说说Python程序中的代码块。

所谓代码块是程序的一个最小的基本执行单位,一个模块文件、一个函数体、一个类、交互式命令中的单行代码都叫做一个代码块。上面的代码由两个代码块构成,a = 257是一个代码块,main函数是另外一个代码块。

Python内部为了进一步提高性能,凡是在一个代码块中创建的整数对象,如果值不在small_ints 缓存范围之内,但在同一个代码块中已经存在一个值与其相同的整数对象了,那么就直接引用该对象,否则创建一个新的对象出来,这条规则对不在small_ints范围的负数并不适用,对负数值浮点数也不适用,但对非负浮点数和字符串都是适用的,这一点读者可以自行证明。所以 b is c返回了True,而a和b不在同一个代码块中,虽然值都是257,但却是两个不同的对象,is运算的结果自然是False了。

为了验证刚刚的结论,我们可以借用dis模块(听名字就知道是进行反汇编的模块)从字节码的角度来看看这段代码。如果不理解什么是字节码,可以先看看《谈谈 Python 程序的运行原理》这篇文章。可以先用import dis导入dis模块并按照如下所示的方式修改代码。

if __name__ == "__main__":main()dis.dis(main)

代码的执行结果如下图所示。

可以看出代码第6行和第7行,也就是main函数中的257是从同一个位置加载的,因此是同一个对象;而代码第9行的a明显是从不同的地方加载的,因此引用的是不同的对象。

如果还想对这个问题进行进一步深挖,推荐大家阅读《Python整数对象实现原理》这篇文章。

坑02 - 嵌套列表的坑

Python中有一种内置的数据类型叫列表,它是一种容器,可以用来承载其他的对象(准确的说是其他对象的引用),列表中的对象可以称为列表的元素,很明显我们可以把列表作为列表中的元素,这就是所谓的嵌套列表。嵌套列表可以模拟出现实中的表格、矩阵、2D游戏的地图(如植物大战僵尸的花园)、棋盘(如国际象棋、黑白棋)等。但是在使用嵌套的列表时要小心,否则很可能遭遇非常尴尬的情况,下面是一个小例子。

def main():names = ['关羽', '张飞', '赵云', '马超', '黄忠']subjs = ['语文', '数学', '英语']scores = [[0] * 3] * 5for row, name in enumerate(names):print('请输入%s的成绩' % name)for col, subj in enumerate(subjs):scores[row][col] = float(input(subj + ': '))print(scores)if __name__ == '__main__':main()

我们希望录入5个学生3门课程的成绩,于是定义了一个有5个元素的列表,而列表中的每个元素又是一个由3个元素构成的列表,这样一个列表的列表刚好跟一个表格是一致的,相当于有5行3列,接下来我们通过嵌套的for-in循环输入每个学生3门课程的成绩。程序执行完成后我们发现,每个学生3门课程的成绩是一模一样的,而且就是最后录入的那个学生的成绩。

要想把这个坑填平,我们首先要区分对象和对象的引用这两个概念,而要区分这两个概念,还得先说说内存中的栈和堆。我们经常会听人说起“堆栈”这个词,但实际上“堆”和“栈”是两个不同的概念。众所周知,一个程序运行时需要占用一些内存空间来存储数据和代码,那么这些内存从逻辑上又可以做进一步的划分。对底层语言(如C语言)有所了解的程序员大都知道,程序中可以使用的内存从逻辑上可以为五个部分,按照地址从高到低依次是:栈(stack)、堆(heap)、数据段(data segment)、只读数据段(static area)和代码段(code segment)。其中,栈用来存储局部、临时变量,以及函数调用时保存现场和恢复现场需要用到的数据,这部分内存在代码块开始执行时自动分配,代码块执行结束时自动释放,通常由编译器自动管理;堆的大小不固定,可以动态的分配和回收,因此如果程序中有大量的数据需要处理,这些数据通常都放在堆上,如果堆空间没有正确的被释放会引发内存泄露的问题,而像Python、Java等编程语言都使用了垃圾回收机制来实现自动化的内存管理(自动回收不再使用的堆空间)。所以下面的代码中,变量a并不是真正的对象,它是对象的引用,相当于记录了对象在堆空间的地址,通过这个地址我们可以访问到对应的对象;同理,变量b是列表容器的引用,它引用了堆空间上的列表容器,而列表容器中并没有保存真正的对象,它保存的也仅仅是对象的引用。

a = object()b = ['apple', 'pitaya', 'grape']

知道了这一点,我们可以回过头看看刚才的程序,我们对列表进行[[0] * 3] * 5操作时,仅仅是将[0, 0, 0]这个列表的地址进行了复制,并没有创建新的列表对象,所以容器中虽然有5个元素,但是这5个元素引用了同一个列表对象,这一点可以通过id函数检查scores[0]和scores[1]的地址得到证实。所以正确的代码应该按照如下的方式进行修改。

def main():names = ['关羽', '张飞', '赵云', '马超', '黄忠']subjs = ['语文', '数学', '英语']scores = [[]] * 5for row, name in enumerate(names):print('请输入%s的成绩' % name)scores[row] = [0] * 3for col, subj in enumerate(subjs):scores[row][col] = float(input(subj + ': '))print(scores)if __name__ == '__main__':main()

或者

def main():names = ['关羽', '张飞', '赵云', '马超', '黄忠']subjs = ['语文', '数学', '英语']scores = [[0] * 3 for _ in range(5)]for row, name in enumerate(names):print('请输入%s的成绩' % name)scores[row] = [0] * 3for col, subj in enumerate(subjs):scores[row][col] = float(input(subj + ': '))print(scores)if __name__ == '__main__':main()

如果对内存的使用不是很理解,可以看看PythonTutor网站上提供的代码可视化执行功能,通过可视化执行,我们可以看到内存是如何分配的,从而避免在使用嵌套列表或者复制对象时可能遇到的坑。

坑03 - 访问修饰符的坑

用Python做过面向对象编程的人都知道,Python的类提供了两种访问控制权限,一种是公开,一种是私有(在属性或方法前加上双下划线)。而用惯了Java或C#这类编程语言的人都知道,类中的属性(数据抽象)通常都是私有的,其目的是为了将数据保护起来;而类中的方法(行为抽象)通常都是公开的,因为方法是对象向外界提供的服务。但是Python并没有从语法层面确保私有成员的私密性,因为它只是对类中所谓的私有成员进行了命名的变换,如果知道命名的规则照样可以直接访问私有成员,请看下面的代码。

class Student(object): def __init__(self, name, age): self.__name = name self.__age = age def __str__(self): return self.__name + ': ' + str(self.__age)def main(): stu = Student('骆昊', 38) # 'Student' object has no attribute '__name' # print(stu.__name) # 用下面的方式照样可以访问类中的私有成员 print(stu._Student__name) print(stu._Student__age)if __name__ == '__main__': main()

Python为什么要做出这样的设定呢?用一句广为流传的格言来解释这个问题:“We are all consenting adults here”(我们都是成年人)。这句话表达了很多Python程序员的一个共同观点,那就是开放比封闭要好,我们应该自己对自己的行为负责而不是从语言层面来限制对数据或方法的访问。

所以在Python中我们实在没有必要将类中的属性或方法用双下划线开头的命名处理成私有的成员,因为这并没有任何实际的意义。如果想对属性或方法进行保护,我们建议用单下划线开头的受保护成员,虽然它也不能真正保护这些属性或方法,但是它相当于给调用者一个暗示,让调用者知道这是不应该直接访问的属性或方法,而且这样做并不影响子类去继承这些东西。

需要提醒大家注意的是,Python类中的那些魔法方法,如__str__、__repr__等,这些方法并不是私有成员哦,虽然它们以双下划线开头,但是他们也是以双下划线结尾的,这种命名并不是私有成员的命名,这一点对初学者来说是比较坑的。

感谢各位的阅读,以上就是“使用Python的坑有哪些”的内容了,经过本文的学习后,相信大家对使用Python的坑有哪些这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

使用Python的坑有哪些

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

使用Python的坑有哪些

这篇文章主要讲解了“使用Python的坑有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“使用Python的坑有哪些”吧!坑01 - 整数比较的坑在 Python 中一切都是对象,整数也是
2023-06-02

使用Python坑有哪些

本篇内容介绍了“使用Python坑有哪些”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1、缩进,符号和空格不正确写代码时大家会使用缩进、对齐
2023-06-02

golang copy函数使用的坑有哪些

这篇文章主要介绍“golang copy函数使用的坑有哪些”,在日常操作中,相信很多人在golang copy函数使用的坑有哪些问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”golang copy函数使用的坑
2023-07-05

使用recvfrom函数遇到的坑有哪些

使用recvfrom函数时可能会遇到以下几个常见的问题:1. 阻塞问题:recvfrom函数默认是阻塞的,即当没有收到数据时,程序会一直阻塞在该函数处,直到有数据到达。如果需要设置为非阻塞模式,可以使用fcntl函数将套接字设置为非阻塞。2
2023-08-24

使用try-catch-finally常遇到的坑有哪些

本篇内容主要讲解“使用try-catch-finally常遇到的坑有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“使用try-catch-finally常遇到的坑有哪些”吧!坑1:final
2023-06-15

go语言使用time.Duration类型的坑有哪些

这篇文章的内容主要围绕go语言使用time.Duration类型的坑有哪些进行讲述,文章内容清晰易懂,条理清晰,非常适合新手学习,值得大家去阅读。感兴趣的朋友可以跟随小编一起阅读吧。希望大家通过这篇文章有所收获!01 踩到的坑先来说说在项目
2023-06-29

Java CPP的坑有哪些

这篇文章主要讲解了“Java CPP的坑有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java CPP的坑有哪些”吧!1.分清楚System.load与System.loadLibra
2023-06-04

关于C++的坑有哪些

这篇文章主要讲解了“关于C++的坑有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“关于C++的坑有哪些”吧!1. string的字符串拼接,导致coredump该问题的核心点在于第9行,
2023-06-16

编写Java的坑有哪些

本篇内容主要讲解“编写Java的坑有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“编写Java的坑有哪些”吧!1、对象比较方法JDK 1.7 提供的 Objects.equals 方法,非常
2023-06-16

Spring事务有哪些坑

这篇文章主要讲解了“Spring事务有哪些坑”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Spring事务有哪些坑”吧!一、Spring事务管理的几种方式:Spring事务在具体使用方式上可
2023-06-15

PHP编程中的坑有哪些

本篇内容介绍了“PHP编程中的坑有哪些”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1、由于使用单引号,以“ ”为分割符,使用PHP函数ex
2023-06-17

使用Python的理由有哪些

本篇内容介绍了“使用Python的理由有哪些”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1. Python 读起来像可执行的伪代码伪代码是
2023-06-16

Python打包Exe程序需要避的坑有哪些

Python打包Exe程序需要避的坑有哪些,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。写完一个python程序之后,如何才能快速地将代码分享给别人,尤其对于初学者来说,能把自
2023-06-15

有哪些C++模板坑

这篇文章主要介绍“有哪些C++模板坑”,在日常操作中,相信很多人在有哪些C++模板坑问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”有哪些C++模板坑”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!问题复现头
2023-06-16

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录