1.可迭代对象、迭代器、生成器
可迭代对象包括迭代器,序列,字典,支持in和not in访问对象中的元素。迭代器可以记住操作的位置,是可迭代对象的子集,而生成器又是迭代器的子集。
迭代器与其他可迭代对象的区别在于迭代器支持__next()__方法,可以通过iter()方法将可迭代对象转化为迭代器。
生成器是迭代器的子集。
yield可以与函数组合成一个生成器,其作用有2个:1.类似于return,返回一个值,函数执行到此处退出。
2.可以记住每次遍历的位置,再次调用函数,就从上次执行的yield的下一句开始执行。
生成器与迭代器相比,在提高运行速度的同时,大大降低了占用的内存。
可以看个例子:
def foo():
print("starting...")
while True:
res = yield 4
print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(next(g))
输出:
starting...
4
********************
res: None
4
2.深拷贝与浅拷贝
在python中,对象赋值实际上是对象的引用。当创建一个对象,然后把它赋给另一个变量的时候,python并没有拷贝这个对象,而只是拷贝了这个对象的引用。
判断的法则是:看拷贝前后变量的内存地址是否发生变化
(1)直接赋值,默认浅拷贝传递对象的引用而已,原始列表改变,被赋值的b也会做相同的改变
>>> b=alist
>>> print b
[1, 2, 3, ['a', 'b']]
>>> alist.append(5)
>>> print alist;print b
[1, 2, 3, ['a', 'b'], 5]
[1, 2, 3, ['a', 'b'], 5]
(2)copy浅拷贝,没有拷贝子对象,所以原始数据改变,子对象会改变,但注意仅仅是被拷贝的部分
>>> import copy
>>> c=copy.copy(alist)
>>> print alist;print c
[1, 2, 3, ['a', 'b']]
[1, 2, 3, ['a', 'b']]
>>> alist.append(5)
>>> print alist;print c
[1, 2, 3, ['a', 'b'], 5]
[1, 2, 3, ['a', 'b']]
>>> alist[3]
['a', 'b']
>>> alist[3].append('cccc')
>>> print alist;print c
[1, 2, 3, ['a', 'b', 'cccc'], 5]
[1, 2, 3, ['a', 'b', 'cccc']] 里面的子对象被改变了
(3)深拷贝,包含对象里面的自对象的拷贝,所以原始对象的改变不会造成深拷贝里任何子元素的改变
>>> import copy
>>> d=copy.deepcopy(alist)
>>> print alist;print d
[1, 2, 3, ['a', 'b']]
[1, 2, 3, ['a', 'b']]始终没有改变
>>> alist.append(5)
>>> print alist;print d
[1, 2, 3, ['a', 'b'], 5]
[1, 2, 3, ['a', 'b']]始终没有改变
>>> alist[3]
['a', 'b']
>>> alist[3].append("ccccc")
>>> print alist;print d
[1, 2, 3, ['a', 'b', 'ccccc'], 5]
[1, 2, 3, ['a', 'b']] 始终没有改变
3.python下划线
-
以单下划线开头,表示这是一个保护成员,只有类对象和子类对象自己能访问到这些变量。以单下划线开头的变量和函数被默认当作是内部函数,使用from module improt *时不会被获取,但是使用import module可以获取
-
以单下划线结尾仅仅是为了区别该名称与关键词
-
双下划线开头,表示为私有成员,只允许类本身访问,子类也不行。在文本上被替换为_class__method
-
双下划线开头,双下划线结尾。一种约定,Python内部的名字,用来区别其他用户自定义的命名,以防冲突。是一些 Python 的“魔术”对象,表示这是一个特殊成员,例如:定义类的时候,若是添加__init__方法,那么在创建类的实例的时候,实例会自动调用这个方法,一般用来对实例的属性进行初使化,Python不建议将自己命名的方法写为这种形式。
4.方法
Python其实有3个方法,即静态方法(staticmethod),类方法(classmethod)和实例方法,如下:
class A(object):
'''
实例方法:对于实例方法,我们知道在类里每次定义方法的时候都需要绑定这个实例,就是`foo(self, x)`,为什么 要这么做呢?因为实例方法的调用离不开实例,我们需要把实例自己传给函数,调用的时候是这样的`a.foo(x)`(其实 是`foo(a, x)`).
'''
def foo(self,x):
print "executing foo(%s,%s)"%(self,x)
'''
类方法一样,只不过它传递的是类而不是实例,`A.class_foo(x)`.注意这里的self和cls可以替换别的参数,但是 python的约定是这俩,还是不要改的好.
'''
@classmethod
def class_foo(cls,x):
print "executing class_foo(%s,%s)"%(cls,x)
'''
对于静态方法其实和普通的方法一样,不需要对谁进行绑定,唯一的区别是调用的时候需要使用`a.static_foo(x)` 或者`A.static_foo(x)`来调用.
'''
@staticmethod
def static_foo(x):
print "executing static_foo(%s)"%x
a=A()
5.为什么python不支持函数重载
函数重载主要是为了解决两个问题。
1。可变参数类型。
2。可变参数个数。
对于情况 1 ,函数功能相同,但是参数类型不同,python 如何处理?答案是根本不需要处理,因为 python 可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在 python 中很可能是相同的代码,没有必要做成两个不同函数。
那么对于情况 2 ,函数功能相同,但参数个数不同,python 如何处理?大家知道,答案就是缺省参数。对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的。
6.函数参数传递
对于不可变参数,例如数字,字符串,元组,类似于值传值,函数内部产生一个副本,函数内对参数的操作,不会影响到函数外的参数。
对于可变参数,例如列表,集合,字典,类似于引用传值,在函数内对参数的操作,在函数外部也会显现出来,但是这种操作不包括重新创建一个对象的修改,这样内存内置就会发生变化。
7.*args
and **kwargs
表示可变长度的参数,其中*args
表示位置参数,**kwargs
表示关键字参数。
args
因为 *
前缀 的存在, 会把位置参数收集到一个tuple
元組中。
kwargs
因为 **
前缀 的存在, 会把位置参数收集到一个dict
字典中
当然 args
和 kwargs
可以为空
8.闭包
闭包又被称为闭合函数,其特征是:一个函数(外函数)嵌套了另外一个函数(内函数),外函数返回了内函数的引用,而内函数使用了外函数的临时变量。
当外函数执行时,会将外函数的临时变量存储下来供内函数使用,需要注意是,多次调用闭包函数,其内函数使用的都是一套外函数的临时变量。可以使用闭包的 closure 属性,来查看该临时变量的地址。
def nth_power(exponent):
def exponent_of(base):
return base ** exponent
return exponent_of
square = nth_power(2)
#查看 __closure__ 的值
print(square.__closure__)
************************************
(<cell at 0x0000014454DFA948: int object at 0x00000000513CC6D0>,)
为什么要使用闭包函数?
闭包函数的优势主要在多次调用上,一般情况下,在确定具有某些功能的函数的时候,都需要先执行一些额外的工作,如果多次执行这个函数,那么这些额外的操作会执行多次,而使用闭包函数,就可以将这些额外操作放在外函数内,这样闭包函数的多次调用,便会直接去提取外函数的这些变量。避免了不必要的开销,提高了程序的运行效率。此外,表达也会更加简洁。
9.装饰器
装饰器基于闭包,本质上是一个函数,其可以让函数在不做任何代码改动的情况下,增加其额外的功能。
def test(func):
def wrapper():
print("ok")
return func()
return wrapper
这是一个最简单的装饰器的例子, print("ok")是原本的功能,func()是拓展额外的功能。从这也可以看出,他的基本结构就是一个闭包。
@test
def func():
print("yes")
func()
***************************
ok
yes
@符号是装饰器的语法糖,等同于func=test(func),其避免了额外的赋值操作。
从以上的例子不难看出,装饰器提高了程序的可重复利用性,并增加了程序的可读性。
-
带参数的装饰器
def nth_power(exponent): def exponent_of(base): return base ** exponent return exponent_of
-
类装饰器
相比于函数装饰器,类装饰器灵活度更大,封装性更强。使用类装饰器可以依靠内部的__call__方法
class test(object): def __init__(self,func): self._func=func def __call__(self, *args, **kwargs): print("ok") self._func() print("yes") @test def foo(): print("good") foo()
-
functools.wrap
装饰器有个问题就是func的函数的元信息会被wrapper取代,这些元信息包括__name__,docstring等
这个时候可以使用functools.wrap,wraps本身也是一个装饰器,其可以使得func的函数的元信息不会被wrapper取代。
from functools import wraps def test(func): @wraps(func) def wrapper(): print("ok") return func() return wrapper @test def func(): print("yes") print(func.__name__) #加 @wraps返回func,不加 @wraps返回wrapper
10.read,readline和readlines
- read 读取整个文件
- readline 读取下一行,使用生成器方法
- readlines 读取整个文件到一个迭代器以供我们遍历
11.内存管理
python的内存管理机制主要类似于金字塔模型
- -1,-2层主要有操作系统进行操作;
- 第0层是C中的malloc,free等内存分配和释放函数进行操作;
- 第1层和第2层是内存池,有Python的接口函数PyMem_Malloc函数实现,当对象小于256K时有该层直接分配内存;
- 第3层是最上层,也就是我们对Python对象的直接操作;
对第二层的python的内存池机制做一些说明:
由于频繁创建大量消耗小内存的对象时,调用new/malloc会导致大量的内存碎片,致使效率降低,因此第1层和第2层的内存池,主要是为了处理小块内存的申请和释放,操作原理是先预先在内存中申请一定数量的,大小相等的内存块留作备用,当有新的内存需求时,就先从内存池中分配内存给这个需求,不够了之后再使用第0层的malloc申请256kb的内存,这样做最显著的优势就是能够减少内存碎片,提升效率。
释放内存时,当一个对象的引用计数变为 0 时,python就会调用它的析构函数。调用析构函数并不意味着最终一定会调用free 释放内存空间,如果真是这样的话,那频繁地申请、释放内存空间会使 Python的执行效率大打折扣。因此在析构时也采用了内存池机制,从内存池申请到的内存会被归还到内存池中,以避免频繁地 释放 动作。
12.异常处理
-
try…except...else
s = 'Hello girl!' try: print( s[100]) except: print('error') print ('continue') ************************ ''' try-except语句按照如下方式工作: 1)执行try子句(在关键字try和except之间的语句) 2)如果没有异常发生,忽略except子句,try子句执行后结束 3)如果在执行try子句的过程中发生了异常,那么try子句余下的部分将被忽略;如果异常的类型和excet之后的名称相符,那么对应的except子句将被执行;最后执行try语句之后的代码 4)如果一个异常没有与任何except匹配,那么这个异常将会传递给上层的try语句中 一个try语句可以包含多个except子句,分别用来处理特定的异常,但是最多只有一个except子句分支会被执行 一个except子句可以同时处理多个异常,这些异常将被放在圆括号里以元组形式出现 except关键字后面甚至可以不加名称(当通配符使用) 另外,try-except语句还有一个可选的else子句,这个子句必须写在except子句之后,当且仅当try子句没有任何异常发生时执行(Phthonic的写法) '''
-
try…except...finally
简而言之,finally语句就是用来做异常处理的扫尾工作的
不管try子句中到底有没有异常,finally子句都会执行
如果一个异常在try子句中(或者except和else子句中)被抛出,而没有任何的except把它截住,那么这个异常就会在fianly子句执行后再次被抛出
13.多线程、多进程
-
多线程和多进程关系
''' ”进程是资源分配的最小单位,线程是CPU调度的最小单位“ 做个简单的比喻:进程=火车,线程=车厢 1.线程在进程下行进(单纯的车厢无法运行) 2.一个进程可以包含多个线程(一辆火车可以有多个车厢) 3.不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘) 4.同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易) 5.进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源) 6.进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢) 7.进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上) 8.进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁"进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量” '''
14.GIL
-
背景:
1.GIL是全局解释器锁,用来保证数据安全。
2.cpu在同一时间只能执行一个线程。(单核cpu的多线程其实是单线程的并发,并行是同一时刻干多个事情,并发是相同的时间间隔干多个事情)
-
GIL的执行过程
在python中,GIL的执行过程是:1.线程获取GIL。2.执行代码直至sleep或者python虚拟机将其挂起。3.释放GIL
从这里看出,某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行,因此python的多线程其实是单线程
-
python多线程的效率如何呢?
判断效率前,首先需要明确GIL锁的释放方法:当前线程遇见IO操作或者ticks计数达到100时,自动释放GIL。(在python3中改为了计时器,达到一定的时间来释放)
- 对于计算密集型任务来说,ticks计数很容易达到100,这样很容易出发GIL的释放于再竞争,容易造成资源浪费。
- 对于IO密集型任务来说,IO操作会进行IO等待。此时,开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序执行效率
- 需要强调的是,多核多线程比单核单线程更差,因为涉及了核与核之间线程的竞争,容易造成线程颠簸。
-
综上所述,在多核任务下,每个进程都有一个GIL锁,因此多进程类似于多线程,多进程的执行效率更高。
参考:https://zhuanlan.zhihu.com/p/20953544
15.内置数据类型、可变数据类型、不可变数据类型
-
内置数据类型
数字,字符串,列表,字典,元组,集合
-
可变数据类型
列表,集合,字典
-
不可变数据类型
数字,字符串,元组
16.python的特点
python是面向对象的解释型的脚本语言,主要特点如下
- 简单好学(近乎伪代码)
- 开源
- 可扩展性(第三方库很多)
- 可移植性
- 解释型(编译型语言:c++/c需要编译器编译,python之间利用解释器,将源代码转换成机器语言)
- 高级语言(自动管理内存,封装了很多实现细节)
- 功能强大
17.python解释器的种类与特点
-
CPython
c语言开发的 使用最广的解释器
-
IPython
- 基于cpython之上的一个交互式计时器 交互方式增强 功能和cpython一样
-
JPython
- 运行在Java上的解释器 直接把python代码编译成Java字节码执行