1、函数基本概念
1.1、什么是函数
具备某一功能的工具/集合体称之为函数
事先准备工具的过程称之为函数的定义
遇到应用场景拿来就用称之为函数的调用
1.2、为何要有函数
函数分为两大类:内置函数、自定义函数
内置函数(python3解释器定义好的,我们只需要拿来使用即可)
优势:拿来主义,提升开发效率
自定义函数:(把程序中实现某一功能一块代码组织整理到一起)
优势:
1、增强程序的组织结构性、提升可读性
2、减少代码冗余
3、提升程序的可维护性与扩展性
1.3、如何使用函数
使用原则:先定义,后调用
定义函数的语法:
def 函数名(参数1, 参数2, 参数3...):
"""文档注释"""
代码1
代码2
代码3
...
return 返回值
调用函数的语法:
函数名(值1, 值2, 值3...)
1.4、函数的使用分为两个阶段
定义阶段:申请内存空间把函数体代码放进去,然后把内存地址绑定给函数名 => 函数名=函数的内存地址
调用阶段:通过函数名找到函数体代码,加括号触发函数体代码运行
函数名() => 函数的内存地址(),会触发函数体代码的运行
函数的使用一定要分两个阶段去看
1、定义阶段: 只检测语法,不执行代码
2、调用阶段: 执行函数体代码
如果发生的是语法错误,定义阶段就会立马检测出来
如果发生的不是语法错误,而是逻辑错误,只能在调用阶段检测到
# 示范一
def bar():
print('from bar')
def foo():
print('from foo')
bar()
foo()
# 示范二
def foo():
print('from foo')
bar() # 定义foo的阶段不会执行函数体代码,即不会执行bar函数
# 在调用foo的时候bar已经定义了 所以不会报错
def bar():
print('from bar')
foo()
1.5、函数定义的三种形式
# 1、无参函数
def test():
print('这是一个测试函数')
# 2、有参函数
# 例:计较两个数字的大小
def max2(x, y):
if x > y:
print(x)
else:
print(y)
max2(10, 11)
max2(20, 21)
# 3、空函数(两种定义方式)
def register():
pass
def login():
...
1.6、函数调用的三种形式
# 1、语句形式:单纯的调用
# 使用内置函数
print("hello world")
# 使用自定义函数
def test():
print('这是一个测试函数')
test()
# 2、表达式方式的调用
def max2(number1, number2):
if number1 > number2:
return number1
else:
return number2
print(max2(10, 11) * 12) # 132 拿到函数的返回值与12相乘
# 等价于:
x = max2(10, 11)
print(x * 12)
# 3、可以当做参数传给另外一个函数
def max2(number1, number2):
if number1 > number2:
return number1
else:
return number2
res = max2(max2(10, 11), 12) # 拿到前两者的最大值与第三个继续比较
print(res)
1.7、函数的返回值
# 1、函数体内没有return或return后没有值:默认返回是None
def func():
# return None
pass
res = func()
print(res)
# 2、return 值:返回的就是这一个值
def func():
return 123
res = func()
print(res)
# 3、return 值1,值2,值3:返回一个小元组(值1,值2,值3)
def func():
return 123, "abc", 3.1
res = func()
print(res, type(res))
# 补充:函数内可以有多个return,return是函数结束的标志,函数体代码但凡运行到一个return就会立刻终止整个函数的运行,然后将return后的值当做本次调用的产品返回
def func():
print("first")
return 111
print('second')
return 2222
print('third')
return 3333
res = func()
print(res) # first 111
def func():
while True:
while True:
return
res = func()
print(res)
函数可以有三种形式的返回值
1、return 值: 返回的就是该值本身
2、return 值1,值2,值3: 返回一个元组
3、没有return: 默认返回None
1.8、函数的参数分类
函数的参数分为两大类
1、形参:在定义函数阶段,括号内指定的参数,称之为形式参数,简称形参
形参相当于定义在函数内的变量名,用于接收外部传进来的值
def func(x, y):
# x=10
# y=11
print(x)
print(y)
2、实参:在调用函数阶段,括号内传入的值,称之为实际参数,简称实参
实参相当于变量值,用于为形参赋值的
func(10, 11)
ps:在函数调用时,会将实参的值绑定给形参,这种绑定关系只能在函数内使用,在函数调用完毕后,实参与形参会解除绑定,回收占用的内存空间
2、函数参数
2.1、第一组形参与实参
2.1.1、位置形参、位置实参
1、位置形参:在函数定义阶段按照从左到右的顺序依次定义形参(变量名)
特点:必须被传值,多一个不行少一个也不行
def func(x, y):
print(x, y)
func(1, 2)
func(1) # 少一个不行
func(1, 2, 3) # 多一个也不行
2、位置实参:在函数调用阶段按照从左到右的顺序依次定义实参(传入的变量值)
特点:按照位置与形参一一对应
def func(x, y):
print(x, y)
func(1, 2)
func(2, 1)
2.1.2、默认参数(默认形参)、关键字实参
1、默认参数
在函数定义阶段就已经为某个形参赋值,该形参称之为有默认值的形参,简称默认形参
特点: 定义阶段就已经被赋值意味着在函数调用阶段可以不用为其赋值
def func(x, y=1111):
print(x, y)
func(1) # 不传y的值使用默认参数
func(1, 222) # 传递y的值,以实参为准
2、关键字实参:按照key=value的形式为指定的形参传值
特点:指名道姓为某个形参传值,可以完全打乱顺序
def func(x, y):
print(x, y)
func(1, 2)
func(y=2, x=1)
2.1.3、位置实参与关键字实参总结
可以混用位置实参与关键字实参
注意:
1、不能对同一个形参重复赋值
def func(x, y):
print(x, y)
func(1,2,x=111) # 报错
2、关键字实参必须跟在位置实参的后面
def func(x, y):
print(x, y)
func(1, y=2)
func(y=2, 1) # 报错
3.1.4、位置形参与默认参数(默认形参)总结
1、位置形参必须放在默认形参的前面
def func(y=1, x): # 报错
print(x, y)
2、默认参数的值是在函数定义阶段被赋值的,准确地说被赋予的是值的内存地址
m = 2
def func(x, y=m): # y=>2的内存地址
print(x, y) # 1 2
m = 333
func(1)
3、虽然默认值可以被指定为任意数据类型,但是不推荐使用可变类型
函数最理想的状态:函数的调用只跟函数本身有关系,不外界代码的影响
函数的调用彼此之间应该做到没有关联,所以说默认形参通常应该是不可变类型
l = []
def func(name, hobby, hobbies=l):
hobbies.append(hobby)
print('%s的爱好是%s' % (name, hobbies)) # allen的爱好是['阅读']
# lily的爱好是['阅读', '羽毛球']
# lucy的爱好是['阅读', '羽毛球', '游戏']
func('allen', '阅读')
func('lily', '羽毛球')
func('lucy', '游戏')
# 推荐写法(优化):
def func(name, hobby, hobbies=None): # 默认形参的值通常应该是不可变类型
if hobbies is None:
hobbies = []
hobbies.append(hobby)
print('%s的爱好是%s' % (name, hobbies))
func('allen', '阅读')
func('lily', '羽毛球')
func('lucy', '游戏')
2.2、第二组形参与实参
2.2.1、可变长度的参数
可变长度的参数指的是在调用函数时,传入的实参个数不固定,而实参是为形参赋值的,所以对应着也应该有新的形参格式来负责接收不固定长度的实参
2.2.2、形参中带*与**
1、*形参名:用来接收溢出的位置实参,溢出的位置实参会被*保存成元组的格式然后赋值紧跟其后的形参名
*后跟的可以是任意名字,但是约定俗成应该是args
def func(x, y, *z):
print(x, y, z)
func(1, 2, 3)
func(1, 2, 3, 4, 5)
func() # 报错
2、**形参名:用来接收溢出的关键字实参,**会将溢出的关键字实参保存成字典格式,然后赋值给紧跟其后的形参名
**后跟的可以是任意名字,但是约定俗成应该是kwargs
def func(x, y, **z):
print(x, y, z)
func(1, 2, a=111, b=222, c=333)
2.2.3、形参中带*与**总结
def func(x, y=2, *args, **kwargs):
print(x, y, args, kwargs)
func(1, )
func(1, 2, 4, 5, 6, 7, a=1, b=2)
def func(*args, **kwargs):
print(args, kwargs)
func(1, 2, 3, 4, 5, 6, a=1, b=2, c=3)
# 使用*args可以获取传入不固定参数的和
def add(*args):
res = 0
for i in args:
res += i
print(res)
add(1, 2, 3)
2.2.4、实参中带*与**
1、实参中带*,*后跟的那个值应该是一个可以被for循环遍历的值
*会将后面的那个值打散成位置实参
func(*(111, 222, 333)) # func(111,222,333)
func(*(111, 222)) # func(111,222)
func(*"hello") # func("h","e","l","l","o")
func(*{"k1": 111, "k2": 222, }) # func("k1","k2")
2、实参中带**,**后跟的那个值应该是一个字典
**会将后面的那个值打散成关键字实参
func(**{"k1": 111, "k2": 222, }) # func(k2=222,k1=111)
func(**{"x": 111, "y": 222, }) # func(y=222,x=111)
func(**[("x", 111), ("y", 222)]) # func(y=222,x=111)
2.2.5、形参与实参中混用*与**
def func(*args, **kwargs):
print(args, kwargs)
func(1, 2, 3, 4, 5, a=1, b=2, c=3)
func((1, 2, 3, 4, 5), {'a': 1, 'b': 2, 'c': 3})
func(*(1, 2, 3, 4, 5), **{'a': 1, 'b': 2, 'c': 3}) # func(1, 2, 3, 4, 5, a=1, b=2, c=3)
2.2.6、命名关键字参数
命名关键字参数:在定义函数时,*后定义的参数,称之为命名关键字参数
特点:
1、命名关键字实参必须按照key = value的形式为其传值
def func(x, y, *, a, b): # 其中,a和b称之为命名关键字参数
print(x, y)
print(a, b)
func(1,2,b=222,a=111)
# 命名关键字参数可以有默认值,实参中可以不传
def func(x, y, *, a=11111, b):
print(x, y)
print(a, b)
func(1, 2, b=22222)
2.组合使用
形参混用的顺序:位置形参,默认形参, *args, 命名关键字形参, ** kwargs
# 模板
def func(x, y=111, *args, z, **kwargs):
print(x)
print(y)
print(args)
print(z)
print(kwargs)
# 示例演示
def func(x, y=111, *args, z, a=123, **kwargs): # z,a必须按照关键字实参的格式为其赋值,a有默认值,可以不传
print(x) # 1
print(y) # 2
print(args) # (3, 4, 5)
print(z) # baidu
print(a) # 666
print(kwargs) # {'b': 'qq'}
func(1, 2, 3, 4, 5, a=666, b='qq', z='baidu')
2.2.7、*与**大总结
形参中带*与**是汇总操作
实参中带*与**是打散操作
def index(x, y):
print(x, y)
def wrapper(*args, **kwargs): # args=(1,2,3,4,5) kwargs={"a":1,"b":2,"c":3}
index(*args, **kwargs)
# 直接调用的是wrapper,然后通过wrapper间接调用index
wrapper(1, 2, 3, 4, 5, a=1, b=2, c=3) # 报错 看似是在为wrapper传参,实际是在给index传参,参数必须符合index函数的要求
wrapper(1, 2)
wrapper(y=2, x=1)
wrapper(1, y=2)
# 原格式--->汇总--->打回原形
3、函数对象
3.1、函数对象的具体操作
在python函数是第一类对象,简称函数对象
函数对象指的就是可以把函数当做变量一样去使用
1、可以赋值
def foo():
print('from foo')
print(foo)
f=foo
print(f is foo) # True
f()
2、可以当做参数传给另外一个函数
def func(aaa): # aaa=函数foo的内存地址
print(aaa)
aaa()
def foo():
print('from foo')
func(foo)
3、可以当做函数的返回值
def func(aaa): # aaa=函数foo的内存地址
return aaa # 返回的是函数foo的内存地址
def foo():
print('from foo')
res=func(foo)
print(res)
res()
4、可以当做容器类型的元素
def foo():
print('from foo')
l=[foo]
print(l)
l[0]()
# 应用场景:
# 利用函数对象可以取代if...elif...else的多分支问题
def login():
print('登录功能...')
def transfer():
print('转账功能...')
def withdraw():
print('提现功能...')
def recharge():
print('充值功能...')
func_dict = {
'1': [login, '登录'],
'2': [transfer, '转账'],
'3': [withdraw, '提现'],
'4': [recharge, '充值']
}
while True:
print('0 退出')
for k, v in func_dict.items():
print('%s %s' % (k, v[1]))
choice = input('请输入指令: ').strip()
if choice == '0':
print('退出成功')
break
if choice in func_dict:
func_dict[choice][0]()
else:
print('指令不存在...')
4、函数嵌套
4.1、定义与基本使用
函数嵌套定义:在一个函数内又定义了另外一个函数
实例一:
def f1():
def f2():
print('from f2')
f2()
x = 11111111
return x
res = f1()
print(res)
实例二:
def f1():
def f2():
print('from f2')
return f2
res = f1()
print(res) # <function f1.<locals>.f2 at 0x00000142A6B1CC80>
res() # from f2
4.2、利用函数嵌套计算圆的周长和面积
import math
def circle(radius,mode):
def perimeter(radius):
return 2 * math.pi * radius
def area(radius):
return math.pi * (radius ** 2)
# 利用标志位判断
if mode == 1:
return perimeter(radius)
elif mode == 2:
return area(radius)
print(circle(18,1))
print(circle(18,2))
4.3、函数嵌套调用
函数嵌套调用:在调用一个函数的过程中又调用了其他函数
# 比较最大值
# # 思路: 两两比较 大问题拆解成小问题
def max2(x, y):
if x > y:
return x
else:
return y
def max4(a, b, c, d):
res1 = max2(a, b)
res2 = max2(res1, c)
res3 = max2(res2, d)
return res3
print(max4(1, 2, 3, 4))
5、名称空间
5.1、名称空间的定义与分类
名称空间就是存放名字的地方
分类:
1、内置名称空间:存放的是python解释器自带的名字,如len、print、input
生命周期:解释器启动则创建,解释器关闭就销毁
2、全局名称空间:内置以及函数内的名字之外的名字都存放于全局名称空间中
生命周期:运行顶级代码/主流水线则创建,顶级代码/主流水线结束则销毁
x = 10 # 全局名称空间的名字
y = 12 # 全局名称空间的名字
# foo = 内存地址
def foo(): # foo本身也是全局的
z = 111
# f1=内存地址
def f1():
pass
if True:
aaa = 33 # aaa是全局名称空间的名字
3、局部名称空间:函数内的名字
生命周期:函数调用时则创建,函数调用完毕则立即销毁
def f1(x, y):
z = 3
def f2():
m=444
n=5555
f2()
f1(1, 2)
5.2、名称空间名字的查找顺序
从当前位置往外查找,如果当前是在局部: 局部名称空间 ——> 全局名称空间 ——> 内置名称空间
从当前位置往外查找,如果当前是在全部: 全局名称空间 ——> 内置名称空间
# 站在全局进行查找
input = 111
def f1():
input = 222
f1()
print(input) # 站在全局找是不会去局部空间找的
# 站在局部进行查找
def f1():
# input = 222
print(input) # 111
input = 111
f1()
强调:名称空间的"嵌套关系"是函数定义阶段、也就是检测语法的时候就确定的,与调用为无关
实例一:
x = 111
def f1():
print(x) # 111
def f2():
x = 222
f1()
f2()
实例二:
def f3():
print(x)
x = 111
x=222
f3() # 报错 UnboundLocalError: local variable 'x' referenced before assignment
6、作用域
6.1、作用域的分类
全局范围/全局作用域: 内置名称空间、全局名称空间
特点: 全局存活,全局有效
局部范围/局部作用域: 局部名称空间
特点: 临时存活,局部有效
6.2、函数的参数传递
函数的参数传递都是值拷贝
1、对全局定义的不可变类型,不可以在函数内直接修改
x = 10
def func(a): # a=10的内存地址
a = 123
func(x) # x=10的内存地址
print(x) # 10
2、对全局定义的可变类型,可以在函数内直接修改
x = []
def func(a): # a=列表的内存地址
a.append(11)
func(x) # x=列表的内存地址
print(x) # [11]
3、如果在局部想要修改全局的名字对应的值(不可变类型),需要用global
x = 10
l = []
def func():
global x # # 声明x这个名字是全局的名字,不要再造新的名字了
x = 22
l.append(11)
func()
print(x) # 22
print(l) # [11]
4、nonlocal:修改函数外层函数包含的名字对应的值(不可变类型)
x=0
def f1():
x=11
def f2():
nonlocal x
x=22
f2()
print('f1内的x:',x) # f1内的x:22
f1()
7、闭包函数
7.1、闭包函数定义与使用
闭包函数=名称空间与作用域+函数嵌套+函数对象
核心点:名字的查找关系是以函数定义阶段为准
"闭"函数指的该函数是内嵌函数
"包"函数指的该函数包含对外层函数作用域名字的引用(不是对全局作用域),内函数引用了外函数的变量
def outter():
x = 1111
def inner():
print(x)
return inner # 千万不要加括号
f = outter()
# print(f)
def f3():
x = 222222
f()
f3()
7.2、为函数体代码传参的两种方式
方式一:直接通过参数的方式传入(直接把函数体需要的参数定义成形参)
def func(x):
print(x)
func(1)
func(2)
func(3)
方式二:通过闭包函数传递参数
def outter(x):
def func():
print(x)
return func
f1 = outter(1)
f2 = outter(2)
f3 = outter(3)
f1()
f2()
f3()
7.3、针对函数体代码传参两种方式的案例
# 直接通过参数的方式传入
def get(url):
response = requests.get(url)
print(len(response.text))
get('https://blog.csdn.net/wcg920212/article/details/106542638')
get('https://blog.csdn.net/wcg920212/article/details/106543644')
get('https://blog.csdn.net/wcg920212/article/details/106543933')
# 通过闭包函数传递参数
def outter(url):
def get():
response = requests.get(url)
print(len(response.text))
return get
article1 = outter('https://blog.csdn.net/wcg920212/article/details/106542638')
article1()
article2 = outter('https://blog.csdn.net/wcg920212/article/details/106543644')
article2 ()
article3 = outter('https://blog.csdn.net/wcg920212/article/details/106543933')
article3()
8、偏函数
Python 偏函数是通过 functools 模块被用户调用
偏函数 partial 应用
函数在执行时,要带上所有必要的参数进行调用。但是,有时参数可以在函数被调用之前提前获知。这种情况下,一个函数有一个或多个参数预先就能用上,以便函数能用更少的参数进行调用
偏函数是将所要承载的函数作为partial()函数的第一个参数,原函数的各个参数依次作为partial()函数后续的参数,除非使用关键字参数
import functools
def create_pen(c_color, c_type):
print('创建了{},颜色是{}'.format(c_type, c_color))
penFunc = functools.partial(create_pen, c_type='钢笔')
penFunc('红色') # 创建了钢笔,颜色是红色
penFunc('绿色') # 创建了钢笔,颜色是绿色
penFunc('紫色') # 创建了钢笔,颜色是紫色
pencilFunc = functools.partial(create_pen, c_type='铅笔')
pencilFunc('红色') # 创建了铅笔,颜色是红色
pencilFunc('绿色') # 创建了铅笔,颜色是绿色
pencilFunc('紫色') # 创建了铅笔,颜色是紫色