33-为什么 obj == obj 为 False、[obj] == [obj] 为 True
33-为什么 obj == obj 为 False、[obj] == [obj] 为 True楔子今天同事在用 pandas 做数据处理的时候,不小心被 nan 坑了一下,他当时被坑的原因类似下面:
1234import numpy as npprint(np.nan == np.nan) # Falseprint([np.nan] == [np.nan]) # True
为了严谨,我们再举个栗子:
1234567891011class A: def __eq__(self, other): return Falsea1 = A()a2 = A()print(a1 == a1, a2 == a2) # False Falseprint([a1, a2] == [a1, a2]) # True
为什么会出现这个结果呢?我们知道两个列表(元组也是同理)如果相等,那么首先列表里面的元素个数要相同、并且相同索引对应的元素也要相等。但问题是这里的 a1 不等于 a1、a2 也不等于 a2,那为啥 [a1, a2] 和 [a1 ...
32-Python 和 Go 联合编程
32-Python 和 Go 联合编程楔子Python 可以和 C 无缝结合,通过 C 来为 Python 编写扩展可以极大地提升 Python 的效率,但是使用 C 来编程显然不是很方便,于是本人想到了 Go。对比 C 和 Go 会发现两者非常相似,没错,Go 语言具有强烈的 C 语言背景,其设计者以及语言的设计目标都和 C 有着千丝万缕的联系。因为 Go 语言的诞生就是因为 Google 中的一些开发者觉得 C++ 太复杂了,所以才决定开发一门简单易用的语言,而 Google 的工程师大部分都有 C 的背景,因此在设计 Go 语言的时候保持了 C 语言的风格。
而在 Go 和 C 的交互方面,Go 语言也是提供了非常大的支持(CGO),可以直接通过注释的方式将 C 源代码嵌入在 Go 文件中,这是其它语言所无法比拟的。最初 CGO 是为了能复用 C 资源这一目的而出现的,而现在它已经变成 Go 和 C 之间进行双向通讯的桥梁,也就是 Go 不仅能调用 C 的函数,还能将自己的函数导出给 C 调用。也正因为如此,Python 和 Go 之间才有了交互的可能。因为 Pyth ...
31-Python 和 C C++ 联合编程
31-Python 和 C / C++ 联合编程楔子Python 和 C / C++ 混合编程已经屡见不鲜了,那为什么要将这两种语言结合起来呢?或者说,这两种语言混合起来能给为我们带来什么好处呢?首先,Python 和 C / C++ 联合,无非两种情况。
1. C / C++ 为主导的项目中引入 Python;
2. Python 为主导的项目中引入 C / C++;
首先是第一种情况,因为 C / C++ 是编译型语言,而它们的编译调试的成本是很大的。如果用 C / C++ 开发一个大型项目的话,比如游戏引擎,这个时候代码的修改、调试是无可避免的。而对于编译型语言来说,你对代码做任何一点改动都需要重新编译,而这个耗时是比较长的,所以这样算下来成本会非常高。这个时候一个比较不错的做法是,将那些跟性能无关的内容开放给脚本,可以是 Lua 脚本、也可以是 Python 脚本,而脚本语言不需要编译,我们可以随时修改,这样可以减少编译调试的成本。还有就是引入了 Python 脚本之后,我们可以把 C / C++ 做的更加模 ...
30-源码解密内置函数 iter、next
30-源码解密内置函数 iter、next楔子这次我们来看看 iter 和 next 这两个内置函数的用法,我们知道 iter 是将一个可迭代对象变成一个迭代器,next 是将迭代器里的值一步一步迭代出来。
123456lst = [1, 2, 3]it = iter(lst)print(it) # <list_iterator object at 0x000001DC6E898640># 调用next, 可以对迭代器进行迭代print(next(it)) # 1
注意:iter 还有一个鲜为人知的用法,我们来看一下:
123456789101112131415161718192021val = 0def foo(): global val val += 1 return val# iter可以接收一个参数: iter(可迭代对象)# iter可以接收两个参数: iter(可调用对象, value)for i in iter(foo, 5): print(i)"""1234"""# 进 ...
29-源码解密 map、filter、zip 底层实现,对比列表解析式
29-源码解密 map、filter、zip 底层实现,对比列表解析式楔子Python 现在如此流行,拥有众多开源、高质量的第三方库是一个重要原因,不过 Python 的简单、灵巧、容易上手也是功不可没的,而其背后的内置函数(类)则起到了很大的作用。举个栗子:
123456789101112numbers = [1, 2, 3, 4, 5]# 将里面每一个元素都加1print(list(map(lambda x: x + 1, numbers))) # [2, 3, 4, 5, 6]strings = ["abc", "d", "def", "kf", "ghtc"]# 筛选出长度大于等于3的print(list(filter(lambda x: len(x) >= 3, strings))) # ['abc', 'def', 'ghtc']keys = ["name", "age&quo ...
28-Python内存管理与垃圾回收(第二部分):源码解密Python中的垃圾回收机制
28-Python内存管理与垃圾回收(第二部分):源码解密Python中的垃圾回收机制楔子现在绝大部分的语言都实现了垃圾回收机制,这其中也包括Python,而不同的语言采用的垃圾回收算法也各不相同。那么,常见的垃圾回收算法都有哪些呢?
引用计数法(reference count): 记录对象的被引用次数, 引用计数降为0时回收
标记-清除法(mark-sweep): 从根集合触发, 遍历所有能访问到的对象并对其进行标记, 然后将未被标记的对象清除
停止-复制法(stop-copy): 将内存划分为大小相同的内存块, 一块用完后启用另一块、并将存活的对象拷贝过去, 原来那块则整体被回收
分代回收法(generational-collection): 根据对象的存活时间将对象分为若干代, 并按照不同代的特征采用最合适的回收策略
那么我们下面来看看Python中的垃圾回收。
引用计数对于Python而言,其对象的生命周期是通过对象的引用计数来管理的,这一点在开始的章节我们就说了,对于Python中实现对象的基石PyObject,有两个属性,一个是该对象的类型,还有一个就是引用计数(ob_ ...
27-Python内存管理与垃圾回收(第一部分):深度剖析Python内存管理架构、内存池的实现原理
27-Python内存管理与垃圾回收(第一部分):深度剖析Python内存管理架构、内存池的实现原理楔子内存管理,对于Python这样的动态语言来说是非常重要的一部分,它在很大程度上决定了Python的执行效率,因为Python在运行中会创建和销毁大量的对象,这些都涉及内存的管理,因此精湛的内存管理技术是确保内存使用效率的关键。
此外,我们知道Python还是一门提供了垃圾回收机制(GC, garbage collection)的语言,可以将开发者从繁琐的手动维护内存的工作中解放出来。
那么下面我们就来分析一下Python中的内存管理和垃圾回收。
内存管理架构首先Python的内存管理机制是分层次的,我们可以看成是有6层:-2、-1、0、1、2、3。
最底层,也就是-2和-1层是由操作系统提供的内存管理接口,因为计算机硬件资源由操作系统负责管理,内存资源也不例外,应用程序通过系统调用向操作系统申请内存。注意:这一层Python是无权干预的。
第0层,C的库函数会将系统调用封装成通用的内存分配器,也就是我们所熟悉的malloc系列函数。注意:这一层Python同样无法干预。
第1、2、 ...
26-解密Python中的多线程(第二部分):源码剖析Python线程的创建、销毁、调度、以及GIL的实现原理
26-解密Python中的多线程(第二部分):源码剖析Python线程的创建、销毁、调度、以及GIL的实现原理初见Python的_thread模块下面我们来说一下Python中线程的创建,我们知道在创建多线程的时候会使用threading这个标准库,这个库是以一个py文件存在的形式存在的,不过这个模块依赖于_thread模块,我们来看看它长什么样子。
_thread是真正用来创建线程的模块,这个模块是由C编写,内嵌在解释器里面。我们可以import调用,但是在Python安装目录里面则是看不到的。像这种底层由C编写、内嵌在解释器里面的模块,以及那些无法使用文本打开的pyd文件,pycharm都会给你做一个抽象,并且把注释给你写好。
记得我们之前说过Python源码中的Modules目录,这个目录里面存放了大量使用C编写的模块,我们在编译完Python之后就,这些模块就内嵌在解释器里面了。而这些模块都是针对那些性能要求比较高的,而要求不高的则由Python编写,存放在Lib目录下。像我们平时调用random、collections、threading,其实它们背后会调用_random、 ...
25-解密Python中的多线程(第一部分):初识GIL、以及多个线程之间的调度机制
25-解密Python中的多线程(第一部分):初识GIL、以及多个线程之间的调度机制楔子这次我们来说一下Python中的多线程,在上篇博客中我们说了Python的线程,我们说Python中的线程是对OS线程进行了一个封装,并提供了一个线程状态(PyThreadState)对象,来记录OS线程的一些状态信息。
那什么是多线程呢?首先线程是操作系统调度cpu工作的最小单元,同理进程则是操作系统资源分配的最小单元,线程是需要依赖于进程的,并且每一个进程只少有一个线程,这个线程我们称之为主线程。而主线程则可以创建子线程,一个进程中如果有多个线程去工作,我们就称之为多线程。
开发一个多线程应用程序是很常见的事情,很多语言都支持多线程,有的是原生支持,有的是通过库的支持。而Python毫无疑问也支持多线程,并且它是通过threading这个库的方式实现的。另外提到Python的多线程,会让人想到GIL(global interpreter lock)这个万恶之源,我们后面会详细介绍。目前我们知道Python中的多线程是不能利用多核的,因为Python虚拟机使用一个全局解释器锁(GIL)来控制线程对 ...
24-Python运行时的环境初始化
24-Python运行时的环境初始化楔子我们之前分析了Python的核心–字节码、以及虚拟机的剖析工作,但这仅仅只是一部分,而其余的部分则被遮在了幕后。记得我们在分析虚拟机的时候,曾这么说过:
当Python启动后,首先会进行 “运行时环境” 的初始化,而关于 “运行时环境” 的初始化是一个非常复杂的过程。并且 “运行时环境” 和 “执行环境” 是不同的, “运行时环境” 是一个全局的概念,而 “执行环境” 是一个栈帧。关于”运行时环境”我们后面将用单独的一章进行剖析,这里就假设初始化动作已经完成,我们已经站在了Python虚拟机的门槛外面,只需要轻轻推动一下第一张骨牌,整个执行过程就像多米诺骨牌一样,一环扣一环地展开。
所以这次,我们将回到时间的起点,从Python的应用程序被执行开始,一步一步紧紧跟随Python的轨迹,完整地展示Python在启动之初的所有动作。当我们根据Python完成所有的初始化动作之后,也就能对Python执行引擎执行字节码指令时的整个运行环境了如执掌了。
线程环境初始化我们知道线程是操作系统调度的最小单元,那么Python中的线程又是怎么样的呢? ...