函数基础与进阶
函数
定义函数的方式
def 函数名(): # 定义阶段(造车轮阶段)
"""函数注释写在这里""" # 函数相当于工具, 注释相当于工具的说明书
<代码块>
# 使用 # 调用阶段(开车阶段)
函数名()
def func():
"""func函数的注释"""
# todo:未来写一个开车函数
pass
func()
注意:函数在定义阶段的时候不执行函数整体代码,智能检测到语法错误
print(func.__doc__)
可以知道函数中的注释
函数的简单实例
我们不妨尝试用函数来实现登录注册功能
def register():
"""注册函数"""
username = input('请输入你的用户名:')
pwd = input('请输入你的密码:')
with open('user_info.txt', 'a', encoding='utf8') as fa:
fa.write(f'{username}:{pwd}|')
def login():
"""登录函数"""
username = input('请输入你的用户名:')
pwd = input('请输入你的密码:')
with open('user_info.txt', 'r', encoding='utf8') as fr:
data = fr.read()
user_list = data.split('|')
print(user_list)
user_info = f'{username}:{pwd}'
if user_info in user_list:
print('登录成功')
else:
print('傻逼,密码都忘了')
有没有发现,当函数没有参数的时候,里面的代码块和在外面打的没有什么区别。其实,函数更多的是一种思想,而不是一种技术
函数的三种定义方式
三种方式分别为无参函数,有参函数,空函数
无参函数
def add():
"""无参函数"""
x = input('num1:')
y = input('num2:')
print(int(x) + int(y))
无参函数可以进行单独使用,上面的登录注册也是同理
有参函数
有参函数在函数中增加了参数,再往函数中输入参数之后才能使用
def add(x, y):
"""有参函数"""
print(int(x) + int(y))
print(1)
x = input('num1:')
y = input('num2:')
add(x, y)
结果为:
num1:1
num2:2
3
空函数
顾名思义,里面什么都没有
当你不知道函数怎么写的时候,可以先放一边,等以后再来写
def func(): # 只知道工具叫什么名字,但不知道如何造出这个工具
pass
函数的调用
def add(x, y):
return x+y
res = add(10, 20) # 函数的调用
print(res*12) # 360
这就是函数简单的调用,无参函数一般都是直接执行func(),有参函数则要在括号中输入符合要求的参数
函数的返回值
函数的返回值就是return,当函数执行到return时,直接返回return后面的数值,有一点类似于break的用法,可以配合if函数的使用来返回多种情况的值
def add(x, y):
return (x, y, x + y) # return可以返回任意数据类型
return x, y, x + y # return可以返回任意数据类型,不加括号返回多个值时,默认用元祖的形式返回
到这里函数的基础部分已经结束了。函数并不难,只要多敲几遍熟悉熟悉,相信马上就能熟能生巧
可变长参数
形参
位置形参
默认形参
实参
位置实参
关键字实参
可变长参数之*
如果当你的用户可以输入很多的值,或者输入的值不固定,也就是实参的个数很多或输入的值不固定的时候的时候,我们不可能设定很多的形参,也没法办到一一对应数量不同的实参。这个时候,我们就需要用到可变长参数。
def func(name, pwd, *args):
print('name:', name, 'pwd:', pwd)
print(args) # args就会接收多余的位置实参
return 1
res = func('hyc', 123658, 18, 180, 'a', ['reading']) # 用户使用func函数的时候会传用户的信息进入函数,但是不知道传几个
print(res)
结果为:
name: hyc pwd: 123658
(18, 180, 'a', ['reading'])
1
*args会接收多余的位置实参,args是自己定义的,但是Python都用args来表示。这是是约定俗成的
*args一般用于用户使用func函数传用户的信息进入函数,但是不知道传几个的时候,他会将传出来的值合并成一个元组,其中的值可以为任意数据类型
虽说*args看似完美的解决一些问题,但是却似乎在实际应用中并不是那么的实用。它输出的是元组,别的人看到很难理会这个元组的意思,之后讲的还有一这方法就较为完美的解决了这一个问题
但是在这之前,不妨先看看*的其他用法,他可以将元组打散成位置实参传给形参
def func(name, pwd, x, y, z):
print('name:', name, 'pwd:', pwd)
print(x, y, z)
return 1
tup = (4, 2, 3)
res = func('hyc', 123456, *tup) # *会把元祖打散成位置实参传给形参
print(res)
结果为:
name: hyc pwd: 123456
4 2 3
1
可变长参数之**
**args的用法和*args较为类似。但是最明显的不同之处在于,**args只能传关键字实参,并且输出的是字典。这样子输出的信息就更加的清楚明了了。
def func(name, pwd, *args, **kwargs):
print('name:', name, 'pwd:', pwd)
print(args)
print(kwargs) # kwargs会接收多余的关键字实参,以字典形式存储
return 1
res = func('hyc', 123456, 1, 2, 3, 4, age=21, height=180)
print(res)
结果为:
name: hyc pwd: 123456
(1, 2, 3, 4)
{'age': 18, 'height': 180}
1
结果为:
name: hyc pwd: 123456
(1, 2, 3, 4)
{'age': 18, 'height': 180}
1
当然,**也可以将字典打算成关键字实参传给形参
def func(name, pwd, **kwargs):
return kwargs
dic = {'a': 5, 'b': 2} # **dic --> a=1 , b=2
res = func('nick', 123658, **dic)
# res = func('nick', 123658, a=1, b=2)
print(res)
结果为
{'a': 5, 'b': 2}
函数对象
函数名等同于变量名,即变量名有的方法,函数名同样也有, 被引用 ; 当做容器元素 ; 当做函数参数 ; 当做函数返回值
def func():
print('from func')
print(func)
并没有报错,可以发现它指向一块内存地址
同时它也可以和变量名一样被赋值
def func():
print('from func')
f = func # func可以加()调用,那么f也可以加()调用
print(f, func)
f()
变量名还可以放入容器类数据类型
def func():
print('from func')
a = 1
lt = [1, 2, a, func]
lt[-1]()
函数名还可以被当做参数
def func():
print('from func')
def f2(name): # name = func
name() # func()
# f2(a)
f2(func)
函数的返回值
def func():
print('from func')
def f3(name): # name = func
return name # name = func
res = f3(func) # res = func
print(res)
res()
函数的嵌套
和if判断一样,函数之内也可以进行嵌套
def f1():
def f2():
print('from f2')
return f2
abc = f1() # f1()拿到函数的返回值,函数的返回值是f2, abc就相当于f2
abc()
它的用法也很简单,但是还是有一些需要让人注意的地方。这些我们会在之后讲
现在,我们可以先用嵌套的方法,来实现一下一个函数求圆的面积和周长
import cmath
def circle(r, action):
def area():
return cmath.pi * r ** 2
def zhouchang():
return 2 * cmath.pi * r
if action == 'area':
area()
elif action == 'zhouchang':
zhouchang()
area = circle(3, 'area')
print(area)
zhouchang = circle(3, 'zhouchang')
print(zhouchang)
当然也可以将它和不用函数嵌套的进行比较
import cmath
def circle(r, action):
if action == 'area':
return cmath.pi * r ** 2
elif action == 'zhouchang':
return 2 * cmath.pi * r
area = circle(3, 'area')
print(area)
zhouchang = circle(3, 'zhouchang')
print(zhouchang)
然后你们就会发现,好像使用嵌套还让代码变得更长了。。
这么看来嵌套除了看起来牛逼好像没有什么实际的用处。。
其实如果代码变得很长时,适当的嵌套还是可以节省代码的长度的
下面这个例子就很好的说明了这一点:在比较四个值中引入比较俩个值的函数:
def self_max(x, y):
if x > y:
return x
return y
res = self_max(10, 20)
print(res)
# 比较四个值
def self_4_max(x, y, z, c):
res1 = self_max(x, y)
res2 = self_max(z, c)
res = self_max(res1, res2)
return res
res = self_4_max(10, 20, 1, 100)
print(res)
这时你会发现,如果比较四个值又重新写一段代码的话会复杂一些
名称空间和作用域
名称空间
名称空间是用来存放名字的(变量名/函数)
内置名称空间
内置名称空间是Python解释器独有的
函数调用必须得定义, 从来都没定义过. Python解释器启动的时候python自动开辟内置名称空间存放了这些python的内置方法,python解释器停止解释之后才会销毁
全局名称空间
除了内置空间和局部空间,其他的都是全局空间
全局空间需要自己定义,python文件执行之后才可能有全局名称空间,文件结束之后才会销毁
局部名称空间
函数内定义的变量名/函数名都存放在局部名称空间
局部名称空间也需要自己定义, 必须得在函数调用之后才会生成, 调用结束之后就会销毁
三种名称空间的执行顺序
内置 --> 全局 --> 局部
z = 10
def f1():
x = 10
def f2():
y = 10
print('from f2')
三种名称空间的查找顺序
首先从自己当前位置开始 --> 局部 --> 全局 --> 内置
x = 1
def f1():
x = 3
print(x)
你会发现,结果为1,这就是因为x=3是函数内部的变量名,所以不会被执行
def f1():
return x
# print(x) # f1中的局部
def f2():
x = 2 # x=2只能在f2中使用
f1()
f2() # f1中的局部和f2中的局部互不干涉
这里也是如此,函数中的变量只能再这个函数中使用
global & nonlocal
如果想要把函数中的变量变为全局,就可以使用global
x = 1
def f1():
global x # 声明x为全局的x
x = 3
f1()
print(x) # 3
nonlocal则可以针对嵌套函数局部之间的修改
nonlocal
def f1():
x = 1
def f2():
nonlocal x # 针对嵌套函数局部之间的修改
x = 3
f2()
print(x)
f1() # 3
作用域的关系仅适用不可变数据类型,不适用于可变数据类型
lt = [1,2,3]
def f1():
lt.append(4)
f1()
print(lt)
dic = dict()
def login():
dic['name'] = 'nick'
login()
def shopping():
if dic.get('name'):
print('购物成功')
else:
print('请登录')
shopping()
s = '空'
def login():
s = 'nick'
login()
print(s)
结果为:
[1, 2, 3, 4]
购物成功
空