一、Python函数剖析

1、函数的调用顺序

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian #函数错误的调用方式
def func(): #定义函数func()
print("in the func")
foo() #调用函数foo()
func() #执行函数func()
def foo(): #定义函数foo()
print("in the foo") ###########打印输出########### #报错:函数foo没有定义
#NameError: name 'foo' is not defined #函数正确的调用方式
def func(): #定义函数func()
print("in the func")
foo() #调用函数foo()
def foo(): #定义函数foo()
print("in the foo")
func() #执行函数func() ###########打印输出###########
#in the func
#in the foo

总结:被调用函数要在执行之前被定义 

2、高阶函数

满足下列条件之一就可成函数为高阶函数

  • 某一函数当做参数传入另一个函数中

  • 函数的返回值包含一个或多个函数

刚才调用顺序中的函数稍作修改就是一个高阶函数

#高阶函数
def func(): #定义函数func()
print("in the func")
return foo() #调用函数foo()
def foo(): #定义函数foo()
print("in the foo")
return 100 res = func() #执行函数func()
print(res) #打印函数返回值 ###########打印输出###########
#in the func
#in the foo
#100

从上面的程序得知函数func的返回值为函数foo的返回值,如果foo不定义返回值的话,func的返回值默认为None;

下面我来看看更复杂的高阶函数:

#更复杂的高阶函数
import time #调用模块time
def bar():
time.sleep(1)
print("in the bar")
def foo(func):
start_time=time.time()
func()
end_time=time.time()
print("func runing time is %s"%(end_time-start_time)) foo(bar)
###########打印输出###########
#in the bar
#func runing time is 1.0000572204589844

其实上面这段代码已经实现了装饰器一些功能,即在不修改bar()代码的情况下,给bar()添加了功能;但是改变了bar()调用方式

下面我们对上面的code进行下修改,不改变bar()调用方式的情况下进行功能添加

#更复杂的高阶函数,不改变调用方式
import time #调用模块time
def bar():
time.sleep(1)
print("in the bar")
def foo(func):
start_time=time.time()
print("in the foo")
return func #返回bar函数的内存地址
end_time=time.time()
print("func runing time is %s"%(end_time-start_time)) bar = foo(bar) #bar重新赋值
bar()
###########打印输出###########
#in the foo
#in the bar

我们没有对bar()源码进行过修改,也没有改变bar()的调用方式,当执行bar()函数时,多加了一些功能,装饰器的一些雏形已经呈现;但是我们又发现之前添加的计算bar()执行时间的功能没有打印出来,return执行后函数就结束了。

3、内嵌函数和作用域

定义:在一个函数体内创建另外一个函数,这种函数就叫内嵌函数(基于python支持静态嵌套域)

#内嵌函数示例
def foo():
print("in the foo")
def bar():
print("in the bar")
bar() foo()
###########打印输出###########
#in the foo
#in the bar

嵌套函数有什么用呢?我们暂时先记住这个内容

局部作用域和全局作用域的访问顺序

#嵌套函数变量与全部变量
x = 0
def grandpa():
x=1
def dad():
x=2
def son():
x=3
print(x)
son()
dad() grandpa()
print(x)
###########打印输出###########
# 3
# 0 

注:内嵌函数中定义的函数在全局中是无法直接执行的

4、装饰器

定义:本质是函数(装饰其他函数),为其他函数添加附加功能的。

遵循原则:①不能修改被装饰函数的源代码

     ②不能修改被装饰函数的调用方式

组成:装饰器由高阶函数+内嵌函数组成

之前说了那么多其实都是了给装饰器做铺垫,回到刚才高阶函数中最后一个示例,能不能给函数加上运算时间计算?

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian #装饰器
import time
def timer(func):
def deco():
start_time=time.time()
func() #执行形参func()
end_time=time.time()
print("func runing time is %s"%(end_time-start_time))
return deco #返回函数deco的内存地址
def test1():
print("in the test1")
time.sleep(1) test1 = timer(test1) #重新赋值test1 此时test1=deco的内存地址
test1() #执行test1
###########打印输出###########
#in the test1
#func runing time is 1.0000572204589844

现在我们已经实现了装饰器的功能,但是如果test1有形参的话,上面的代码就会报错了,下面我们对代码做下修改

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian #装饰器
import time
def timer(func):
def deco(*args,**kwargs):
start_time=time.time()
func(*args,**kwargs) #执行形参func()
end_time=time.time()
print("func runing time is %s"%(end_time-start_time))
return deco #返回函数deco的内存地址
@timer #test1 = timer(test1) test1=deco
def test1(name):
print("in the test1 name %s"%name)
time.sleep(1) test1("lzl") #执行test1
###########打印输出###########
#in the test1
#func runing time is 1.0000572204589844

上面的代码是不是觉得很完美了,呵呵,假如test1()有return返回值怎么办?你会发现最后执行test1返回值丢失了,所以要对上面的代码再完善一下了。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian #装饰器
import time
def timer(func):
def deco(*args,**kwargs):
start_time=time.time()
res = func(*args,**kwargs) #执行形参func()
end_time=time.time()
print("func runing time is %s"%(end_time-start_time))
return res
return deco #返回函数deco的内存地址
@timer #test1 = timer(test1) test1=deco
def test1(name):
print("in the test1 name %s"%name)
time.sleep(1)
return "return form test1" print(test1("lzl")) #执行test1
###########打印输出###########
#in the test1
#func runing time is 1.0000572204589844
#return form test1

好了现在我们探讨一个问题,函数可以被多个装饰器装饰吗?!

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian #函数用多个装饰器
def w1(func):
def inner(*args, **kwargs):
print("in the w1")
return func(*args, **kwargs)
return inner
def w2(func):
def inner(*args, **kwargs):
print("in the w2")
return func(*args, **kwargs)
return inner
@w1
@w2
def f1(*args, **kwargs):
print("in the f1") f1()
###########打印输出###########
#in the w1
#in the w2
#in the f1

终极版装饰器来了......

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian #终极版装饰器
def Before(*args, **kwargs):
print("before")
def After(*args, **kwargs):
print("after") def Filter(before_func, after_func):
def outer(main_func):
def wrapper(*args, **kwargs):
before_result = before_func(*args, **kwargs)
if (before_result != None):
return before_result
main_result = main_func(*args, **kwargs)
if (main_result != None):
return main_result
after_result = after_func(*args, **kwargs)
if (after_result != None):
return after_result
return wrapper
return outer @Filter(Before, After) #Filter(Before,After)=outer Index=outer(Index)=wrapper
def Index(*args, **kwargs):
print("index") Index() #Index() = wrapper()
###########打印输出###########
#before
#index
#after

  

 5、生成器

学习生成器之前,我们先来看看什么是列表生成式

#列表生成式
b = [ i*2 for i in range(10)]
print(b) ###########打印输出###########
#[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,还需要花费很长时间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种调用时才会生成相应数据的机制,称为生成器:generator

要创建一个generator,有很多种方法,第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个生成器

#生成器
l = [ i*2 for i in range(10)]
print(l) g = (i*2 for i in range(10))
print(g) ###########打印输出###########
#[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
#<generator object <genexpr> at 0x0064AAE0>

print(g) 打印出来的信息显示g是一个生成器,创建lg的区别仅在于最外层的[]()l是一个list,而g是一个generator;我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值

#生成器next打印
print(next(g))
#......... 不断next 打印10次
#..........
print(next(g)) ###########打印输出###########
#0
#........
#18
#Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
#StopIteration

我们讲过,generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象,所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误

#生成器for调用
g = (i*2 for i in range(10)) #不用担心出现StopIteration错误
for i in g:
print(i) ###########打印输出###########
# 0
# 2
# 4
# 6
# 8
# 10
# 12
# 14
# 16
# 18

generator非常强大。如果推算的算法比较复杂,用列表生成式转换的生成器无法去实现时,我们还可以用函数来实现。比如,著名的斐波拉契数列(Fibonacci)

#函数表示斐波拉契数列
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n += 1
return 'done' fib(5)
###########打印输出###########
# 1
# 1
# 2
# 3
# 5

仔细观察,可以看出,fib函数实际上是定义了斐波那契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator;也就是说,上面的函数和generator仅一步之遥,那我们能不能把上面的函数变成一个生成器呢?

#斐波拉契数列转换为generator
def fib(max):
n, a, b = 0, 0, 1
while n < max:
#print(b)
yield b
a, b = b, a + b
n += 1
return 'done' print(type(fib(5))) #打印fib(5)的类型
for i in fib(5): #for循环去调用
print(i)
###########打印输出###########
# <class 'generator'>
# 1
# 1
# 2
# 3
# 5

要把fib函数变成generator,只需要把print(b)改为yield b就可以了,这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator

但是用for循环调用generator时,会发现拿不到generator的return语句的返回值,也就是return的值没有打印出来,现在我们来看看怎么去打印generator的返回值

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian #获取generator的返回值
def fib(max):
n, a, b = 0, 0, 1
while n < max:
#print(b)
yield b
a, b = b, a + b
n += 1
return 'done' g = fib(5)
while True:
try:
x = next(g)
print( x)
except StopIteration as e:
print(e.value)
break
###########打印输出###########
# 1
# 1
# 2
# 3
# 5
# done

如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIterationvalue中,关于如何捕获错误,后面的错误处理还会详细讲解。 

还可通过yield实现在单线程的情况下实现并发运算的效果

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian import time
def consumer(name):
print("%s 准备吃包子啦!" %name)
while True:
baozi = yield print("包子[%s]来了,被[%s]吃了!" %(baozi,name)) def producer(name):
c = consumer('A')
c2 = consumer('B')
c.__next__()        #c.__next__()等同于next(c)
c2.__next__()
print("老子开始准备做包子啦!")
for i in range(10):
time.sleep(1)
print("%s做了2个包子!"%(name))
c.send(i)
c2.send(i) producer("lzl")

6、迭代器

我们已经知道,可以直接作用于for循环的数据类型有以下2种:

  • 集合数据类型,如listtupledictsetstr等;

  • 生成器,包括generator和带yield的generator function;

定义:这些可以直接作用于for循环的对象统称为可迭代对象:Iterable  

我们可以使用isinstance()去判断一个对象是否是Iterable对象

#可迭代对象
from collections import Iterable print(isinstance([], Iterable))
# True
print(isinstance("abc", Iterable))
# True
print(isinstance((x for x in range(10)), Iterable))
# True
print(isinstance(100, Iterable))
# False

我们知道生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了

重点来了....*可以被next()函数调用并不断返回下一个值的对象称为迭代器Iterator

可以使用isinstance()判断一个对象是否是Iterator对象

#迭代器对象
from collections import Iterator print(isinstance([], Iterator))
# True
print(isinstance("abc", Iterator))
# False
print(isinstance((x for x in range(10)), Iterator))
# True
print(isinstance(100, Iterator))
# False

由上面可知,生成器都是Iterator对象,但listdictstr虽然是Iterable,却不是Iterator;把listdictstrIterable变成Iterator可以使用iter()函数

#可迭代对象转迭代器对象
print(isinstance(iter([]), Iterator))
# True
print(isinstance(iter("abc"), Iterator))
# True

你可能会问,为什么listdictstr等数据类型是Iterable但不是Iterator

这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

小结:

  • 凡是可作用于for循环的对象都是Iterable类型;
  • 凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
  • 集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象;

Python的for循环本质上就是通过不断调用next()函数实现的,例如:

for x in [1, 2, 3, 4, 5]:
pass

实际上完全等价于:

# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
try:
# 获得下一个值:
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循环
break

  

Python开发【第四章】:函数剖析的更多相关文章

  1. ASP.NET自定义控件组件开发 第四章 组合控件开发CompositeControl

    原文:ASP.NET自定义控件组件开发 第四章 组合控件开发CompositeControl 第四章 组合控件开发CompositeControl 大家好,今天我们来实现一个自定义的控件,之前我们已经 ...

  2. ASP.NET自定义控件组件开发 第四章 组合控件开发CompositeControl 后篇 --事件冒泡

    原文:ASP.NET自定义控件组件开发 第四章 组合控件开发CompositeControl 后篇 --事件冒泡 CompositeControl  后篇 --事件冒泡 系列文章链接: ASP.NET ...

  3. 进击的Python【第四章】:Python的高级应用(一)

    Python的高级应用(一) 本章内容: 内置函数 生成器 迭代器 装饰器 JSON和PICKLE的简单用法 软件目录结构规范 一.内置函数 1.数学运算类 abs(x) 求绝对值1.参数可以是整型, ...

  4. [Python笔记][第四章Python正则表达式]

    2016/1/28学习内容 第四章 Python字符串与正则表达式之正则表达式 正则表达式是字符串处理的有力工具和技术,正则表达式使用预定义的特定模式去匹配一类具有共同特征的字符串,主要用于字符串处理 ...

  5. Javascript模式(第四章函数)------读书笔记

    一 背景 js函数的两个特点:1 函数是第一类对象(first-class object):2 函数可以提供作用域 1 函数是对象: 1 函数可以在运行时动态创建,还可以在程序执行过程中创建 2 可以 ...

  6. Android系统移植与驱动开发--第四章

    第四章 源代码的下载和编译 一个android内核相当于4G,而一个Linux内个只有几百M,Linux内核相对于android内核来说实在是小巫见大巫.了解android源代码不一定要详细了解,只去 ...

  7. python 教程 第四章、 控制流

    第四章. 控制流 控制语句后面要加冒号: 1)    if语句 if guess == number: print 'Congratulations, you guessed it.' # New b ...

  8. Python开发【第二章】:Python深浅拷贝剖析

    Python深浅拷贝剖析 Python中,对象的赋值,拷贝(深/浅拷贝)之间是有差异的,如果使用的时候不注意,就可能产生意外的结果. 下面本文就通过简单的例子介绍一下这些概念之间的差别. 一.对象赋值 ...

  9. python自动化开发-[第四天]-函数

    今日概要: - 函数对象 - 函数嵌套 - 命名空间和作用域 - 闭包 - 装饰器 - 迭代器 - 生成器 - 内置函数 一.函数对象 1.函数对象的定义: 函数是第一类对象,即函数可以当作数据传递 ...

  10. Python开发【第二章】:深浅拷贝剖析

    Python深浅拷贝剖析 Python中,对象的赋值,拷贝(深/浅拷贝)之间是有差异的,如果使用的时候不注意,就可能产生意外的结果. 下面本文就通过简单的例子介绍一下这些概念之间的差别. 一.对象赋值 ...

随机推荐

  1. 【2018.07.30】(广度优先搜索算法/队列)学习BFS算法小记

    一些BFS参考的博客: https://blog.csdn.net/ldx19980108/article/details/78641127 https://blog.csdn.net/u011437 ...

  2. java把一段时间分成周,月,季度,年的时间段

    package com.mq.test.activeMQ; import java.text.DateFormat; import java.text.ParseException; import j ...

  3. Apache Flink - 作业和调度

    Scheduling: Flink中的执行资源通过任务槽(Task Slots)定义.每个TaskManager都有一个或多个任务槽,每个槽都可以运行一个并行任务管道(pipeline).管道由多个连 ...

  4. MySQL格式化时间date_format

    select date_format(deal_date, '%Y年%m月%d日 %H时%i分%s秒'), date_format(deal_date, '%Y-%m-%d %H:%i:%s') fr ...

  5. Ionic4.x 中的 UI 组件(UI Components) Slides 轮播图组件、Searchbar 组件、 Segment 组件

    Slides 轮播图组件 Ionic4.x 中的轮播图组件是基于 swiper 插件,所以配置 slides 的属性需要在 swiper 的 api 中 找 Swiper Api:http://ida ...

  6. angular组件数据

    import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-news', templateUrl: ' ...

  7. web手工项目02-注册功能输入分析,处理,输出方法-测试用例及缺陷编写-首页轮播图和购物车

    web手工项目第二天笔记 昨日回顾 搭建测试环境(WAMP,phpStudy,tpshop项目文件) 熟悉项目(四个步骤,三个来源) 项目测试流程(需求评审,测试计划与方案,测试用例设计与评审,测试执 ...

  8. ABAP Memory ID

    转自:https://blog.csdn.net/lyq123333321/article/details/52659114 (一)          Difference Between SAP a ...

  9. 常规函数模块CALL in new task 报错

    使用START NEW TASK, 函数需要是远程调用模块. 错误:FUNCTION module  ' ZMMFM0021'  cannot be used for 'remote' CALLS. ...

  10. Vue + GraphQL初试

    基本用法 GraphQL概述 GraphQL基本语法特性 GraphQL类型系统 GraphQL类型系统内置基础类型 GraphQL类型系统内置修饰符 GraphQL工作原理 GraphQL执行过程 ...