函数
简介
函数是组织好的,可重复使用的,用来实现,单一或相关联的功能
函数能提高模块的利用行,和代码的重复利用率以及冗余,其实你已经使用了很多的Python函数,比如print()。
但是你也可以自己创建函数,这种叫做自定义函数
函数规则
- 函数代码块必须以def 关键字开头,后面为函数标识符名以及()
- 任何传入的参数和自定变量必须放在圆括号中间。圆括号之间可以用来定义参数
- 函数的第一行语句可以选择性的使用注释字符来表明函数的作用及说明
- 函数的内容以冒号起始,并且必须缩进
- return [表达式] 结束函数,选择性的返回一个值给调用该函数方。不带表达式的函数默认会返回
None
语法
#语法 def functionname( parameters ): "函数_文档字符串" function_suite return [expression] #实例 def printme( str ): "打印传入的字符串到标准显示设备上" print(str) return
默认情况下,参数值和参数名称是按函数声明中定义的顺序匹配起来的。
函数调用
- 定义一个函数只能给函数一个名称,指定函数里包含的参数,和代码块结构
- 函数基本结构完成以后,你可以通过另一个函数调用执行,也可以直接从python提示符执行
#!/usr/bin/python # -*- coding: UTF-8 -*- # 定义函数 def printme( str ): #打印任何传入的字符串 print(str); return; # 调用函数 printme("hello world"); #传参 printme("再次调用同一函数");
参数传递
- 函数已经讲清楚了,但是函数不够完美,比如一个例子,函数里定义一个变量a='hello world' 调用函数我们只能返回一个不能变化的字符串,我们如果想打印别的字符串就不行了
例子:
接上面函数调用的代码
我们告诉printme每次打印我传进来的东西,这个过程叫做 传递参数 , 简称
传参
实参和形参
参数还有分别:
我们调用函数时传递的这个
hello world
被称为 实际参数 ,因为这个是我们实际交给函数处理的内容,简称 实参
定义函数时,括号里的str,只是一个变量的名字,被称为 形式参数 ,因为在函数定义时它只是一个形式,表示这里需要一个参数,检查 形参 传递多个参数:
参数可以传递多个,用逗号
,
分开实参角度
- 按照位置传参
def mymax(x,y): #此时x=10,y=20 the_max = x if x > y else y > return the_max ma = mymax(10,20) print(ma)
2.按照关键字传参
def mymax(x,y): #此时x = 20,y = 10 print(x,y) the_max = x if x > y else y return the_max ma = mymax(y = 10,x = 20) print(ma)
3.位置、关键字形式混着用
def mymax(x,y): #此时x = 10,y = 20 print(x,y) the_max = x if x > y else y return the_max ma = mymax(10,y = 20) print(ma)
正确用法:
- 问题一:位置参数必须在关键字参数的前面
- 问题二:对于一个形参只能赋值一次
形参角度
位置参数必须传值
def mymax(x,y): #此时x = 10,y = 20 print(x,y) the_max = x if x > y else y return the_max #调用mymax不传递参数 ma = mymax() print(ma) #结果 TypeError: mymax() missing 2 required positional arguments: 'x' and 'y'
默认参数
- 正常使用
作用:可以将变化最小的值设置成默认参数
- 默认参数的定义
def stu_info(name,sex = "male"): """打印学生信息函数,由于班中大部分学生都是男生, 所以设置默认参数sex的默认值为'male' """ print(name,sex) stu_info('alex') stu_info('eva','female')
- 参数陷阱:默认参数是一个可变数据类型
def defult_param(a,l = []): l.append(a) print(l) defult_param('alex') defult_param('egon')
动态参数
按位置传值多余的参数都由args统一接收,保存成一个元组的形式
def mysum(*args): the_sum = 0 for i in args: the_sum+=i return the_sum the_sum = mysum(1,2,3,4) print(the_sum)
按默认参数传入多个参数,保存成为字典格式
def stu_info(**kwargs): print(kwargs) print(kwargs['name'],kwargs['sex']) stu_info(name = 'alex',sex = 'male')
命名空间和作用域
- 命名空间的本质:存放名字与值的绑定关系
命名空间一共分为三种: 全局命名空间 内置命名空间 局部命名空间
内置命名空间
存放了python解释器为我们提供的名字:input print str list tuple 拿过来就可以用的函数方法三种命名空间直接的加载和顺序
加载顺序:内置命名空间(程序运行前加载) --> 全局命名空间(程序运行中:从上到下加载) --> 局部命名空间(程序运行中,调用时才会加载)
取值
在局部调用:局部命名空间->全局命名空间->内置命名空间
x = 1 def f(x): print(x) print(10)
在全局调用:全局命名空间->内置命名空间
#全局x x = 1 def f(x): print(x) f(10) print(x) #全局max print(max)
- 作用域
作用域就是作用范围,按照生效范围可以分为全局作用域和局部作用域。
全局作用域:包含 内置名称空间、全局名称空间 ,在整个文件的任意位置都能被引用、全局有效
局部作用域:局部名称空间,只能在局部范围 内 生效
globals和locals方法
print(globals()) print(locals()) # 在局部调用globals和locals def func(): a = 12 b = 20 print(locals()) print(globals()) func() # 关键字global a = 10 def func(): global a a = 20 print(a) func() print(a)
函数小结
-
定义函数的规则:
1.定义:def 关键词开头,空格之后接函数名称和圆括号()。
2.参数:圆括号用来接收参数。若传入多个参数,参数之间用逗号分割。 参数可以定义多个,也可以不定义。 参数有很多种,如果涉及到多种参数的定义,应始终遵循位置参数、*args、默认参数、**kwargs顺序定义。 如上述定义过程中某参数类型缺省,其他参数依旧遵循上述排序
3.注释:函数的第一行语句应该添加注释。
4.函数体:函数内容以冒号起始,并且缩进。
5.返回值:return [表达式] 结束函数。不带表达式的return相当于返回 Nonedef 函数名(参数1,参数2,*args,默认参数,**kwargs):
"""注释:函数功能和参数说明"""
函数体
……
return 返回值 -
调用函数的规则:
1.函数名()
函数名后面+圆括号就是函数的调用。
2.参数:
圆括号用来接收参数。
若传入多个参数:
应按先位置传值,再按关键字传值
具体的传入顺序应按照函数定义的参数情况而定
3.返回值
如果函数有返回值,还应该定义“变量”接收返回值
如果返回值有多个,也可以用多个变量来接收,变量数应和返回值数目一致无返回值的情况:
函数名()有返回值的情况:
变量 = 函数名()多个变量接收多返回值:
变量1,变量2,... = 函数名() -
命名空间:
一共有三种命名空间从大范围到小范围的顺序:内置命名空间、全局命名空间、局部命名空间
作用域(包括函数的作用域链): 小范围的可以用大范围的但是大范围的不能用小范围的范围从大到小
高阶函数
-
函数的本质
函数名的本质实际上就是函数的内存地址
- 可以被引用
def func():
print('in func')
f = func
f()
print(f)
- 可以被当作容器的元素
def f1():
print('f1')
def f2():
print('f2')
l = [f1,f2]
d = {'f1':f1,'f2':f2}
l[0]()
d['f1']()
-
闭包函数
def func(): name = 'eva' def inner(): print(name)
闭包函数定义 :内部函数包含对外部作用域而非全局作用域名字的引用,该内部函数称为闭包函数
函数内部定义的函数称为内部函数,这样就不能拿到函数内部的变量和函数,只能返回
#输出的__closure__有cell元素 :是闭包函数 def func(): name = 'eva' def inner(): print(name) print(inner.__closure__) return inner f = func() f() #输出的__closure__为None :不是闭包函数 name = 'egon' def func2(): def inner(): print(name) print(inner.__closure__) return inner f2 = func2() f2()
装饰器
python装饰器是利用函数特性的闭包完成的,
开放封闭原则 :面向对象编程核心原则,软件实体应该是可以扩展的,而不可修改的。也就是说对扩展时开放的,而对代码本体修改是封闭的
- 开放原则:对扩展时开放的
- 封闭原则:对修改时封闭的
装饰器的作用 :装饰器是可以在不改动原有代码功能的基础上,可以扩展的功能。正是对开放封闭原则的完美体现。
工作原理 :依然在调用原有功能代码,但是能实现调用装饰器函数里的功能
def index(): '''这是一个主页信息''' print('from index') print(index.__doc__) #查看函数注释的方法 print(index.__name__) #查看函数名的方法 print(inner.__closure__) #输出的__closure__有cell元素 :是闭包函数
标准格式 :
from functools import wraps #已经开发好的装饰器模块 def deco(func): # 装饰器函数 #@wraps(func) # 加在最内层函数正上方 def wrapper(*args,**kwargs): # 加一堆参数 *args,**kwargs接受 return func(*args,**kwargs) return wrapper #返回内部函数的名字(注意) @deco #==> index = deco(index) #语法糖 @装饰器函数名,下面必须是被装饰的函数 def index(): '''哈哈哈哈''' print('from index') print(index.__doc__) print(index.__name__)
固定格式 :
使用情况 :
在已经写好的发版的程序功能基础上,需要对一个函数执行前后增加功能的时候
有的时候也会写好一些装饰器,装在需要装饰的函数上
生成器
我们知道的迭代器有两种:一种是调用方法直接返回的,一种是可迭代对象通过执行iter方法得到的,迭代器有的好处是可以节省内存。
如果在某些情况下,我们也需要节省内存,就只能自己写。我们自己写的这个能实现迭代器功能的东西就叫生成器。
Python中提供的生成器:
1.生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行
2.生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
生成器Generator:
本质:迭代器(所以自带了__iter__方法和__next__方法,不需要我们去实现)
特点:惰性运算,开发者自定义
生成器函数:
一个包含yield关键字的函数就是一个生成器函数。yield可以为我们从函数中返回值,但是yield又不同于return,return的执行意味着程序的结束,调用生成器函数不会得到返回的具体的值,而是得到一个可迭代的对象。每一次获取这个可迭代对象的值,就能推动函数的执行,获取新的返回值。直到函数执行结束。
示例代码:
import time
def genrator_fun1():
a = 1
print('现在定义了a变量')
yield a
b = 2
print('现在又定义了b变量')
yield b
g1 = genrator_fun1()
print('g1 : ',g1) #打印g1可以发现g1就是一个生成器
print('-'*20) #我是华丽的分割线
print(next(g1))
time.sleep(1) #sleep一秒看清执行过程
print(next(g1))
生成器有什么好处呢?就是不会一下子在内存中生成太多数据
文件监听例子
import time
def tail(filename):
f = open(filename)
f.seek(0, 2) #从文件末尾算起
while True:
line = f.readline() # 读取文件中新的文本行
if not line:
time.sleep(0.1)
continue
yield line
tail_g = tail('tmp')
for line in tail_g:
print(line)
列表推导式和生成器表达式
#老男孩由于峰哥的强势加盟很快走上了上市之路,alex思来想去决定下几个鸡蛋来报答峰哥
egg_list=['鸡蛋%s' %i for i in range(10)] #列表解析
#峰哥瞅着alex下的一筐鸡蛋,捂住了鼻子,说了句:哥,你还是给我只母鸡吧,我自己回家下
laomuji=('鸡蛋%s' %i for i in range(10))#生成器表达式
print(laomuji)
print(next(laomuji)) #next本质就是调用__next__
print(laomuji.__next__())
print(next(laomuji))
总结:
1.把列表解析的[]换成()得到的就是生成器表达式
2.列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存
3.Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如, sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和:
sum(x ** 2 for x in range(4))
而不用多此一举的先构造一个列表:
sum([x ** 2 for x in range(4)])
更多详情请看:https://www.cnblogs.com/Eva-J/articles/7276796.html
匿名函数
匿名函数
匿名函数:为了解决那些功能很简单的需求而设计的一句话函数
#这段代码
def calc(n):
return n**n
print(calc(10))
#换成匿名函数
calc = lambda n:n**n
print(calc(10))
上面是我们对calc这个匿名函数的分析,下面给出了一个关于匿名函数格式的说明
函数名 = lambda 参数 :返回值
#参数可以有多个,用逗号隔开
#匿名函数不管逻辑多复杂,只能写一行,且逻辑执行结束后的内容就是返回值
#返回值和正常的函数一样可以是任意数据类型
我们可以看出,匿名函数并不是真的不能有名字。
匿名函数的调用和正常的调用也没有什么分别。 就是 函数名(参数) 就可以了~~~
练一练:
请把以下函数变成匿名函数
def add(x,y):
return x+y
上面是匿名函数的函数用法。除此之外,匿名函数也不是浪得虚名,它真的可以匿名。在和其他功能函数合作的时候
l=[3,2,100,999,213,1111,31121,333]
print(max(l))
dic={'k1':10,'k2':100,'k3':30}
print(max(dic))
print(dic[max(dic,key=lambda k:dic[k])])
res = map(lambda x:x**2,[1,5,7,4,8])
for i in res:
print(i)
输出
1
25
49
16
64
res = filter(lambda x:x>10,[5,8,11,9,15])
for i in res:
print(i)
输出
11
15
面试题练一练
现有两个元组(('a'),('b')),(('c'),('d')),请使用python中匿名函数生成列表[{'a':'c'},{'b':'d'}]
#答案一
test = lambda t1,t2 :[{i:j} for i,j in zip(t1,t2)]
print(test(t1,t2))
#答案二
print(list(map(lambda t:{t[0]:t[1]},zip(t1,t2))))
#还可以这样写
print([{i:j} for i,j in zip(t1,t2)])
1.下面程序的输出结果是:
d = lambda p:p*2
t = lambda p:p*3
x = 2
x = d(x)
x = t(x)
x = d(x)
print x
2.现有两元组(('a'),('b')),(('c'),('d')),请使用python中匿名函数生成列表[{'a':'c'},{'b':'d'}]
3.以下代码的输出是什么?请给出答案并解释。
def multipliers():
return [lambda x:i*x for i in range(4)]
print([m(2) for m in multipliers()])
请修改multipliers的定义来产生期望的结果。
递归函数
Python内部:最大的递归深度为1000次
内置函数
# dir可以查看一个数据可以调用哪些方法 也可以通过某一个方法是不是在结果中,从而判断
dir()
callable()
print()
sep # 分隔符
ent # 结束符
file # 写入文件,文件句柄
eval()
exec()
open()
input()
id()
sum min max # 都支持接受iter 可迭代对象
ord # 字符找到Ascii码的位置
chr # Ascii码的位置找到字符
repr # 打印某个变量的值的值得时候,更便于区分类型
reversed # 反转 本身不变,返回的是个迭代器
filter # 返回值为迭代器 查找
map # 返回值为迭代器
enumerate #枚举函数 enumerate(iterable,1)