Python 语言如今风靡一时,因其简单易学的语法和庞大完善的社区支持深受程序员的喜爱,本篇博文主要整理一下 Python 相关的基础知识以及一些汇总。
一、知识点概览
- 1.Python 基本数据结构与类型转换
- 2.作用域和命名空间
- 3.内存管理和垃圾回收
- 4.异常处理
- 5.类与对象
- 6.闭包与装饰器
- 7.实例方法、静态方法和类方法
- 8.魔法方法
- 9.模块与包
- 10.迭代器和生成器
- 11.浅拷贝和深拷贝
- 12.常见的高阶函数
- 13.多任务和
GIL
- 14.PEP8 规范
二、解析与说明
1.Python 基本数据结构与类型转换
1.1.常见数据类型
数据类型 | 操作 | 时间复杂度 | 是否有序 | 是否可变 | |
---|---|---|---|---|---|
平均情况 | 最坏情况 | ||||
字符串 | 查 | O(n) | O(n) | 是 (√) | 否 (×) |
列表 | 增 | O(1) | O(1) | 是 (√) | 是 (√) |
删 | O(n) | O(n) | |||
改 | O(1) | O(1) | |||
查 | O(n) | O(n) | |||
元组 | 查 | O(n) | O(n) | 是 (√) | 否 (×) |
集合 | 增 | O(1) | O(1) | 否 (×) | 是 (√) |
删 | O(1) | O(1) | |||
查 | O(1) | O(n) | |||
交集(s&t) | O(min(len(s), len(t)) | O(len(s) * len(t)) | |||
并集(s|t) | O(len(s)+len(t)) | ||||
补集(s-t) | O(len(s)) | ||||
对称补集(s^t) | O(len(s)) | O(len(s) * len(t)) | |||
字典 | 增 | O(1) | O(n) | 否 (×) | 是 (√) |
删 | O(1) | O(n) | |||
改 | O(1) | O(n) | |||
查 | O(n) | O(n) |
n
:容器中元素的数量。
数据类型相关的内置操作方法可参考:Python(四)- 数据类型及常用的操作!
1.2.类型转换
类型转换是指将一种数据类型转换为另一种数据类型。
- int() :将其他数据类型转换为整数类型
- float(): 将其他数据类型转换为
float
类型 - ord() : 将字符转换为整数
- hex() : 将整数转换为十六进制
- oct() : 将整数转换为八进制
- tuple(): 此函数用于转换为元组。
- set() : 此函数在转换为 set 后返回类型。
- list() : 此函数用于将任何数据类型转换为列表类型。
- dict() : 此函数用于将顺序元组(键,值)转换为字典。
- str() : 用于将整数转换为字符串。
- complex(real,imag): 此函数将实数转换为复数。
2.作用域和命名空间
2.1.命名空间
命名空间是从名称到对象的映射,大部分的命名空间都是通过 Python 字典来实现的。
命名空间是一个命名系统,各个命名空间是独立的,用于确保名称是唯一性,以避免命名冲突。
一般有三种命名空间:
- 内置名称(built-in names):Python 语言内置的名称,比如函数名 abs、char 和异常名称 BaseException、Exception 等等。
- 全局名称(global names):模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。
- 局部名称(local names):函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量。(类中定义的也是)
命名空间查找顺序: 局部的命名空间 -> 全局命名空间 -> 内置命名空间。如果找不到变量,它将放弃查找并引发一个 NameError 异常。
命名空间的生命周期:命名空间的生命周期取决于对象的作用域,如果对象执行完成,则该命名空间的生命周期就结束。因此,无法从外部命名空间访问内部命名空间的对象。
2.2.作用域
作用域
就是一个 Python 程序可以直接访问命名空间的正文区域。
Python 有四种作用域:
- L(Local):本地作用域,最内层,包含局部变量,比如一个函数/方法内部。
- E(Enclosing):当前作用域被嵌入的本地作用域,包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套函数,一个函数(或类) A 里面又包含了一个函数 B ,那么对于 B 中的名称来说 A 中的作用域就为 nonlocal。
- G(Global):全局/模块作用域,当前脚本的最外层,比如当前模块的全局变量。
- B(Built-in): 内置作用域,包含了内建的变量/关键字等,最后被搜索。
规则顺序: 本地作用域(L) –> 当前作用域被嵌入的本地作用域(E) –> 全局/模块作用域(G) –> 内置作用域(B)。
全局变量:在函数外或全局空间中声明的变量称为全局变量。这些变量可以由程序中的任何函数访问。
局部变量:在函数内声明的任何变量都称为局部变量。此变量存在于局部空间中,而不是全局空间中。
参考:菜鸟教程
3.内存管理和垃圾回收
python 中的内存管理由 Python 私有堆空间管理。所有 Python 对象和数据结构都位于私有堆中。程序员无权访问此私有堆。python 解释器负责处理这个问题。
Python 对象的堆空间分配由 Python 的内存管理器完成。核心 API 提供了一些程序员编写代码的工具。
Python 还有一个内置的垃圾收集器,它可以回收所有未使用的内存,并使其可用于堆空间。
从三个方面说明:
- 对象的引用计数机制
- 垃圾回收机制
- 内存池机制
3.1.对象的引用计数机制
Python 内部使用引用计数,来保持追踪内存中的对象,所有对象都有引用计数。
- 引用计数增加的情况:
- 一个对象分配一个新名称
- 将其放入一个容器中(如列表、元组或字典) +引用计数减少的情况:
- 使用 del 语句对对象别名显示的销毁
- 引用超出作用域或被重新赋值
sys.getrefcount( )函数可以获得对象的当前引用计数,
多数情况下,引用计数比你猜测得要大得多。对于不可变数据(如数字和字符串),解释器会在程序的不同部分共享内存,以便节约内存。
3.2. 垃圾回收
- 当一个对象的引用计数归零时,它将被垃圾收集机制处理掉。
- 当两个对象 a 和 b 相互引用时,del 语句可以减少 a 和 b 的引用计数,并销毁用于引用底层对象的名称。然而由于每个对象都包含一个对其他对象的应用,因此引用计数不会归零,对象也不会销毁。(从而导致内存泄露)。为解决这一问题,解释器会定期执行一个循环检测器,搜索不可访问对象的循环并删除它们。
3.3. 内存池机制
Python 提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统。
- Pymalloc 机制。为了加速 Python 的执行效率,Python 引入了一个内存池机制,用于管理对小块内存的申请和释放。
- Python 中所有小于 256 个字节的对象都使用 pymalloc 实现的分配器,而大的对象则使用系统的 malloc。
- 对于 Python 对象,如整数,浮点数和 List,都有其独立的私有内存池,对象间不共享他们的内存池。也就是说如果你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。
4.异常处理
如果我们没有对异常进行任何预防,那么在程序执行的过程中发生异常,就会中 断程序,调用 python 默认的异常处理器,并在终端输出异常信息。
Python 中的异常处理语句有:
try...except...finally
语句: 当try
语句执行时发生异常,回到try
语句层, 寻找后面是否有except
语句。找到 except 语句后,会调用这个自定义的异常处 理器。except
将异常处理完毕后,程序继续往下执行。finally
语句表示,无论 异常发生与否,finally 中的语句都要执行。assert
语句:判断assert
后面紧跟的语句是True
还是False
,如果是True
则 继续执行print
,如果是False
则中断程序,调用默认的异常处理器,同时输出assert
语句逗号后面的提示信息。with
语句:如果with
语句或语句块中发生异常,会调用默认的异常处理器处理,但文件还是会正常关闭。
5.类与对象
类(class):即某一类事物的统称。它们具有相同的属性,例如狗,狗的毛色多样,其颜色就属于狗的 属性(也被叫做变量)。
对象(object):黑狗,黄狗等等都是对象,这个对象就是类的 实例(instance)。其作用为属性引用。
Python 类可以看作属性和方法的集合,其中定义了每个对象所共有的属性和方法。
对象为类的实例,对象可调用类的属性和方法。
Python 中声明一个类,需要:关键字(class) + 标识符(类名) + 继承类。
类的实例化需要一个初始化方法(__init__()
)。
如:
1 | class Animal(object): |
面向对象的三大特性:
- 封装: 将抽象得到的数据和行为(功能)相结合,形成一个有机的整体(即类)。广义的说法,即类与函数本身也是一种封装的体现;狭义的说法是将某些不想被外界访问的属性进行私有化,对外提供接口和访问方法。
- 继承: python 中,一个类可以继承一个或多个父类。
- 多态: 一类事物的多种形态,如:动物类可衍生猫、狗、猪类等等,Python 的多态通过继承实现,子类在执行相同的方法时表现出不同状态。
5.1.封装
封装的原则:
- 将不需要对外提供的内容都隐藏起来;
- 把属性都隐藏,提供公共方法对其访问。
封装的作用:
- 提高复用性
- 降低冗余度
- 提高安全性
5.2.继承
其作用是:
- 减少重复代码,提高空间利用率
- 增加代码的可读性,避免冗长阅读
- 提高可维护性,代码快速定位
子类继承自父类,可以重用父类的方法,也可以覆写该继承方法(派生),如:
1 | # 修改上述Dog类 |
多继承:遵循广度优先遍历顺序
5.3.多态
多态意味着多种形式,其作用是让程序在不同情况下用一个函数名启用不同形态的方法,它给予了面向对象系统极大的灵活性。
如:
1 | class Cat(Animal): |
6.闭包与装饰器
6.1.闭包
闭包(closure)是函数式编程的重要的语法结构,也是一种组织代码的结构, 它同样提高了代码的可重复使用性。
当一个内嵌函数引用其外部作作用域的变量,我们就会得到一个闭包。
总结一下,创建一个闭包必须满足以下几点:
- 必须有一个内嵌函数
- 内嵌函数必须引用外部函数中的变量
- 外部函数的返回值必须是内嵌函数
6.2.装饰器
装饰器本质上是一个 Python 函数
,它可以让其他函数在不需要做任何代码变动 的前提下,增加额外功能,装饰器的返回值也是一个函数对象。
它经常用于有切面 需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。
有了装饰器,就可以抽离出大量与函数功能本身无关的相似代码并继续重用。
7.实例方法、静态方法和类方法
7.1.实例方法
Python 中,实例方法定义在类中,它的第一个参数为 self
。
self
在定义类的方法时是必须有的,虽然在调用时不必传入相应的参数。
注意:self
代表类的 实例,而不是这个类。
如:
1 | class Test(object): |
7.2.静态方法(staticmethod)
在类中无需实例参与即可调用的方法(无需 self
参数),在调用过程中,无需创建类的实例,通过类直接来调用的方法。
在 Python 中,静态方法使用装饰器 @staticmethod
来声明。
使用场景:开发过程中需要定义的一些方法和类有关,但在实现时并不需要引用类或者实例,此时用静态方法,将其作为一个封装在类中的普通函数。
好处是调用时它返回一个真正的函数,且是同一实例。
例:
1 | class TestS(object): |
7.3.类方法(classmethod)
类方法传入的第一个参数为 cls
,是类本身。
其意义是表明为类对象所拥有的方法。
类方法可以通过类直接调用,也可以通过类的实例来调用。
在 Python 中,类方法使用装饰器 @classmethod
来声明。
使用场景:大多情况下,可以替代静态方法。增加了一个对实际调用类的引用。
例:
1 | class TestC(object): |
7.4.@property
属性装饰器
其作用是把一个方法变成属性。
对于 @property
,在对实例属性操作的时候,该属性的读写通过 getter 和 setter 方法来实现。
1 | class Student(object): |
上面的 birth
是可读写属性,而 age
就是一个只读属性,因为 age
可以根据 birth
和当前时间计算出来。
8.魔法方法
在 Python 中,一般我们将类内外定义的函数,称之为方法和函数,差别不大,只是写法上的差异。
但是在类中,有一些特殊的方法,被归为一类,这些特殊方法以前后双下划线和字母组成(__xxx__
),我们称之为魔法方法。
常见的魔法方法有:
基本方法:
方法 描述 补充说明 __init__(self, *args, **kwargs)
构造器,当一个实例对象被定义时调用 初始化对象,类似于 C++的构造函数 __new__(cls, *args, **kwargs)
在一个对象实例化的时候所调用的第一个方法,返回一个实例 实例化方法,决定是否要使用该 __init__
方法,如果未返回实例对象,则__init__
不会被调用__call__(self, *args, **kwargs)
允许一个类像函数一样被调用 class_obj(*args, **kwargs)
实际调用的是class_obj.__call__(*args, **kwargs)
__del__(self)
析构器,当删除一个实例对象时调用 类似于 C++的析构函数 __len__(self)
获得实例对象的长度 相当于 len(obj)
__repr__(self)
将实例对象转化为字符串的形式,给解释器看 与函数 repr(obj)功能相同 __str__(self)
将实例对象转化为字符串的形式,给程序员看 相当于 str(obj) … … … 运算符操作:
方法 描述 补充说明 __add__(self, other)
加法:+ __sub__(self, other)
减法:- __mul__(self,other)
乘法:* __truediv(self, other)
除法:/ 返回一个浮点数 __floordiv(self, other)
整除:// 向下取整 __mod__(self, other)
求余:% __pow__(self, other[, mod])
幂运算 相当于函数 pow(base, exp, mod=None)
__and__(self, other)
按位与 相当于 &
__or__(self, other)
按位或 相当于 ` … … … 比较操作符:
方法 描述 补充说明 __gt__(self, other)
大于:> 以 >
判断__lt__(self, other)
小于:< 以 <
判断__eq__(self, other)
等于:== 以 ==
判断__ne__(self, other)
不等于:!= 以 !=
判断… … … 属性操作:
方法 描述 __getattr__(self, name)
访问一个不存在的属性时调用 __setattr__(self, name, value)
设置属性时调用 __delattr__(self, name)
删除一个属性时调用 __getattribute(self, name)
访问存在的属性时调用 … … 容器类型操作:
方法 描述 __iter__(self)
定义迭代器对象的行为 __next__(self)
定义迭代器中元素的行为 __getitem__(self, key)
获取容器中指定元素的行为 __setitem__(self, key, value)
设置容器中指定元素的行为 __delitem__(self, key)
删除容器中指定元素的行为 … …
参考:python魔法方法总结
9.模块与包
Python 模块是包含 Python 代码的.py 文件。此代码可以是函数类或变量。一些常用的内置模块包括:sys、math、random、time、json、os、re。
Python 包是包含多个模块的命名空间。即是一个目录,目录下可以有多个模块文件,必须要有一个 __init__.py
文件
10.迭代器和生成器
首先,搞清楚几个概念:容器(container)、可迭代对象(iterable)、迭代器(iterator)、生成器(generator)。
迭代是 Python 最强大的功能之一,是访问集合元素的一种方式。
10.1.容器(container)
容器
是一种把多个元素组织在一起的数据结构,容器
中的元素可通过迭代逐个获取,用 in
, not in
关键字可以判断元素是否在 容器
中。
序列
是指一块可存放多个值的连续内存空间,这些值的排列有一定的顺序(有序),其中的每个值可以通过所在位置的编号(索引)来访问和获取。
Python 中,特殊的 容器
在 collections
中可以找到,通常,序列、字典、迭代器都可作为 容器
。
常见的 容器
对象有:
- 字符串(str)
- 列表(list), 双端队列(deque), …
- 元组(tuple), 命名元组(namedtuple), …
- 集合(set), 固定集合(frozenset), …
- 字典(dict), 有序字典(OrderedDict), …
参考:官方文档
10.2.可迭代对象(iterable)
使用 iter()
内置函数可以返回一个迭代器的对象,就是 可迭代对象
。即凡是可以返回一个迭代器的对象都可称之为 可迭代对象
,可见: 可迭代对象
包含 迭代器
。
可迭代对象
的实现:
- 实现了能返回迭代器的
__iter__
方法; - 没有实现
__iter__
方法,但实现了__getitem__
方法而且其参数是从零开始的索引。
例:
1 | class IterA(object): |
10.3.迭代器(iterator)
迭代器
是一个可以记住遍历的位置的对象。迭代器
对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器
只能往前不会后退。
迭代器
有两个基本的方法:iter()
和 next()
。
将一个类作为一个 迭代器
使用,需要在类中实现两个方法: __iter__()
与 __next__()
。
__iter__()
方法返回一个特殊的迭代器
对象,__next__()
方法(Python2 里是 next()
)会返回下一个可用元素。
例:
1 | class Counter(object): |
10.4.生成器(generator)
在 Python 中,使用了 yield
的函数被称为 生成器
(generator)。
生成器
是一个返回迭代器的函数,只能用于迭代操作。
可以简单理解:在 python 中,生成器
就是一个迭代器。
例:
1 | import sys |
参考:菜鸟教程
11.浅拷贝和深拷贝
- 直接赋值:其实就是对象的(别名)引用(内存地址)的传递。
- 浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象,依然使用原始的引用。
- 深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象(完全独立的对象,新的引用)。
12.常见的高阶函数
变量可以指向函数,函数的参数能接收变量。
如果一个函数能够接收另一个函数作为参数,那么这种函数就称为高阶函数。
在 Python 中,常用的高阶函数有:filter()
、sorted()
、reduce()
、map()
。
12.1.匿名函数(lambda
)
匿名函数没有名字,只是一个表达式,其返回值就是该表达式的结果。
匿名函数也是一个函数对象,可以通过赋值给一个变量来调用,非常简洁方便。
通常将其与高阶函数一起使用。
匿名函数的好处:
- 匿名函数没有名字,可以避免函数名冲突
- 代码简洁,不增加额外变量
例:
1 | func = lambda x,y: x * y |
12.2.filter()
函数
filter()
函数的作用是过滤元素,入参接收一个函数和一个序列。
语法:
filter(func, seq)
参数:
func
:一个函数,用来设置判断条件,返回True
或False
,也可以为none
,若func
为none
,filter()
将返回序列中为True
的元素组成的filter
对象。seq
:一个序列
返回值:过滤func
返回值为True
的元素组成的 filter
对象。
例:
1 | def filter_test(): |
12.3.sorted()
函数
sorted()
函数默认将序列升序排列后,返回一个新的 list
,可以自定义key
来进行排序,也可以通过 reverse
参数设置来指定升/降序, reverse=True
时为降序。
语法:
sorted(iterable, key, reverse)
参数:
iterable
:可迭代对象key
:排序关键字reverse
:升降序(bool
值)
返回值:一个排序后的新列表
例:
1 | def sorted_test(): |
除了sorted()
函数外,Python 中,sort()
函数也具有排序的功能。
语法:
sort(key=None, reverse=False)
其区别在于:
sort()
函数是在原列表上进行元素的排序,不会生成新的列表sort()
函数是列表的操作方法,其他iterable
无法使用
12.4.reduce()
函数
reduce()
函数接收一个函数和一个序列以及一个初始化参数,其作用相当于累计计算。
语法:
reduce(function, sequence, initial=None)
参数:
function
:一个函数sequence
:序列initial
:初始化值
返回值:累计结果
例:
1 | from functools import reduce |
12.5.map()
函数
map()
函数接收一个函数和多个可迭代对象,其作用是分别逐个提取这些iterable
的同位元素,作为函数参数进行处理,即传入函数循环提取后面每个iterable
同位元素,处理后返回结果,其结果个数以最小iterable
计。
语法:
map(func, *iterables)
参数:
func
: 一个函数,接收可迭代对象内的同位元素。*iterables
: 可迭代对象,可以是多个(func
函数接收参数也相应增加)。
返回值:一个map
对象,存储了func
函数对*iterables
的处理结果。
例:
1 | def map_test(): |
13.多任务和 GIL
多任务:指的是操作系统同时运行多个任务。
多任务编程的目的就是通过应用程序利用多个计算机核心达到多任务同时执行的目的,以此来提升程序执行效率。
多任务的处理方式通常为:多进程、多线程、进程池、线程池。
Python 有一个名为 Global Interpreter Lock(GIL)
的全局解释器锁。GIL
确保每次只能执行一个 “线程”。一个线程获取 GIL
执行相关操作,然后将 GIL
传递到下一个线程。
虽然看起来程序被多线程并行执行,但它们实际上只是轮流使用相同的 CPU
核心。
所有这些 GIL
传递都增加了执行的开销。这意味着多线程并不能让程序运行的更快。
13.1.多进程
进程:程序在一个数据集上的一次动态执行过程进程是一个动态的过程,占有 CPU 资源,有一定的生命周期。
程序:是一个静态的描述,不占有计算机资源。
多进程:在 Python 中,有一个多进程库(multiprocessing
)。
- 优点:
- 并行多个任务,提高运行效率
- 空间独立,数据安全,创建方便
- 缺点:
- 进程创建销毁的过程中消耗较多的计算机资源
进程间通信: 管道(Pipe)、消息队列(Queue)、共享内存、信号(signal)、信号量(Semaphore)、套接字(Socket)。
比较项 | 管道 | 消息队列 | 共享内存 |
---|---|---|---|
开辟空间 | 内存 | 内存 | 内存 |
读写方式 | 双向/单向 | 先进先出 | 操作覆盖内存 |
效率 | 一般 | 一般 | 快 |
应用 | 多用于亲缘进程 | 方便灵活、广泛 | 较复杂 |
是否需要互斥机制 | 否 | 否 | 需要 |
13.2.多线程
线程:操作系统中能够进行运算调度的最小单位。包含在进程之中,是进程中的实际运作单位。
线程又称为轻量级的进程,在创建和删除时消耗的计算机资源小。
在 Python 中,有一个多线程库(Threading
),但是由于 GIL
锁的存在,用多线程来加速代码的效果并不是那么的好。
线程的通信:全局变量(需要加锁)、消息队列(Queue)、事件(event)、线程锁(Lock)、条件变量(condition)。
14.PEP8 规范
PEP8 是 python 的一种编程规范,PEP 代表 Python Enhancement Proposal。它是一组规则,指定如何格式化 Python 代码以获得最大可读性。
这里简述其中几条规范:
- 缩进:缩进使用 4 个空格
- 行字符限制:所有行限制的最大字符数为 79
- 导入语句:import 引入语句独占一行,引入顺序:标准库导入 -> 相关第三方库导入 -> 本地导入(推荐使用绝对路径导入)
- 命名:模块应该用简短全小写的名字,类名一般首字母大写,函数名应该小写,如果想提高可读性可以用下划线分隔。
- 注释:行注释为
#
加上一个空格开始每一行的注释,且与代码间隔 2 个空格以上;块注释缩进到其描述代码的相同级别,每一行相当于一个行注释;文档字符串一般在函数方法的代码之前。
编程规范可以大概了解一下,IDE 一般都集成了代码规范类的插件,可以格式化编写代码,如:pycharm、vscode 等等。
可参考:PEP8 官方文档