python基础
深浅拷贝
深拷贝
表面理解:对象A赋值给对象B,且我们修改对象B中的数据和或方法,对象A不受影响,我们称之为深拷贝
底层理解:深拷贝拷贝的是原数据的值,就是把新的数据重新的开辟一个新内存进行存储
(深拷贝是对一个对象的所有层次的拷贝(递归),内部和外部都会被拷贝到新的内存地址)
对于可变与不可变类型
对于不可变类型(字符串(str)、数字(number)、元组 (tuple)),深拷贝会和浅拷贝一样,拷贝的是引用,不会创建新的内存地址
对于可变类型( 列表(list)、字典(dictionary)、集合 (set)),深拷贝会拷贝每一层,新建内存地址进行储存
浅拷贝
表层理解:对象A赋值给对象B,且我们修改对象B中的数据或方法,对象A会受到影响,我们就称之为浅拷贝
底层理解:浅拷贝是拷贝的是原数据的引用
(浅拷贝是对一个对象父级(最外层)的拷贝,并不会拷贝子级(内部))
对于可变与不可变类型
对于不可变类型(字符串(str)、数字(number)、元组 (tuple)),浅拷贝仅仅是地址指向,不会开辟新空间
对于可变类型( 列表(list)、字典(dictionary)、集合 (set)),浅拷贝会开辟新的空间地址(仅仅是最顶层开辟了新的空间,里层的元素地址还是一样的),进行浅拷贝
可变与不可变类型
可变数据类型 :可以在原数据的基础上进行修改,修改后内存地址不变
常见的可变类型:列表(list)、字典(dictionary)、集合 (set)
不可变数据类型:不能在原数据基础上进行修改,重新赋值后内存地址改变
常见的不可变类型:字符串(str)、数字(number)、元组 (tuple)
字符编码
-
ASCII:ASCII不支持中文字符
-
GBK:是中国的中文字符(中国人自己研发的),其包含了简体中文和繁体中文的字符
-
Unicode : 万国编码(Unicode 包含GBK)
-
Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码
-
规定虽有的字符和符号最少由 16 位来表示(2个字节),即:2 **16 = 65536
-
这里还有个问题:使用的字节增加了,那么造成的直接影响就是使用的空间就直接翻倍了
-
-
Utf-8 : 可变长码, 是Unicode 的扩展集
-
UTF-8编码:是对Unicode编码的压缩和优化,他不再使用最少使用2个字节,而是将所有的字符和符号进行分类
-
ASCII码中的内容用1个字节保存、欧洲的字符用2个字节保存,东亚的字符用3个字节保存
-
python2和python3的区别
-
从编码的角度来说
python2 默认编码方式ASCII码
(不能识别中文,要在文件头部加上 #-– encoding:utf-8 –– 指定编码方式)
python3 默认编码方式UTF-8
(能识别中文,是Unicode 的扩展集)(可识别中文)
-
从输出(print)的角度来说
python2中加不加括号都可以打印
python3中必须加括号
-
从输入(input)的角度来说
python2 raw_input()
python3 input()
-
从range()函数的角度来说
python2 range()/xrange()
python3 range()
-
从类的角度来说
python3 中都是新式类python2 中经典类和新式类混合
新式类中使用广度优先,经典类中使用深度优先
python3 可以使用superpython2 不能使用super
-
从字符串的角度来说
python2中字符串有str和unicode两种类型
python3 中字符串有str和字节(bytes) 两种类型
-
从语法格式角度来说
python3中不再支持u中文的语法格式
python2中支持
进程、线程、协程
进程
介绍
-
进程是资源分配的最小单位
-
一个可执行的的程序被系统加载在内存中
进程属性
-
进程与进程之间不能相互访问
-
都有自己独立的内存
-
不共享任何状态
-
调度由操作系统完成
进程的特征
-
动态性
:进程的实质是程序在多道程序系统执行的一次过程,进程是动态产生、动态消亡的 -
并发性
:进程之间可以一起并发执行 -
独立性
:进程是一个能独立运行的基本单位 -
异步性
:进程之间的进度不同,不是同步的 -
结构特征
:进程由程序、数据和进程控制块三部分
线程
介绍
-
线程是操作系统能够进行运算调度的最小单位
-
线程是包含在进程中的
-
进程自己是无法执行的,是靠线程进行执行的
特点
-
一个进程中可以有多个线程,每个线程并行执行不同的任务
-
线程的资源可以共享
-
线程可以操作同一进程中的其他线程
协程
介绍
-
协程是微线程
-
协程拥有自己的寄存器上下文和栈
-
协程能保留上一次调用时的状态
协程最主要的作用:是在单线程的条件下实现并发的效果
为什么能处理并发
-
遇IO组自动切换
-
内部封装的有Greenlet模块(遇到IO手动切换) 和 Gevent模块(遇到IO自动切换)
协程的好处
-
无需线程上下文切换,因为协程拥有自己的寄存器
-
不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量 冲突
-
用法:最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能
协程的缺点
-
无法利用多核资源:协程的本质是个单线程,它不能同时将单个CPU的多个核用上,协程需要和进程配合才能运行在多CPU上
-
线程阻塞操作会阻塞掉整个程序
协程一般用于网络请求 socket 链接
请求本身是从内核copy到用户空间
什么叫阻塞:进程和线程在获取某一个资源,没有获取到,就是阻塞
扩展知识
进程和线程的区别
-
进程包含线程
-
线程可以共享内存空间,进程的内存是独立的
-
进程可以生成子进程,子进程之间不能互相访问
-
一个进程中线程可以交流,两个进程想通信,必须通过中间代理来实现
-
一个线程可以操作统一进程里的其他线程,单进程只能操作子进程
进程和程序的区别
-
进程是系统进行资源分配和调度的一个独立单位
-
一个程序对应多个进程,一个进程为多个程序服务(两者之间是多对多的关系)
-
一个程序执行在不同的数据集上就成为不同的进程,可以用进程控制块来唯一地标识每个进程
-
程序只是一个普通文件,是一个机器代码指令和数据的集合,所以,程序是一个静态的实体 , 而进程是程序运行在数据集上的动态过程,进程是一个动态实体,它应创建而产生,应调度执行因等待资源或事件而被处于等待状态,因完成任务而被撤消
进程、线程、协程区别
-
先有进程,然后进程可以创建线程,线程是依附在进程里面的, 线程里面可以包含多个协程
-
进程之间不共享全局变量,线程之间共享全局变量,但是要注意资源竞争的问题
-
多进程开发比单进程多线程开发稳定性要强,但是多进程开发比多线程开发资源开销要大
-
多线程开发线程之间执行是无序的,协程之间执行按照一定顺序交替执行
-
协程以后主要用在网络爬虫和网络请求,开辟一个协程大概需要5k空间,开辟一个线程需要512k空间, 开辟一个进程占用资源最多
有了进程为什么还要线程
-
进程只能在同一时间干一件事
-
进程在执行的过程中如果阻塞,即使进程中有些工作不依赖输入的数据,也无法执行
GIL全局解释器
介绍
每个线程在执⾏的过程都需要先获取GIL,保 证同⼀时刻只有⼀个线程可以执⾏代码
什么时候释放GIL锁
-
在遇到IO操作时会释放这把锁,但在执⾏完毕后,必须重新获取
-
会有一个专门的解释器进行计数,当数值达到100时,这时候释放GIL锁,让别的线程有机会执行
GIL特性
-
本质就是一把互斥锁,相当于执行权限,每个进程内都会存有一把GIL锁
-
同一进程的多个线程必须抢到GIL锁才能使用CPython解释器解释执行自己的代码
-
同一个进程的多个线程无法实现并行,但可以实现并发
-
如果没有GIL锁的话,多线程拿到解释器并行,当x=10的线程中内存产生一个10,还没来得及绑定x,就有可能会被垃圾回收机制回收,为了保证数据的安全,加GIL锁
GIL的优点
-
避免了大量加锁解锁的过程
-
保证CPython解释器内存管理的线程的安全
GIL的缺点
CPython解释器的多线程无法实现并行,无法利用多核优势
怎么解决GIL
-
更换cpython为jpython,比较慢(不建议使用)
-
使用多进程完成多线程的任务
-
在使用多线程可以使用C语言去实现
为什么有了GIL锁,还需要加线程锁?
因为CPU是分时的 ,如果GIL处理的任务特别繁琐,到一定时间会自动切换其他进程 造成混乱 ,所以要加一个线程锁
线程锁
线程锁
当多线程同时进行任务时,为了保证不会有多个线程不会对同一个数据进行操作,就需要对这个线程加一个线程锁
线程锁的优点:
-
确保了某一段代码只能有一个线程从头到尾完整的执行。
线程锁的缺点:
-
首先是阻止了多线程的多发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大的降低。
-
其次由于可以存在多个锁,不同线程存在不同的锁,并试图获取对方持有的锁时,可以会造成锁死,导致多个线程全部挂起,既不能执行也不能结束,只能靠操作系统强制终止。
线程互斥锁和GIL的区别
1.线程互斥锁是Python代码层面的锁,解决Python程序中多线程共享资源的问题(线程数据共共享,当各个线程访问数据资源时会出现竞争状态,造成数据混乱);
2.GIL是Python解释层面的锁,解决解释器中多个线程的竞争资源问题(多个子线程在系统资源竞争是,都在等待对象某个部分资源解除占用状态,结果谁也不愿意先解锁,然后互相等着,程序无法执行下去)
装饰器、生成器、迭代器
装饰器
定义:在不修改源代码和调用方式的情况下给其他函数添加其他功能
格式: @关键字+装饰函数
按照形式可以分为:无参装饰器和有参装饰器,有参装饰器即给装饰器加上参数
装饰器必须准寻得原则
-
不修改被装饰器的函数的源代码
-
不修改装饰器函数的调用方式
-
在满足前者条件的情况下增加额外的功能
装饰器基于闭包
实现装饰器知识储备
装饰器 = 高阶函数 + 函数嵌套 + 闭包
生成器
介绍:使用yield的函数被称为生成器,生成器是一个特殊的迭代器(生成器会记住上一次返回时在函数体中的位置)
在Python中,一边循环,一边计算的机制,称为生成器
生成器是一个特殊的迭代器,他的实现更加简单优雅
yield是生成器实现next()方法的关键
生成器的特点
-
节约内存,在超大列表的情况下是非常吃内存的!
-
迭代到下⼀次的调⽤时,所使⽤的参数都是第⼀次所保留下的,即是说,在整个所有函数调⽤的参数都是第⼀次所调⽤时保留的,⽽不是新创建的,这样非常高效!迭代是访问集合元素的⼀种⽅式。迭代器是⼀个可以记住遍历的位置的对象。迭代器对象从集合的第⼀个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退!
yield与return有何区别?
return只能返回一次函数就彻底结束了,而yield能返回多次值
yield到底干了什么事情:
yield把函数变成生成器(生成器就是迭代器)
函数在暂停以及继续下一次运行时的状态是由yield保存
迭代器
介绍:迭代器是访问集合内元素的一种方式。
迭代器对象从集合的第一个元素开始访问,直到所有的元素都被访问一遍后结束。
代器的两个基本的方法: iter() 和 next()
为什么要用迭代器
优点
-
迭代器提供了一种不依赖于索引的取值方式,这样就可以遍历那些没有索 引的可迭代对象了(字典,集合,文件)
-
迭代器与列表比较,迭代器是惰性计算的,更节省内存
缺点
-
无法获取迭代器的长度,使用不如列表索引取值灵活
-
一次性的,只能往后取值,不能倒着取值
字符串、列表、字典、集合、元组、文件都是可迭代的,但是只有文件是迭代器
闭包
定义
内层函数对外层函数非全局变量的引用就叫做闭包
闭包的特性
如果函数闭包,那么该变量在内存中,不会随着函数运行完成,而被释放掉,可重复使用
闭包的特点
-
必须有一个内嵌函数
-
内嵌函数必须引用外部函数中的变量
-
外部函数的返回值必须是内嵌函数
闭包中内函数修改外函数局部变量
-
在基本的python语法当中,一个函数可以随意读取全局数据,但是要修改全局数据的时候有两种方法:
-
global 声明全局变量
-
全局变量是可变类型数据的时候可以修改
-
-
在闭包情况下使用下面两种方法修改
-
在python3中,可以用nonlocal 关键字声明 一个变量, 表示这个变量不是局部变量空间的变量,需要向上一层变量空间找这个变量
-
在python2中,没有nonlocal这个关键字,我们可以把闭包变量改成可变类型数据进行修改,比如列表。
-
判断是不是闭包
双下滑线 closure方法去测:
①:如果返回cell开头就是闭包函数
②:如果返回None,就不是
高阶函数
map
map()函数接收两个参数,一个是函数,一个是序列,
map将传入的函数依次作用到序列的每个元素,并把结果作为新的list返回
##首字母大写,其他小写
from functools import reduce
def normalize(name):
s = name.lower() #将传入的name参数序列全部小写.
b = s.capitalize()#利用首字母大写函数capitalize
return b
L1 = ['adam', 'LISA', 'barT','JASon']
L2 = list(map(normalize, L1))
print(L2)
结果为: ['Adam', 'Lisa', 'Bart', 'Jason']
##利用map直接将整数转换为str
print(list(map(str,[1,2,3,4,5,6,7,8])))
结果为:['1', '2', '3', '4', '5', '6', '7', '8']
reduce
函数套函数,reduce必须接收两个函数,reduce将结果与序列的下一个元素做累积计算
>>> from functools import reduce
>>> def f(x, y):
... return x + y
...
>>> reduce(f, ['ab', 'c', 'de', 'f'])
'abcdef'
filter
用来对数据进行过滤的
a = filter(lambda x:x%2 == 1,range(1,20))
print(list(a))
结果为:
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
sorted
用来排序的,也可以按照自己的定义排序
>>> sorted([6, -2, 4, -1])
[-2, -1, 4, 6]
>>> sorted([6, -2, 4, -1], key=abs)
[-1, -2, 4, 6]
>>> sorted([6, -2, 4, -1], key=abs, reverse=True)
[6, 4, -2, -1]
>>> sorted(['Windows', 'iOS', 'Android'])
['Android', 'Windows', 'iOS']
>>> d = [('Tom', 170), ('Jim', 175), ('Andy', 168), ('Bob', 185)]
>>> def by_height(t):
... return t[1]
...
>>> sorted(d, key=by_height)
[('Andy', 168), ('Tom', 170), ('Jim', 175), ('Bob', 185)]
类方法、静态方法
类方法
定义
使用装饰器@classmethod。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法
调用
实例对象和类对象都可以调用。
理解
原则上,类方法是将类本身作为对象进行操作的方法。假设有个方法,且这个方法在逻辑上采用类本身作为对象来调用更合理,那么这个方法就可以定义为类方法。另外,如果需要继承,也可以定义为类方法。
静态方法
定义
使用装饰器@staticmethod。参数随意,没有“self”和“cls”参数,但是方法体中不能使用类或实例的任何属性和方法
调用
实例对象和类对象都可以调用。
理解
静态方法是类中的函数,不需要实例
静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作。可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护。
上下文管理(with)
介绍
上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明enter和exit方法
该协议的对象要实现这两个方法。
用途
1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在exit中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处
三元运算符
格式
result = 值1 if 条件成立 result = 1 否则 result = 2
作用:减少代码量,是对简单的条件语句的缩写
lambda表达式
格式:lambda的一般形式是关键字lambda后面跟一个或多个参数,紧跟一个冒号,之后是一个表达式
#lambda基本使用#
f = lambda x,y,z:x+y+z
print(f(1,2,3)) # 6
my_lambda = lambda arg : arg + 1
print(my_lambda(10)) # 11
Python的基础数据类型
-
Numbers(数字)——用于保存数值
-
Strings(字符串)——字符串是一个字符序列。我们用单引号或双引号来声明字符串。
-
Lists(列表)——列表就是一些值的有序集合,我们用方括号声明列表。
-
Tuples(元组)——元组和列表一样,也是一些值的有序集合,区别是元组是不可变的,意味着我们无法改变元组内的值。
-
Dictionary(字典)——字典是一种数据结构,含有键值对。我们用大括号声明字典
猴子补丁
在运行期间动态修改一个类或模块
class A:
def func(self):
print("Hi")
def monkey(self):
print("Hi, monkey")
m.A.func = monkey
a = m.A()
a.func()
select、poll、epoll(重点)
select、poll、epoll都是IO多路复用
一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作
select
用途
在一段指定时间内,监听用户所感兴趣的文件描述符上的可读、可写和异常事件
缺点
-
所能监视的文件描述符的数量有限制,sizeof(fd_set)=128,说明能监视的描述符的最大值为128*8=1024个
-
不能告诉用户程序具体哪个连接有数据
适用场景
适用于所监视的文件描述符数量较少的场景
poll
poll系统调用和select类似,也是在制定时间内轮询一定数量的文件描述符,以测试其中是否有就绪的文件描述符
优点
-
所能监视的文件描述符的数量没有限制
-
也不用每次都把fd集合从用户区拷贝数据到内核,它使用一个 struct pollfd结构体来维护每个fd
缺点
它本质上是和selece一样的,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多
适用场景
也适用于所监视文件描述符少的场景
epoll
epoll是Linux特有的IO复用函数,被认为性能最好的一种方法
特点
不仅没有最大监控数量限制,还能告诉用户程序哪个连接有活跃
epoll() 中内核则维护一个链表,epoll_wait 直接检查链表是不是空就知道是否有文件描述符准备好了
在内核实现中 epoll 是根据每个 sockfd 上面的与设备驱动程序建立起来的回调函数实现的。
垃圾回收机制
-
引用计数
理解
当一个对象的引用被创建过着复制时,对象的引用计数加1,当一个对象的引用被移除时,对象的引用就是减1,当对象的引用计数为0时,就意味着对象已经不再被使用,可以将其内存释放掉
缺点
对于循环引用的对象无法进行回收
-
标记清除((建立在引用计数技术基础之上)):解决循环引用问题
当引用计数为0的时候就给这个对象打上一个标签”可清除“,但是不会立马清除,而是会等到系统给程序分配的内存要用完之时,停下来将可清除标签的对象销毁然后继续。
-
分代回收(建立在标记清除技术基础之上)
分代回收共分为三个“代”:年轻代、中年代、老年代,他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小
新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,
当中年代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到老年代去,
依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内
同时,分代回收是建立在标记清除技术基础之上。分代回收同样作为Python的辅助垃圾收集技术处理那些容器对象.
TCP、UTP
TCP三次握手
第一次握手
:建立连接时,客户端发送SYN包到服务端,并进入SYN_SENT状态,等待服务器确认。
SYN:同步序列编号
第二次握手
:服务器收到SYN包,必须确认客户的SYN包,同时自己也发送一个SYN包,即SYN+ACK包,此时服务器进入SYN_RECV状态
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入,ESTABLISHED(TCP连接成功)状态,完成三次握手。
面向对象(三大特征)
封装
属性和方法放到类内部,通过对象访问属性或者方法,隐藏功能的实现细节.当然还可以设置访问权限
简单的说,一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。
在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。
通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。
继承
新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类
注意
-
继承中的方法调用顺序, 如果自己有就调用自己的, 如果自己没有就调用父类的
-
在继承中方法可以重写, 但是属性(成员变量)不能重写
继承的缺点
耦合性太强(依赖性太强)
继承的优点
提高代码的复用性,可以让类与类之间产生关系, 正是因为继承让类与类之间产生了关系所以才有了多态
用子类的对象,调用父类的方法如果子类中没有这个方法,直接使用父类的如果子类中有与父类同名的方法: 经典类:指名道姓调用 类名.方法名(子类对象) 类内外一致 新式类:使用super方法,super(子类名,子类对象).方法名() 类内可以省略super的参数
继承概念的实现方式有二类:实现继承与接口继承。
实现继承是指直接使用基类的属性和方法而无需额外编码的能力;
接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
多态
同一个方法不同对象调用同一个方法功能的表现形式不一样
封装,继承和多态的本质目的都是为了快速开发,提高代码复用性
读写文件(三种读操作比较)
open函数用来打开文件
open(name[, mode[, buffering]])
打开文件可传的参数
打开文件的模式
-
r,只读模式(默认)。
-
w,只写模式。【不可读;不存在则创建;存在则删除内容;】
-
a,追加模式。【可读; 不存在则创建;存在则只追加内容;】
-
注: “+” 表示可以同时读写某个文件
-
w,只写模式。【不可读;不存在则创建;存在则删除内容;】• w+,写读
• a+,同a
三种读操作比较
readline()
每次读取一行,当前位置移到下一行(迭代读)
readline()作用
readline 的用法,速度是fileinput的3倍左右,每秒3-4万行,好处是 一行行读 ,不占内存,适合处理比较大的文件,比如超过内存大小的文件
readline读取大文件
f1 = open('test02.py','r')
f2 = open('test.txt','w')
while True:
line = f1.readline()
if not line:
break
f2.write(line)
f1.close()
f2.close()
readlines()
读取整个文件所有行,保存在一个列表(list)变量中,每行作为一个元素
readlines()作用
readlines会把文件都读入内存,速度大大增加,但是木有这么大内存,那就只能乖乖的用readline
#readlines读文件#
f1=open("readline.txt","r")
for line in f1.readlines():
print(line)
read(size)
从文件当前位置起读取size个字节,如果不加size会默认一次性读取整个文件(适用于读取小文件)
read(n)读取指定长度的文件
f = open(r"somefile.txt")
print(f.read(7)) # Welcome 先读出 7 个字符
print(f.read(4)) #‘ to ‘ 接着上次读出 4 个字符
f.close()
seek(offset[, whence]) 随机访问 :从文件指定位置读取或写入
f = open(r"somefile.txt", "w")
f.write("01234567890123456789")
f.seek(5)
f.write("Hello, World!")
f.close()
f = open(r"somefile.txt")
print(f.read()) # 01234Hello, World!89
tell 返回当前读取到文件的位置下标
f = open(r"somefile.txt")
f.read(1)
f.read(2)
print(f.tell()) # 3 3就是读取到文件的第三个字符