函数 复习

1、函数的定义:

  函数是组织好的,可重复使用的,用来实现单一或相关联功能的代码段。
  函数能提高应用的模块性,和降低代码的重复利用率。我们已熟知Python内部提供了很多内建函数,你也可以自己定义创建函数,但别和内部函数(模块)重名,叫做用户自定义函数。

定义一个函数:

可以自定义一个由自己想要功能的函数,以下是创建函数的简单规则:

  函数代码块以 def 关键词开头,后接函数标识符名称和圆括号()。
  任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数。
  函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
  函数内容以冒号起始,并且缩进。
  return [表达式] 结束函数,选择性地返回一个值给调用方,返回结果为元组类型。不写表达式return 或 return后没有值,返回的结果是 None。

语法:

	def 函数名(参数1,参数2,参数3,,,,):
		“描述信息”
		函数体
		return       #用来定义返回值,可以跟任意数据类型
#举例
def print_line():
	print("*"*13)

def print_msg():
	print("alex lala")

print_line()
print_msg()
print_line()
*************
alex lala
*************

2、函数的调用:

  定义一个函数只给了函数一个名称,指定了函数里包含的参数,和代码块结构。这个函数的基本结构完成以后,你可以通过另一个函数调用执行,也可以直接执行。

3、关于return

  return语句[表达式] 有return会直接退出函数,选择性地向调用方返回一个表达式。
  没有return返回None

  return 1返回1
  return1,2,3返回(1,2,3)元组
  多个return只返回第一个

4、变量的作用域:

  一个程序的所有的变量并不是在哪个位置都可以访问的。访问权限决定于这个变量是在哪里赋值的。变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。

两种最基本的变量作用域如下: 
  全局变量:定义在函数外的拥有全局作用域,py文件内任何位置都可以调用。
  局部变量:定义在函数内部的变量,拥有局部作用域,仅能函数内部访问。
  局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
total = 0; # 这是一个全局变量
# 可写函数说明
def sum( arg1, arg2 ):
   #返回2个参数的和."
   total = arg1 + arg2; # total在这里是局部变量.
   print "函数内是局部变量 : ", total
   return total;
  
#调用sum函数
sum( 10, 20 );
print "函数外是全局变量 : ", total
  以上示例输出结果为:

1
2
函数内是局部变量 :  30
函数外是全局变量 :  0

5、传参:

Python函数的两种类型参数:一种是函数定义里的形参,一种是调用函数时传入的实参。
  
从实参的角度:

   规则:按位置传值必须在按关键字传值的前面
  对一个形参只能赋值一次
    1.按照位置传值
    2.按照关键字传值
    3.混着用

从形参的角度:

  规则:默认参数必须放到位置参数的后面
    1.位置参数
    2.默认参数
    3.*args (接收位置传值)
    4.**kwargs(接收关键字传值)

6、函数的嵌套

  1、python支持嵌套函数;
  2、内层函数可以访问外层函数中定义的变量,但不能重新赋值(rebind);
  3、内层函数的local namespace[局部名称空间]不包含外层函数定义的变量

函数的嵌套分为:函数的嵌套调用,函数的嵌套定义。这和作用域有关。

#嵌套定义
- 找值的过程:先在局部名称空间找,再到上一级的局部名称空间找,再到全局名称空间找,再到内置名称空间
- 注意:在函数内定义的函数 在外面不能用到
x=1111111
def f1():
	x=1
	print("----->f1",x)
	def f2():
		x=2
		print("---->f2",x)
			def f3():
				x=3
				print("--->f3",x)
		f3()
	f2()
f1()
  
#嵌套调用
 - 将一个大的功能细化成各种小的函数功能并调用
def my_max(x,y)
	res=x id x>y else y
	return res
print(my_max(10,100))
 
def my_max1(a,b,c,d):
	res1=my_max(a,b)
	res2=my_max(res1,c)
	res3=my_max(res2,d)
	return res3
print(my_max1(1,23,34,4))

7、Python的高阶函数

Python的高阶函数,一切皆对象,函数对象
  高阶函数就是把函数当做参数传递的一种函数。
    函数本身可以赋值给变量,赋值后变量为函数;
    允许将函数本身作为参数传入另一个函数;
    允许返回一个函数。

def foo():#foo代表函数的内存地址
		print('foo')
	print(foo)#打印出的是foo函数的内存地址,内存地址加括号就可以调用该函数
	 
	#函数可以被赋值
	f=foo
	print(f)#打印的是foo函数的内存地址
	f()#等于foo()
	 
	#把函数当成参数传递
	def bar(func):
		print(func)
		func()
	bar(foo)#传入的是foo函数的内存地址,运行结果是打印foo函数的内存地址和foo函数的运行结果
	 
	#把函数当成返回值
	def bar(func):
		print(func)
		return func
	f=bar(foo)
	print(f)
	f()

8、闭包

  定义:首先必须是内部定义的函数,该函数包含对外部作用域而不是全局作用域名字的引用

#闭包
x=1000000000
def f1():
	x=1
	y=2
	def f2():
		print(x)
		print(y)
	return f2  #返回得f2不仅是返回了f2函数局部作用域还返回了引用的外部作用域的变量
f=f1()
print(f)
print(f.__closure__)#必须是闭包才能用此命令
print(f.__closure__[0].cell_contents)#查看值
print(f.__closure__[1].cell_contents)
-------------------------------------------------------------------------------------------------------------------------------
###############################################################################################################################
<function f1.<locals>.f2 at 0x0000000000A7E1E0>
(<cell at 0x0000000000686D08: int object at 0x000000005E5522D0>, <cell at 0x0000000000686D38: int object at 0x000000005E5522F0>)
  #代表f2引用了,f1中的x=1
  #代表f2引用了,f2中的y=2

闭包用途:爬虫

#爬虫
from urllib.request import urlopen
def get(url):
	return urlopen(url).read()
print(get('http://www.baidu.com'))
 
 
#专门爬百度页面
def f1(url):
	def f2():
		print(urlopen(url).read())
	return f2  #返回的是f2的内存地址 和 url
baidu=f1('http://www.baidu.com')#等式右边就是return的值,也就是f2的内存地址 和 url<br><br>baidu()

9、递归函数

  必须有一个明确的结束条件
  每次进入更深一层递归时,问题规模相比上一次递归都应有所减少
  递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出),栈的大小默认是1000可以修改,但是并没有意义。

import sys
sys.setrecursionlimit(10000)
  

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

  举个例子,我们来计算阶乘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)    #这种思想太牛逼了,感觉脑洞大开
print(fact(5))
  我们在计算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),最好别试。

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

  尾递归是指,在函数返回的时候,调用自身本身,并且,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)
print(fact(5))
  可以看到,return fact_iter(num - 1, num * product)仅返回递归函数本身,num - 1和num * 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标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。

10、递归函数二分法理论

data = [1, 3, 6, 7, 9, 12, 14, 16, 17, 18, 20, 21, 22, 23, 30, 32, 33, 35]
def search(num,data):
	print(data)
	if len(data) > 1:
		mid_index=int(len(data)/2)
		mid_value=data[mid_index]
		if num > mid_value:
			data = data[mid_index:]
			return search(num,data)
		elif num < mid_value:
			data = data[:mid_index]
			return search(num,data)#这里的return是下一个search函数的return值
		else:
			print('find it')
			return 666   #要想这个地方的return的值能有每个函数上面所有的search前面都要加return因为,这是一个递归函数,
	else:
		if data[0] == num:
			print('find it')
			return 777
		else:
			print('not found')
			return 888
search(19,data)
print(search(19,data))

11、装饰器

  定义一个Python函数,在不改变原代码的前提下,为函数增加额外功能。
语法及注意事项
  1、书写规范 @ *** ***指装饰器的函数名
   2、装饰器作为一个函数,他会把其下一行的主函数名作为变量,传递到自己的函数去调用。再重新赋值主函数。
   3、装饰器必须放到被装饰的函数上边,并且独占一行;
  4、某个函数上应用了多个装饰器,当定义的时候是按照由内到外定义,执行时是按照先外到内执行。

书写格式:
	(无参装饰器)
	def wrapper(func)
		def inner(*args,**kwargs)
			#要额外添加的方法
			res = func(*args,**kwargs)  #原函数,有返回值就接收没返回值为None
			return res
		return innner
	(有参装饰器)
	def foo(auth)
		def wrapper(func)
			def inner(*args,**kwargs)
				if auth 进行判断
				#要额外添加的方法
					res = func(*args,**kwargs)  #原函数,有返回值就接收没返回值为None
					return res
			return innner
		return wrapper
		
	@foo(auth=XXX)  #带参数的认证
	@wrapper
	def index()
		print("测试")
	
	index()

装饰器执行流程:
  1、首先明确,Python代码是自上而下执行的,对于函数仅是先定义不会执行函数内部的方法。
  2、先定义wrapper 函数,然后走到@warpper【这是个语法糖,意思是把被装饰的函数名 index 以参数的形式传入wrapper】-->等价于warpper(index)
  3、顺序执行回到wrapper函数,函数内部继续向下执行,定义了其内部函数 inner,执行原函数 index()并return,然后把 inner 函数定义 返回。其实就是将原来的 index 函数塞进另外一个函数中去执行。
  内部执行:
    def inner:
      #新添加的方法
      return index() # func是参数,此时 func 等价于 index
    return inner # 返回的 inner,inner代表的是函数,非执行函数
  4、将执行完的 wrapper 函数返回值 赋值 给@wrapper下面调用执行函数【index()】的函数名 。
  此时,wrapper函数的返回值是:
    def inner:
      #新添加的方法
      return 原来index()执行结果 # 此处的 index 表示原来的index函数
  5、然后,将此返回值再重新赋值给 index,即:
    新index = def inner:
      #新添加的方法
      return 原来index()
  6、函数调用执行了新的 index(),不仅执行了原来的函数内容,并且也执行了新添加的方法。

多个装饰器存在,执行顺序情况
def decorator_a(func):
	print('Get in decorator_a')
	def inner_a(*args, **kwargs):
		print('Get in inner_a')
		return func(*args, **kwargs)
	return inner_a

def decorator_b(func):
	print('Get in decorator_b')
	def inner_b(*args, **kwargs):
		print('Get in inner_b')
		return func(*args, **kwargs)
	return inner_b

@decorator_b
@decorator_a
def f(x):
	print('Get in f')
	return x * 2

f(1)
#输出结果:	
Get in decorator_a
Get in decorator_b
Get in inner_b
Get in inner_a
Get in f

12、迭代器

  可迭代对象:判断对象本身是否内置了__iter__方法,那就是可迭代对象。元组,字符串,字典,集合,列表都是 可迭代对象。文件 既是可迭代对象也是迭代器。
  可迭代对象,执行对象下的__iter__(内置item())方法,就会得到一个返回值,得到的返回值就是迭代器。使用next()就能依次取出迭代器中的值(在迭代器元素个数之内,超出会报错,也是一次性的元素,不可重复取值),这样就不用再依赖下标的方式。


  迭代器:迭代器提供了一个统一的访问的接口,只要是定义了iter()方法的对象,就可以使用迭代器进行访问。只要可以进行访问,就能被next()方法调用并不断返回下一个值的对象称为迭代器。换句话说,迭代器对象具有next()方法。

判断类型:isinstance()
  from collections import Iterable,Iterator #导入模块,可迭代对象,迭代器

迭代器特点:
  1、取值从第一个元素开始,直至所有元素全部遍历完毕。迭代器不能回退,只能往前进行迭代。
  2、迭代器的长度未知,取值不灵活,只能一直next()去取值。
  3、不依赖索引取值,这就导致迭代器是惰性计算,当next()取值迭代到某元素的时候才会计算该元素。
  4、迭代器的内容是一次性的,取完就完了!

关于异常:
  因为取值长度位置,所以一直next()取值的话,就有可能会溢出抛出异常。常用try except捕获异常

l=['a','b','c','d','e']
i=l.__iter__()
while True:
	try:  #监听代码是否会报异常 StopIteration
		print(next(i))
	except StopIteration:  #判断异常是否为 StopIteration,是break
		break

关于for循环:
  for 循环本质就是内部封装了迭代器,对 可迭代的对象 进行遍历取值,同时会在遇到异常捕获的时候会自行处理,所以for 循环作用在迭代器上不报错。
  可以使用for循环进行迭代的对象都是可迭代(Iterable)类型!可以调用next()方法的对象都是迭代器(Iterator)类型!

13、生成器

  一言以蔽之:带有 yield 的函数被称为生成器
  加入了yield函数的循环,所有的执行过程都会在yield函数这里停顿进行判定或是开始,当执行一周再次走到yield函数这里时,会再次停顿进行判定或是开始。
  注意点:生成器是可迭代对象,也是迭代器对象。生成器的本质,就是将函数做成了一个迭代器,取名为生成器。所以生成器可以next()取值,也可以使用for 循环遍历取值
yield 函数到底干了什么事情:
  1)yield 把函数变成生成器 ---> 迭代器;
  2)用return 返回只能返回一次,而yield返回多次;
  3)函数在暂停以及继续下一次运行时的状态,是由yield保存。

14、协程函数:

  如果在一个函数内部yield的使用方式是表达式形式的话,如x=yield,那么该函数成为协程函数。
  生成器代码执行过程中,碰到yield 程序就会暂停,既然yield 以表达式的形式出现在函数中,就表明将 yield 暂停时从外部所携带来的值 传给等号左边的变量。
  整个代码再通过next()函数触发,从yield处开始往下走,代码循环一圈之后,又回到yield这儿停止,以此循环。整个代码通过外部的send()函数给yield传值。
注意点:
  1、协程函数第一次需要next()先激活;
  2、协程函数需要send()传值。

带装饰器的协程函数,触发交给装饰器

def init(func):
	def warpper(*args,**kwargs):
		g=func(*args,**kwargs)
		next(g)
		return g
	return warpper

@init #eater=init(eater) #eater=warpper
def eater():
	print('start to eat')
	while True:
		food=yield
		print('is eating food:%s' % food)

e=eater()#e=warpper()
e.send('北京烤鸭')

输出结果:
start to eat
is eating food:北京烤鸭

15、匿名函数:

  定义:是指一类无需定义标识符(函数名)的函数或子程序。
  语法格式:lambda 参数(可多个):表达式

应用:

x = int(input('please input a number:'))
#1、用户输入一个数字,生成一个1至此数的列表
la1 = lambda x : [i for i in range(1,x+1)]
print(la1(x))
#2、在列表的基础上,每一个数字都+1,返回一个新列表
la2 = lambda x : [i+1 for i in range(1,x+1)]
print(la2(x))
#3、在这个列表的基础上,所有的奇数都x2,返回一个新列表,只有奇数
la3 = lambda x : [i*2 for i in range(1,x+1,2)]
print(la3(x))
#4、在这个列表的基础上,变成["1+1=2","2+2=4"]返回一个新的字符串列表
la4 = lambda x :["%s+%s=%s"%(i,i,2*i) for i in range(1,x+1)]
print(la4(x))
#5、在这个列表的基础上,所有奇数乘以该奇数的下一个偶数
la5 = lambda x :["%sx%s=%s"%(i,i+1,i*(i+1))for i in range(1,x+1,2)]
print(la5(x))

16、内置函数:

abs(参数) 取绝对值 参数必须是整型

不是可迭代对象 '',None,0
all(可迭代对象) 判断是否是可迭代对象,返回布尔值  与运算,只要有一个不是就返回False  特殊的可迭代对象为空返回True
any(可迭代对象) 判断是否是可迭代对象,返回布尔值  或运算,只要有一个是就返回True     特殊的可迭代对象为空返回False

进制转换:参数为10进制数
bin(参数) #转为二进制
hex(参数) #转为十六进制
oct(参数) #转为八进制

ASCII码转换:
chr()  #将数字转换成字符
ord()  #将字符转换成数字

数据类型:强制转换为对应类型
int() #整型  str() #字符串 list() #列表 tuple() #元组  dict() #字典  set() #集合 frozenset() #不可变集合  bool() #布尔判断 返回True or False
bytes(字符串,编码格式encoding="") #字符串转成字节 

type(参数) #查看参数的数据类型
isinstance() 函数来判断一个对象是否是一个已知的类型,返回布尔值 语法:isinstance(对象,类型)

callable(参数[一般为函数名]) #判断是否是函数,返回布尔值

dir(对象) 查看对象都能调用什么方法;
help()直接查看 函数 具体的帮助信息
id(变量名) #查看唯一标识身份信息 

divmod(int1,int2) #除法计算 返回结果为元组(商,余数)

enumerate(可迭代对象) #枚举
hash(参数) #哈希算法
eval(字符串)  #将字符串转换为表达式进行计算

reversed() 取反,本末倒置
zip(参数1,参数2) #拉链函数 参数是可迭代对象或是迭代器
	一一对应取值,重组成一个个元组。不管哪方个数多,最终结果都只会输出对应的元组,多余的不对应的舍弃

sorted() #排序 返回值为列表	默认为升序!
	语法格式:sorted(要排序参数,key=匿名函数lambda) 匿名函数定义排序判断的内容

max() or min() 最大值 or 最小值
	max(*args,key=func)   key可有可无。当需要用函数判断的时候,添加key.    min与max的使用方法一致。

map() #映射 语法格式:map(func,list) #对列表内的每个元素按照func的方法处理,最终结果是返回一个迭代器,list()取值得到处理之后的列表

reduce() #合并规则 语法格式:reduce(func,list,初始值)
	reduce()传入的函数func 必须接收两个参数,reduce() 对list的每个元素反复调用函数func,并返回最终处理完毕的结果

filter() #过滤器  语法格式:filter(函数func,可迭代对象list)
	函数的作用是对迭代对象的每个元素进行判断,返回 True或 False,filter()根据判断结果自动过滤掉为 False 的元素,返回由符合条件元素组成的新list。


pow() 两个值求方,三个值是前两个求方然后值和第三个数再取余数
round() 带小数点的值,四舍六入五留双。。。
slice() 定义切片对象
vars() #局部变量
_import_('字符串') 将导入的字符串转成模块

issubclass(Me, object)            # Me类是否为object类的子类
#部分代码举例,其他请自己测试

s = "hello"
l = [1,2,3,4,5,6]
print(list(zip(l,s)))
print(list(map(lambda x:x**2,l)))
from functools import reduce
print(reduce(lambda x,y:x+y,l,10))

salaries={
	 'egon':3000,
	 'alex':100000000,
	 'wupeiqi':10000,
	 'yuanhao':2500
}
#min同理
print(max(salaries))
print(max(salaries,key=lambda x:salaries[x]))

name_l =[
	{"name":'egon','age':120},
	{"name":"fsw","age":1000},
	{'name':'wang','age':2000},
	{'name':'jie','age':18}
]
f = filter(lambda d:d['age']>100,name_l)
print(list(f))

 17、列表生成式 和 生成器表达式

列表生成式:

  通俗的来讲,列表生成式由三部分拼接组成:当然每次写之前都应该先给出[],然后在里边添加。
    1.expression 指要生成的元素(参数,变量),放在最前面
    2.后面跟上for循环
    3.for循环之后还可以加上if条件判断,以便进行筛选。
  实际使用的过程中,若一个for循环不能完成问题,还可以往下嵌套。
  例如:[i for i in range(3)]

生成器表达式:
  生成器表达式,我个人认为还不如叫列表生成器,就是把列表表达式改变了一下,变成了一个生成器。而且这种改变非常简单,就是把外[]换成了()就创建了一个generator。
  通过列表生成式,我们可以直接创建一个列表。但受到内存的限制,列表容量肯定是有限的,同时那么庞大的数据流,一下子拿出来什么机器得卡的受不了。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。
  在Python中,这种一边循环一边计算的机制,称为生成器:generator。generator生成器保存的是算法,每次通过next()触发取值,并且每次只取一个元素的值,直到计算到最后一个元素。没有更多的元素时,就会抛出StopIteration的错误。我们可以通过for循环来迭代它,并且不需要关心StopIteration的错误。

 

posted @ 2017-09-09 20:35  细雨蓝枫  阅读(304)  评论(0)    收藏  举报