一、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开发【第四章】:Python函数剖析的更多相关文章

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

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

  2. python开发第四篇:函数(1)

    函数 阅读目录 一 函数知识体系 二 函数基础 三 函数对象.函数嵌套.名称空间与作用域.装饰器 四 迭代器.生成器.面向过程编程 五 三元表达式.列表推导式.生成器表达式.递归.匿名函数.内置函数 ...

  3. [Python学习笔记][第四章Python字符串]

    2016/1/28学习内容 第四章 Python字符串与正则表达式之字符串 编码规则 UTF-8 以1个字节表示英语字符(兼容ASCII),以3个字节表示中文及其他语言,UTF-8对全世界所有国家需要 ...

  4. Python进阶----反射(四个方法),函数vs方法(模块types 与 instance()方法校验 ),双下方法的研究

    Python进阶----反射(四个方法),函数vs方法(模块types 与 instance()方法校验 ),双下方法的研究 一丶反射 什么是反射: ​ 反射的概念是由Smith在1982年首次提出的 ...

  5. 0003.5-20180422-自动化第四章-python基础学习笔记--脚本

    0003.5-20180422-自动化第四章-python基础学习笔记--脚本 1-shopping """ v = [ {"name": " ...

  6. python学习第四讲,python基础语法之判断语句,循环语句

    目录 python学习第四讲,python基础语法之判断语句,选择语句,循环语句 一丶判断语句 if 1.if 语法 2. if else 语法 3. if 进阶 if elif else 二丶运算符 ...

  7. 【转】python 历险记(四)— python 中常用的 json 操作

    [转]python 历险记(四)— python 中常用的 json 操作 目录 引言 基础知识 什么是 JSON? JSON 的语法 JSON 对象有哪些特点? JSON 数组有哪些特点? 什么是编 ...

  8. 孤荷凌寒自学python第二十四天python类中隐藏的私有方法探秘

    孤荷凌寒自学python第二十四天python类中隐藏的私有方法探秘 (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) 今天发现了python的类中隐藏着一些特殊的私有方法. 这些私有方法不管我 ...

  9. 孤荷凌寒自学python第十四天python代码的书写规范与条件语句及判断条件式

    孤荷凌寒自学python第十四天python代码的书写规范与条件语句及判断条件式 (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) 在我学习过的所有语言中,对VB系的语言比较喜欢,而对C系和J系 ...

  10. 【神经网络与深度学习】【python开发】caffe-windows使能python接口使用draw_net.py绘制网络结构图过程

    [神经网络与深度学习][python开发]caffe-windows使能python接口使用draw_net.py绘制网络结构图过程 标签:[神经网络与深度学习] [python开发] 主要是想用py ...

随机推荐

  1. 如何查看项目svn路径

    1.选择项目根目录---->鼠标右键---->属性---->版本控制(Subversion) 如图:

  2. POJ1419 & 最大团

    题意: 求一个图的最大点独立集.SOL: 转化为补图的最大团,最大团似乎是一个NP问题,那么只好爆搜了. 补一补图论基础,代码不想打了,来自某blog #include <iostream> ...

  3. jquery属性过滤选择器

    http://www.jb51.net/article/46279.htm   $("div[id]").addClass("highlight"); //查找 ...

  4. 关于GC垃圾回收的原理

    .NET Framework 并不需要担心垃圾回收.但我们还是需要了解它的原理.才能让我们写出更高效的应用程序. .Net Framework 有一个GC(垃圾回收器),它会自动的帮我们把不需要的数据 ...

  5. JS类的封装及实现代码

    js并不是一种面向对向的语言, 没有提供对类的支持, 因此我们不能像在传统的语言里那样 用class来定义类, 但我们可以利用js的闭包封装机制来实现js类, 我们来封装一个简的Shape类. 1. ...

  6. 【ZOJ】1015 Fishing Net

    http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1015 题意:给出一个n个点的无向图,询问是否为弦图,弦图定义为对于图中任意 ...

  7. js小效果-轮播图

    <!DOCTYPE html><html><head lang="en"> <meta charset="UTF-8" ...

  8. 八、天气App案例

    该app为 现版本 SDK 8.4 Xcode 一.新建项目 运行Xcode 选择 Create a new Xcode project ->Single View Application 命名 ...

  9. Java I/O Basic

    /* 记住每个类相应的用法*/流的分类: io包内定义了所有的流 分类: 方向:输入流.输出流 处理数据单位:字节流.字符流 功能不同:节点流.处理流 所有流类型,位于java.io包内,分别继承以下 ...

  10. session.load()和session.get()的区别

    Session.load/get方 法均可以根据指定的实体类和id从数据库读取记录,并返回与之对应的实体对象. 其区别在于: 如果未能发现 符合条件的记录,get方法返回null, 而load方 法会 ...