Python 语言高阶

1. Python - with语句

在实际的编码过程中,有时有一些任务,需要事先做一些设置,事后做一些清理,这时就需要Pythonwith出场了,with能够对这样的需求进行一个比较优雅的处理,最常用的例子就是对访问文件的处理。
with语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要"清理"操作,释放资源。比如文件使用后的自动关闭,线程中的自动获取和释放等。

  • 上下文管理协议(Context Management Protocol):包含方法 __enter__()__exit__(),支持该协议的对象要实现这两个方法。
  • 上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了__enter__()__exit__()方法。上下文管理器定义执行with语句时要建立的运行时上下文,负责执行with语句块上下文中的进入与退出操作。通常使用with语句调用上下文管理器,也可以通过直接调用其方法来使用。

一段基本的with表达式,其结构是这样的:

with EXPR as VAR:
    BLOCK

其中EXPR可以是任意表达式;as VAR是可选的。其一般的执行过程是这样的:
(1). 执行EXPR,生成上下文管理器context_manager
(2). 获取上下文管理器的__exit()__方法,并保存起来用于之后的调用;
(3). 调用上下文管理器的__enter__()方法;如果使用了as子句,则将__enter__()方法的返回值赋值给as子句中的VAR
(4). 执行BLOCK中的表达式;
(5). 不管是否执行过程中是否发生了异常,执行上下文管理器的__exit__()方法,__exit__()方法负责执行“清理”工作,如释放资源等。如果执行过程中没有出现异常,或者语句体中执行了语句break/continue/return,则以None作为参数调用__exit__(None, None, None);如果执行过程中出现异常,则使用sys.exc_info得到的异常信息为参数调用__exit__(exc_type, exc_value, exc_traceback)
(6). 出现异常时,如果__exit__(type, value, traceback)返回False,则会重新抛出异常,让with之外的语句逻辑来处理异常,这也是通用做法;如果返回True,则忽略异常,不再对异常进行处理。

自定义上下文管理器

自定义的上下文管理器要实现上下文管理协议所需要的__enter__()__exit__()两个方法:

#!/usr/bin/env python

class DBManager(object):
    def __init__(self):
        pass

    def __enter__(self):
        print('__enter__')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('__exit__')
        return True

def getInstance():
        return DBManager()

with getInstance() as dbManagerIns:
    print('with demo')

代码运行结果如下:

__enter__
with demo
__exit__

参考:Python中with用法详解

2. Python - 迭代器

迭代器指的是迭代取值的工具,迭代是指一个重复的过程,每一次重复都是基于上一次结果而来.
迭代提供了一种通用的不依赖索引的迭代取值方式.

一. 可迭代对象
    但凡内置有__iter__方法的对象,都称为可迭代对象,可迭代的对象:str,list,tuple,dict,set,文件对象

二. 迭代器对象
    1. 既内置有__next__方法的对象,执行该方法可以不依赖索引取值
    2. 又内置有__iter__方法的对象,执行迭代器的__iter__方法得到的依然是迭代器本身
迭代器一定是可迭代对象,可迭代对象不一定是迭代器对象,文件对象本身就是一个迭代器对象.

dic = {'x':1,'y':2,'z':3}
iter_dic = dic.__iter__()
print(iter_dic.__next__())
print(iter_dic.__next__())
print(iter_dic.__next__())
print(iter_dic.__next__())

结果:

x
y
z
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

for循环本质为迭代器循环
工作原理:

  1. 先调用in后对象的__iter__方法,将其变成一个迭代器对象
  2. 调用next(迭代器),将得到的返回值赋值给变量名
  3. 循环往复直到next(迭代器)抛出异常,for会自动捕捉异常然后结束循环
    PS:可以从for的角度,分辨但凡可以被for循环取值的对象就是可迭代对象

迭代器优点:

  1. 提供了一种通用不依赖索引的迭代取值方式
  2. 同一时刻在内存中只存在一个值,更节省内存

迭代器缺点:

  1. 取值不如按照索引的方式灵活,不能取指定的某一个值,只能往后取,不能往前去
  2. 无法预测迭代器的长度

3. Python - 生成器

生成器就是一种自定义的迭代器,本质为迭代器
但凡函数内包含yield关键字,调用函数不会执行函数体代码,会得到一个返回值,该返回值就是生成器对象

例如:

>>> def func():
...          print('first')
...          yield 1
...          print('second')
...          yield 2
...          print('third')
...
>>> g=func() #调用函数不会执行函数体代码,会得到一个生成器对象
>>> res1=next(g)
first
>>> print(res1)
1
>>> res2=next(g)
second
>>> print(res2)
2
>>>

总结yield:只能在函数内使用
    1. yield提供了一种自定义迭代器的解决方案
    2. yield可以保存函数的暂停的状态
    3. yield对比return:
相同点:都可以返回值,值得类型与个数没有限制
不同点:yield可以返回多次值,而return只能返回一次值函数就会结束
** 生成器表达式

>>> g = (i**2 for i in range(1,6) if i > 3)
>>> print(g)
<generator object <genexpr> at 0x0000022C27C911B0>
>>> print(next(g))
16
>>> print(next(g))
25
>>>

两种创建生成器的方式(生成器表达式和yield关键字)

迭代器与生成器的区别:
(1). 生成器
生成器本质上就是一个函数,它记住了上一次返回时在函数体中的位置。
对生成器函数的第二次(或第n次)调用,跳转到函数上一次挂起的位置。
而且记录了程序执行的上下文。
生成器不仅“记住”了它的数据状态,生成还记住了程序执行的位置。
(2). 迭代器
迭代器是一种支持next()操作的对象。它包含了一组元素,当执行next()操作时,返回其中一个元素。
当所有元素都被返回后,再执行next()报异常—StopIteration
生成器一定是可迭代的,也一定是迭代器对象
(2). 区别:
①生成器是生成元素的,迭代器是访问集合元素的一中方式
②迭代输出生成器的内容
③迭代器是一种支持next()操作的对象
④迭代器(iterator):其中iterator对象表示的是一个数据流,可以把它看做一个有序序列,但我们不能提前知道序列的长度,只有通过nex()函数实现需要计算的下一个数据。可以看做生成器的一个子集。

一个生成器对象一定是迭代器对象,是可迭代的。但是一个迭代器对象,不一定是生成器对象。生成器与可迭代器是两个不同的对象

参考:
迭代器与生成器的区别
Python迭代器

4. Python - lambda函数

Python中,lambda的语法是唯一的。其形式如下:

lambda argument_list: expression

  • 这里的argument_list是参数列表,它的结构与Python中函数(function)的参数列表是一样的。

  • 这里的expression是一个关于参数的表达式。表达式中出现的参数需要在argument_list中有定义,并且表达式只能是单行的

这里的lambda argument_list: expression表示的是一个函数。这个函数叫做lambda函数。

三个特性

lambda函数有如下特性:

  • lambda函数是匿名的:所谓匿名函数,通俗地说就是没有名字的函数。lambda函数没有名字。

  • lambda函数有输入和输出:输入是传入到参数列表argument_list的值,输出是根据表达式expression计算得到的值。

  • lambda函数一般功能简单:单行expression决定了lambda函数不可能完成复杂的逻辑,只能完成非常简单的功能。由于其实现的功能一目了然,甚至不需要专门的名字来说明。

三、用法

  1. lambda函数赋值给一个变量,通过这个变量间接调用该lambda函数。

例如,add=lambda x, y: x+y

add(1,2),输出为3

  1. lambda函数赋值给其他函数,从而将其他函数用该lambda函数替换。

例如,为了把标准库time中的函数sleep的功能屏蔽(Mock),我们可以在程序初始化时调用:time.sleep=lambda x:None

这样,在后续代码中调用time库的sleep函数将不会执行原有的功能。

例如,执行time.sleep(3)时,程序不会休眠3秒钟,而是什么都不做。

  1. lambda函数作为其他函数的返回值,返回给调用者。

函数的返回值也可以是函数。

例如,return lambda x, y: x+y  返回:加法函数。

这时,lambda函数实际上是定义在某个函数内部的函数,称之为嵌套函数,或者内部函数。

对应的,将包含嵌套函数的函数称之为外部函数

内部函数能够访问外部函数的局部变量。

  1. lambda函数作为参数传递给其他函数。

部分Python内置函数接收函数作为参数。典型的此类内置函数有这些。

  • filter函数

此时lambda函数用于指定过滤列表元素的条件。

例如filter(lambda x: x % 3 == 0, [1, 2, 3])指定将列表[1,2,3]中能够被3整除的元素过滤出来,其结果是[3]。

  • sorted函数

此时lambda函数用于指定对列表中所有元素进行排序的准则。

例如sorted([1, 2, 3, 4, 5, 6, 7, 8, 9], key=lambda x: abs(5-x))

将列表[1, 2, 3, 4, 5, 6, 7, 8, 9]按照元素与5距离从小到大进行排序,其结果是[5, 4, 6, 3, 7, 2, 8, 1, 9]。

  • map函数

此时lambda函数用于指定对列表中每一个元素的共同操作。

例如map(lambda x: x+1, [1, 2,3])将列表[1, 2, 3]中的元素分别加1,其结果[2, 3, 4]。

  • reduce函数

此时lambda函数用于指定列表中两两相邻元素的结合条件。

例如reduce(lambda a, b: '{}, {}'.format(a, b), [1, 2, 3, 4, 5, 6, 7, 8, 9])将列表 [1, 2, 3, 4, 5, 6, 7, 8, 9]中的元素从左往右两两以逗号分隔的字符的形式依次结合起来,其结果是'1, 2, 3, 4, 5, 6, 7, 8, 9'。

另外,部分Python库函数也接收函数作为参数,例如geventspawn函数。此时,lambda函数也能够作为参数传入。
参考:
python lambda表达式
python lambda表达式简单用法

5. Python - 递归函数

在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。

举个例子,我们来计算阶乘n! = 1 x 2 x 3 x ... x n,用函数fact(n)表示,可以看出:

fact(n) = n! = 1 x 2 x 3 x ... x (n-1) x n = (n-1)! x n = fact(n-1) x n
所以,fact(n)可以表示为n x fact(n-1),只有n=1时需要特殊处理。

于是,fact(n)用递归的方式写出来就是:

def fact(n):
    if n==1:
        return 1
    return n * fact(n - 1)

上面就是一个递归函数。可以试试:

>>> fact(1)
1
>>> fact(5)
120
>>> fact(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

如果我们计算fact(5),可以根据函数定义看到计算过程如下:

===> fact(5)
===> 5 * fact(4)
===> 5 * (4 * fact(3))
===> 5 * (4 * (3 * fact(2)))
===> 5 * (4 * (3 * (2 * fact(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120

递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。

使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。可以试试fact(1000)

>>> fact(1000)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in fact
  ...
  File "<stdin>", line 4, in fact
RuntimeError: maximum recursion depth exceeded in comparison

解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。

尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

上面的fact(n)函数由于return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中:

def fact(n):
    return fact_iter(n, 1)

def fact_iter(num, product):
    if num == 1:
        return product
    return fact_iter(num - 1, num * product)

可以看到,return fact_iter(num - 1, num * product)仅返回递归函数本身,num - 1num * product在函数调用前就会被计算,不影响函数调用。

fact(5)对应的fact_iter(5, 1)的调用如下:

===> fact_iter(5, 1)
===> fact_iter(4, 5)
===> fact_iter(3, 20)
===> fact_iter(2, 60)
===> fact_iter(1, 120)
===> 120

尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。

遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出

小结

  • 使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。

  • 针对尾递归优化的语言可以通过尾递归防止栈溢出。尾递归事实上和循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。

  • Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。

参考:
python递归函数

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容