重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
在Python语言中,可以在函数中定义函数。 这种在函数中嵌套定义的函数也叫内部函数。我们来看下面的代码:
成都创新互联公司是专业的永新网站建设公司,永新接单;提供成都网站制作、成都网站建设,网页设计,网站设计,建网站,PHP网站建设等专业做网站服务;采用PHP框架,可快速的进行永新网站开发网页制作和功能扩展;专业做搜索引擎喜爱的网站,专业的做网站团队,希望更多企业前来合作!
上述代码中,定义了函数greet,在函数greet内部又定义了一个函数inner_func, 并调用该函数打印了一串字符。
我们可以看到,内部函数inner_func的定义和使用与普通函数基本相同。需要注意的是变量的作用域,在上述代码中,函数参数name对于全局函数greet是局部变量,对内部函数inner_func来说则是非局部变量。内部函数对于非局部变量的访问规则类似于标准的外部函数访问全局变量。
从这个例子我们还可以看到内部函数的一个作用,就是通过定义内部函数的方式将一些功能隐藏起来,防止外部直接调用。常见的场景是,在一个复杂逻辑的函数中,将一些小的任务定义成内部函数,然后由这个外层函数使用,这样可以使代码更为清晰,易于维护。这些内部函数只会在这个外层函数中使用,不能被其他函数或模块使用。
在Python语言中, 函数也是对象,它可以被创建、赋值给变量,或者作为函数的返回值。我们来看下面这个例子。
在上述代码中,在函数gen_greet内部定义了inner_func函数,并返回了一个inner_func函数对象。外部函数gen_greet返回了一个函数对象,所以像gen_greet这样的函数也叫工厂函数。
在内部函数inner_func中,使用了外部函数的传参greet_words(非局部变量),以及函数的参数name(局部变量),来打印一个字符串。
接下来,调用gen_greet("Hello")创建一个函数对象say_hello,紧接着调用say_hello("Mr. Zhang"),输出的结果为:Hello, Mr. Zhang!
同样的,调用gen_greet("Hi")创建一个函数对象say_hi,调用say_hello("Mr. Zhang"),输出的结果为:Hi,Tony!
我们可以发现,gen_greet返回的函数对象具有记忆功能,它能够把所需使用的非局部变量保存下来,用于后续被调用的时候使用。这种保存了非局部变量的函数对象被称作闭包(closure)。
那么闭包是如何实现的呢?其实并不复杂,函数对象中有一个属性__closure__,它就是在创建函数对象时用来保存这些非局部变量的。
__closure__属性是一个元组或者None类型。在上述代码中,我们可以通过下面方式查看:
函数的嵌套所实现的功能大都可以通过定义类的方式来实现,而且类是更加面向对象的代码编写方式。
嵌套函数的一个主要用途是实现函数的装饰器。我们看下面的代码:
在上述代码中,logger函数返回函数with_logging,with_logging则是打印了函数func的名称及传入的参数,然后调用func, 并将参数传递给func。其中的@wraps(func)语句用于复制函数func的名称、注释文档、参数列表等等,使得with_logging函数具有被装饰的函数func相同的属性。
代码中接下来用@logger对函数power_func进行修饰,它的作用等同于下面的代码:
可见,装饰器@符其实就是上述代码的精简写法。
通过了解了嵌套函数和闭包的工作原理,我们在使用过程中就能够更加得心应手了。
循环是一种常用的程序控制结构。我们常说,机器相比人类的最大优点之一,就是机器可以不眠不休的重复做某件事情,但人却不行。而“循环”,则是实现让机器不断重复工作的关键概念。
在循环语法方面,Python 表现的即传统又不传统。它虽然抛弃了常见的 for(init;condition;incrment) 三段式结构,但还是选择了 for 和 while 这两个经典的关键字来表达循环。绝大多数情况下,我们的循环需求都可以用 forin来满足, while相比之下用的则更少些。
虽然循环的语法很简单,但是要写好它确并不容易。在这篇文章里,我们将探讨什么是“地道”的循环代码,以及如何编写它们。
什么是“地道”的循环?
“地道”这个词,通常被用来形容某人做某件事情时,非常符合当地传统,做的非常好。打个比方,你去参加一个朋友聚会,同桌的有一位广东人,对方一开口,句句都是标准京腔、完美儿化音。那你可以对她说:“您的北京话说的真地道”。
既然“地道”这个词形容的经常是口音、做菜的口味这类实实在在的东西,那“地道”的循环代码又是什么意思呢?让我拿一个经典的例子来解释一下。
如果你去问一位刚学习 Python 一个月的人:“如何在遍历一个列表的同时获取当前下标?”。他可能会交出这样的代码:

上面的循环虽然没错,但它确一点都不“地道”。一个拥有三年 Python 开发经验的人会说,代码应该这么写:
enumerate() 是 Python 的一个内置函数,它接收一个“可迭代”对象作为参数,然后返回一个不断生成 (当前下标,当前元素) 的新可迭代对象。这个场景使用它最适合不过。
所以,在上面的例子里,我们会认为第二段循环代码比第一段更“地道”。因为它用更直观的代码,更聪明的完成了工作。
enumerate() 所代表的编程思路
不过,判断某段循环代码是否地道,并不仅仅是以知道或不知道某个内置方法作为标准。我们可以从上面的例子挖掘出更深层的东西。
如你所见,Python 的 for 循环只有 forin这一种结构,而结构里的前半部分 - 赋值给 item- 没有太多花样可玩。所以后半部分的 可迭代对象 是我们唯一能够大做文章的东西。而以 enumerate() 函数为代表的“修饰函数”,刚好提供了一种思路:通过修饰可迭代对象来优化循环本身。
这就引出了我的第一个建议。
建议1:使用函数修饰被迭代对象来优化循环
使用修饰函数处理可迭代对象,可以在各种方面影响循环代码。而要找到合适的例子来演示这个方法,并不用去太远,内置模块 itertools 就是一个绝佳的例子。
简单来说,itertools 是一个包含很多面向可迭代对象的工具函数集。我在之前的系列文章《容器的门道》里提到过它。
如果要学习 itertools,那么 Python 官方文档 是你的首选,里面有非常详细的模块相关资料。但在这篇文章里,侧重点将和官方文档稍有不同。我会通过一些常见的代码场景,来详细解释它是如何改善循环代码的。
1. 使用 product 扁平化多层嵌套循环
虽然我们都知道“扁平的代码比嵌套的好”。但有时针对某类需求,似乎一定得写多层嵌套循环才行。比如下面这段:

对于这种需要嵌套遍历多个对象的多层循环代码,我们可以使用 product() 函数来优化它。product() 可以接收多个可迭代对象,然后根据它们的笛卡尔积不断生成结果。

相比之前的代码,使用 product() 的函数只用了一层 for 循环就完成了任务,代码变得更精炼了。
2. 使用 islice 实现循环内隔行处理
有一份包含 Reddit 帖子标题的外部数据文件,里面的内容格式是这样的:

可能是为了美观,在这份文件里的每两个标题之间,都有一个 "---" 分隔符。现在,我们需要获取文件里所有的标题列表,所以在遍历文件内容的过程中,必须跳过这些无意义的分隔符。
参考之前对 enumerate() 函数的了解,我们可以通过在循环内加一段基于当前循环序号的 if 判断来做到这一点:

但对于这类在循环内进行隔行处理的需求来说,如果使用 itertools 里的 islice() 函数修饰被循环对象,可以让循环体代码变得更简单直接。
islice(seq,start,end,step) 函数和数组切片操作( list[start:stop:step] )有着几乎一模一样的参数。如果需要在循环内部进行隔行处理的话,只要设置第三个递进步长参数 step 值为 2 即可(默认为 1)。

3. 使用 takewhile 替代 break 语句
有时,我们需要在每次循环开始时,判断循环是否需要提前结束。比如下面这样:

对于这类需要提前中断的循环,我们可以使用 takewhile() 函数来简化它。takewhile(predicate,iterable)会在迭代 iterable 的过程中不断使用当前对象作为参数调用 predicate 函数并测试返回结果,如果函数返回值为真,则生成当前对象,循环继续。否则立即中断当前循环。
使用 takewhile 的代码样例:

itertools 里面还有一些其他有意思的工具函数,他们都可以用来和循环搭配使用,比如使用 chain 函数扁平化双层嵌套循环、使用 zip_longest 函数一次同时循环多个对象等等。
篇幅有限,我在这里不再一一介绍。如果有兴趣,可以自行去官方文档详细了解。
4. 使用生成器编写自己的修饰函数
除了 itertools 提供的那些函数外,我们还可以非常方便的使用生成器来定义自己的循环修饰函数。
让我们拿一个简单的函数举例:

在上面的函数里,循环体内为了过滤掉所有奇数,引入了一条额外的 if 判断语句。如果要简化循环体内容,我们可以定义一个生成器函数来专门进行偶数过滤:

将 numbers 变量使用 even_only 函数装饰后, sum_even_only_v2 函数内部便不用继续关注“偶数过滤”逻辑了,只需要简单完成求和即可。
Hint:当然,上面的这个函数其实并不实用。在现实世界里,这种简单需求最适合直接用生成器/列表表达式搞定:sum(numfornuminnumbersifnum%2==0)
建议2:按职责拆解循环体内复杂代码块
我一直觉得循环是一个比较神奇的东西,每当你写下一个新的循环代码块,就好像开辟了一片黑魔法阵,阵内的所有内容都会开始无休止的重复执行。
但我同时发现,这片黑魔法阵除了能带来好处,它还会引诱你不断往阵内塞入越来越多的代码,包括过滤掉无效元素、预处理数据、打印日志等等。甚至一些原本不属于同一抽象的内容,也会被塞入到同一片黑魔法阵内。
python函数修饰符@ 修饰符 ‘@’符号用作函数修饰符是python2.4新增加的功能,修饰符必须出现在函数定义前一行,不允许和函数定义在同一行。也就是说@A def f(): 是非法的。 只可以在模块或类定义层内对函数进行修饰,不允许修修饰一个类。一个修饰符就是一个函数,它将被修饰的函数做为参数,并返回修饰后的同名函数或其它可调用的东西。 本质上讲,装饰符@类似于 回调函数 ,把其它的函数(暂且称为目的参数,后面紧接着的函数)作为自己的入参,在目的函数执行前,执行一些自己的操作, 比如:计数、打印一些提示信息等,然后返回目的函数。下面列举一个简单的例子。
创建函数修饰符的规则:
(1)修饰符是一个函数
(2)修饰符取被修饰函数为参数
(3)修饰符返回一个新函数
(4)修饰符维护被维护函数的签名
例子1: 被修饰函数不带参数
运行结果:
例子2: 使用functools模块提供的修改函数属性的方法wraps
运行结果:
可见test1的函数名称变了,如果某些代码用到就会出问题,可以使用functools模块提供的修改函数属性的方法wraps
运行结果:
例子3: 被修饰函数带参数
运行结果:
例子4: 修饰符带参数 ,需要比上面例子多一层包装
运行结果:
#Python
2.5
#这个可以用修饰器来完成
#但是一般不会限制参数类型
#给你个思路:
def
argfilter(*types):
def
deco(func):
#这是修饰器
def
newfunc(*args):
#新的函数
if
len(types)==len(args):
correct
=
True
for
i
in
range(len(args)):
if
not
isinstance(args[i],
types[i]):
#判断类型
correct
=
False
if
correct:
return
func(*args)
#返回原函数值
else:
raise
TypeError
else:
raise
TypeError
return
newfunc
#由修饰器返回新的函数
return
deco
#返回作为修饰器的函数
@argfilter(int,
str)
#指定参数类型
def
func(i,
s):
#定义被修饰的函数
i,
s
#之后你想限制类型的话,
就这样:
#@argfilter(第一个参数的类名,
第二个参数的类名,
...,
第N个参数的类名)
#def
yourfunc(第一个参数,
第一个参数,
...,
第N个参数):
#
...
#
#相当于:
#def
yourfunc(第一个参数,
第一个参数,
...,
第N个参数):
#
...
#yourfunc
=
argfilter(第一个参数的类名,
第二个参数的类名,
...,
第N个参数的类名)(yourfunc)