函数-入门
Posted chao-sir
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了函数-入门相关的知识,希望对你有一定的参考价值。
函数介绍
函数的作用
解决以下问题:
- 代码的组织结构不清晰,可读性差
- 遇到重复的功能只能重复编写实现代码,代码冗余
- 功能需要扩展时,需要找出所有实现该功能的地方修改之,无法统一管理且维护难度极大
函数的定义
函数的意义就是一个功能模块,用来使用,下次再用的时候只需要拿来就可以使用,减少重复的代码编写的过程,但是用之前需要先造好
函数的分类
内置函数
这种函数是Python自带的,在所有情况下都可以随时调用
自定义函数
自己定制的特殊函数,只有在自己有需要的时候,但是内置函数无法解决问题的时候来进行自定制
定义函数
如何自定义函数
# 语法
def 函数名称(参数1,参数2,参数3,....):
"""注释"""
执行语句
return 返回值
# 函数名要能体现具体的功能
# 例子
def number_sum(a, b):
c = a + b
return c
print(number_sum(1, 3))
# 4
函数的使用规则:先定义,再调用
未定义就调用(报错NameError)
# 测试一
print(number_sum(1, 3))
def number_sum(a, b):
c = a + b
return c
# NameError: name ‘number_sum‘ is not defined
# 测试二
def a():
print("from a")
def b():
print("from b")
a()
b()
# from b
# from a
# 测试三
def a():
print("from a")
b()
def b():
print("from b")
a()
# 此时会运行吗?
# from a
# from b
‘‘‘
总结:
1、原则是先定义后调用
2、但是要注意调用的位置,区分定义阶段和调用的阶段(何时何地)
‘‘‘
def a():
print("from a")
b()
def b():
print("from b")
#以上是定义阶段
a()
#调用阶段
#在调用阶段时上述函数已经被定义所以不会报错
函数在定义阶段干了什么?
只检测语法,不执行代码
也就说,语法错误在函数定义阶段就会检测出来,而代码的逻辑错误只有在执行时才会知道
定义函数的三种形式
没有参数
# 无参:定义时无参,意味着调用时也无需传入参数
def out():
print(‘输出‘)
out()
# 输出
有参数
# 有参:定义时有参,意味着调用时则必须传入参数
def out(who):
print("输出%s" % (who))
out(‘justin‘)
# 输出justin
空函数:设计代码结构
def auth(user, password):
‘‘‘
:param user: 用户名
:param password: 密码
:return: 认证结果
‘‘‘
pass
....
#程序的设计立体可见
调用函数以及返回值
调用函数
函数的调用:函数名加括号
- 先找到名字
- 根据名字调用代码
函数返回值
- 无return->None
- return 1个值->返回1个值
- return 逗号,分隔多个值->元组
# 什么时候该有返回值?
‘‘‘
调用函数,经过一系列的操作,最后要拿到一个明确的结果,则必须要有返回值
通常有参函数需要有返回值,输入参数,经过计算,得到一个最终的结果
‘‘‘
# 什么时候不需要有返回值?
‘‘‘
调用函数,仅仅只是执行一系列的操作,最后不需要得到什么结果,则无需有返回值
通常无参函数不需要有返回值
‘‘‘
调用函数的三种形式
- 语句形式:函数名()
- 表达式形式:3*len(‘hello‘)
- 当中另外一个函数的参数:range(len(‘hello‘))
函数的参数
形参与实参
形参与实参是什么?
形参(形式参数):指的是在定义函数时,括号内定义的参数,形参其实就变量名
实参(实际参数):指的是在调用函数时,括号内传入的值,实参其实就变量的值
#x,y是形参
def func(x,y): #x=10,y=11
print(x)
print(y)
#10,11是实参
func(10,11)
注意
- 实参值(变量的值)与形参(变量名)的绑定关系只在函数调用时才会生效/绑定
- 实参值(变量的值)与形参(变量名)的绑定关系在函数调用结束后就立刻解除绑定
位置参数
位置即顺序,位置参参数指的就是按照从左到右的顺序依次定义的参数
1、在定义函数时,按照位置定义的形参,称为位置形参
def foo(x,y,z):
print(x,y,z)
#注意:
#位置形参的特性是:在调用函数时必须为其传值,而且多一个不行,少一个也不行
# foo(1,2)
#TypeError: foo() missing 1 required positional argument: ‘z‘
# foo(1,2,3,4)
#TypeError: foo() takes 3 positional arguments but 4 were given
2、在调用函数时,按照位置定义的实参,称为位置实参
#注意:位置实参会与形参一一对应
foo(1,3,2)
关键字参数
在调用函数时,按照key=value的形式定义的实参,称为关键字参数
def foo(x,y,z): #x=1,y=2,z=3
print(x,y,z)
注意:
- 相当于指名道姓地为形参传值,意味着即便是不按照顺序定义,仍然能为指定的参数传值
foo(2,1,3)
foo(y=2,x=1,z=3)
foo(z=2,aaaa=1)
- 在调用函数时,位置实参与关键字实参可以混合使用,但必须
- 必须遵循形参的规则
# foo(1,z=3,y=2)
# foo(1,z=3)
- 不能为同一个形参重复传值
# foo(1,x=1,y=3,z=2)
- 位置实参必须放到关键字实参的前面
# foo(y=3,z=2,1)
默认参数
在定义阶段,已经为某个形参赋值,那么该形参就称为默认参数
注意:
- 定义阶段已经有值,意味着调用阶段可以不传值
# def register(name,age,sex=‘male‘):
# print(name,age,sex)
# register(‘justin‘,18,)
# register(‘toy‘,73,‘female‘)
# register(‘lxx‘,84,)
# register(‘xxx‘,84)
# register(‘yy‘,84)
- 位置形参必须在默认参数的前面
# def func(y=1,x): #错误
# pass
- 默认参数的值只在定义阶段赋值一次,也就是说默认参数的值再定义阶段就固定死了
# m=10
# def foo(x,y=m):
# print(x,y)
#
# m=‘aaa‘
# foo(1)
# foo(1,11)
- 记住:默认参数的值应该设置为不可变类型
def register(name,hobby,l=[]): # name=‘justin‘,hobby=‘play‘
l.append(hobby) # l=[‘play‘]
print(name,l) # justin [‘play‘]
register(‘justin‘,‘play‘) # justin [‘play‘]
register(‘bingo‘,‘read‘) # bingo [‘read‘]
register(‘toy‘,‘music‘) # toy [‘music‘]
register(‘justin‘,‘play‘,[]) # justin [‘play‘]
register(‘bingo‘,‘read‘,[]) # bingo [‘read‘]
register(‘toy‘,‘music‘,[]) # toy [‘music‘]
def register(name,hobby,l=None):
if l is None:
l=[]
l.append(hobby) # l=[‘play‘]
print(name,l) # lxx [‘play‘]
register(‘lxx‘,‘play‘) # lxx [‘play‘]
register(‘justin‘,‘read‘) # justin [‘read‘]
register(‘new‘,‘music‘) # new [‘music‘]
应用:
- 对于经常需要变化的值,需要将对应的形参定义成位置形参
- 对于大多数情况值都一样的情况,需要将对应的形参定义成默认形参
可变长参数(*args,**kwargs)
可变长指的是实参值的个数不固定
而实参有按位置和按关键字两种形式定义,针对这两种形式的可变长,形参对应有两种解决方案来完整地存放它们,分别是*args,**kwargs
1、*args(多传入的变成元组)
- 情况一
def foo(x, y, *args):
# *args会把多传入的参数变成一个元组的形式
print(x, y)
print(args)
foo(1, 2, 3, 4, 5)
# 输出
# 1 2
# (3,4,5)
- 情况二
def foo(x, y, *args):
print(x, y, args)
foo(‘qiaofu‘, 20)
# 输出
# qiaofu 20 () #后面这个()就是args,只是因为没有传值多以为空
- 情况三
# 相当于解开*的传值方式
def foo(x, y, *args):
print(x, y)
print(args)
foo(1, 2, *[3, 4, 5])
# 1 2
# (3,4,5)
# 或者
def foo(x, y, z):
print(x, y, z)
foo(*[1, 2, 3])
# 1 2 3
2、**kwargs(多余的关键字参数当做字典)
- 情况一
def foo(x, y, **kwargs):
# **kwargs会把多传入的参数变成一个dict形式
print(x, y)
print(kwargs)
foo(1, y=2, a=1, b=2, c=3)
# 结果
# 1 2
# {‘a‘: 1, ‘b‘: 2, ‘c‘: 3}
- 情况二
def foo(x, y, **kwargs):
print(x, y)
print(kwargs)
foo(1, y=2, **{‘a‘: 1, ‘b‘: 2, ‘c‘: 3}) # 解开**的情况
# 结果
# 1 2
# {‘a‘: 1, ‘b‘: 2, ‘c‘: 3}
- 情况三
def foo(x, y, *args, **kwargs):
print(x, y, args, kwargs)
foo(1, 2, 3, 4,name=‘qiaofu‘,age = 20)
# 结果
# 1 2 (3,4) {‘name‘:‘qiaofu‘,‘age‘:20} #如果不传入键值类型参数那么会输出空的{}
- 情况四
def foo(x, y, z):
print(x, y, z)
foo(**{‘z‘: 1, ‘x‘: 2, ‘y‘: 3})
# 结果
# 2 3 1
3、混合使用*args+**kwargs
def foo(x, y):
print(x, y)
def wrapper(*args, **kwargs):
print(‘====>‘)
foo(*args, **kwargs)
# 相当于原封不动的传进来
命名关键字参数
*后定义的参数,必须被传值(有默认值的除外),且必须按照关键字实参的形式传递可以保证,传入的参数中一定包含某些关键字
def foo(x, y, *args, a=1, b, **kwargs):
print(x, y)
print(args)
print(a)
print(b)
print(kwargs)
foo(1, 2, 3, 4, 5, b=3, c=4, d=5)
# 结果:
# 1
# 2
# (3, 4, 5)
# 1
# 3
# {‘c‘: 4, ‘d‘: 5}
函数练习一
题目
# 1、写函数,用户传入修改的文件名,与要修改的内容,执行函数,完成批了修改操作
#
# 2、写函数,计算传入字符串中【数字】、【字母】、【空格] 以及 【其他】的个数
#
# 3、写函数,判断用户传入的对象(字符串、列表、元组)长度是否大于5。
#
# 4、写函数,检查传入列表的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者。
#
# 5、写函数,检查获取传入列表或元组对象的所有奇数位索引对应的元素,并将其作为新列表返回给调用者。
#
# 6、写函数,检查字典的每一个value的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者。
#
# dic = {"k1": "v1v1", "k2": [11,22,33,44]}
#
# PS:字典中的value只能是字符串或列表
答案
# 题目一
def modify_file(filename, old, new):
import os
with open(filename, ‘r‘, encoding=‘utf-8‘) as read_f, open(‘.bak.swap‘, ‘w‘, encoding=‘utf-8‘) as write_f:
for line in read_f:
if old in line:
line = line.replace(old, new)
write_f.write(line)
os.remove(filename)
os.rename(‘.bak.swap‘, filename)
modify_file(‘/Users/jieli/PycharmProjects/爬虫/a.txt‘, ‘alex‘, ‘SB‘)
# 题目二
def check_str(msg):
res = {
‘num‘: 0,
‘string‘: 0,
‘space‘: 0,
‘other‘: 0,
}
for s in msg:
if s.isdigit():
res[‘num‘] += 1
elif s.isalpha():
res[‘string‘] += 1
elif s.isspace():
res[‘space‘] += 1
else:
res[‘other‘] += 1
return res
res = check_str(‘hello name:aSB passowrd:alex3714‘)
print(res)
# 题目三
def check_len(obj):
if len(obj) > 5:
print(‘ture‘)
else:
print(‘false‘)
obj = input()
check_len(obj)
# 题目四
def func1(seq):
if len(seq) > 2:
seq = seq[0:2]
return seq
print(func1([1, 2, 3, 4]))
# 题目五
def func2(seq):
return seq[::2]
print(func2([1, 2, 3, 4, 5, 6, 7]))
# 题目六
def func3(dic):
d = {}
for k, v in dic.items():
if len(v) > 2:
d[k] = v[0:2]
return d
print(func3({‘k1‘: ‘abcdef‘, ‘k2‘: [1, 2, 3, 4], ‘k3‘: (‘a‘, ‘b‘, ‘c‘)}))
函数对象
函数可以当作数据传递
- 可以被引用
- 可以当作参数传递
- 返回值可以是函数
- 可以当作容器类型的元素
优雅的取代多分支的if
def foo():
print(‘foo‘)
def bar():
print(‘bar‘)
dic={
‘foo‘:foo, #如果值是函数名不加引号,否则需要加引号表示字符串
‘bar‘:bar,
}
while True:
choice=input(‘>>: ‘).strip()
if choice in dic:
dic[choice]()
嵌套函数
函数的嵌套调用
def max(x,y):
return x if x > y else y
def max4(a,b,c,d):
res1=max(a,b)
res2=max(res1,c)
res3=max(res2,d)
return res3
print(max4(1,2,3,4))
函数的嵌套定义
def f1():
def f2():
def f3():
print(‘from f3‘)
f3()
f2()
f1()
f3() #报错,为何?名称空间与作用域
名称空间和作用域
什么是名称空间
名称空间:存放名字的地方,三种名称空间,(之前遗留的问题x=1,1存放于内存中,那名字x存放在哪里呢?名称空间正是存放名字x与1绑定关系的地方)
名称空间的加载顺序
# cmd中运行
python test.py
- python解释器先启动,因而首先加载的是:内置名称空间
- 执行test.py文件,然后以文件为基础,加载全局名称空间
- 在执行文件的过程中如果调用函数,则临时产生局部名称空间
名字的查找顺序
局部名称空间--->全局名称空间--->内置名称空间
需要注意的是:在全局无法查看局部的,在局部可以查看全局的,如下示例
# max=1
def f1():
# max=2
def f2():
# max=3
print(max)
f2()
f1()
print(max)
作用域
1、作用域即范围
- 全局范围(内置名称空间与全局名称空间属于该范围):全局存活,全局有效
- 局部范围(局部名称空间属于该范围):临时存活,局部有效
2、作用域关系是在函数定义阶段就已经固定的,与函数的调用位置无关,如下
x=1
def f1():
def f2():
print(x)
return f2
x=100
def f3(func):
x=2
func()
x=10000
f3(f1())
3、查看作用域:globals(),locals()
# LEGB 代表名字查找顺序:
# locals -> enclosing function -> globals -> __builtins__
# locals 是函数内的名字空间,包括局部变量和形参
# enclosing 外部嵌套函数的名字空间(闭包中常见)
# globals 全局变量,函数定义所在模块的名字空间
# builtins 内置模块的名字空间
global与nonlocal关键字
global适用于函数内部修改全局变量的值
nonlocal适用于嵌套函数中内部函数修改外部变量的值
如果没有使用以上关键字,对全局变量或者外部变量进行修改,python会默认将全局变量隐藏起来
# 例1:
def outside():
var = 5
def inside():
var = 3
print(var)
inside()
outside()
# 3
# 例2:
def outside():
var = 5
def inside():
# inside函数改变了var所以python将var隐藏了起来,这里的print找不到var因而报错。
print(var)
var = 3
inside()
outside()
# 例1不会显示报错,但是例2会
# UnboundLocalError: local variable ‘var‘ referenced before assignment
高阶函数
变量可以指向函数,函数的参数能接受变量,那么一个函数就可以接受另一个函数作为参数,这种函数就称为高阶函数
ef f(something):
return something
def add(x,y,z):
return f(x)+f(y)
res = add(3, -6,abs)
print(res)
# -3
‘‘‘
# 只需要满足以下任意一个条件都可以称这个函数为高阶函数
# 1.接受一个或多个函数作为输出
# 2.return返回另一个函数
‘‘‘
递归
递归调用的定义
递归调用是函数嵌套调用的一种特殊形式,函数在调用时,直接或间接调用了自身,就是递归调用
直接调用本身
def f1():
print(‘from f1‘)
f1()
f1()
间接调用本身
def f1():
print(‘from f1‘)
f2()
def f2():
print(‘from f2‘)
f1()
f1()
-
调用函数会产生局部的名称空间,占用内存,因为上述这种调用会无需调用本身,python解释器的内存管理机制为了防止其无限制占用内存,对函数的递归调用做了最大的层级限制
-
可以修改递归最大深度
import sys
sys.getrecursionlimit()
sys.setrecursionlimit(2000)
def f1(n):
print(‘from f1‘,n)
f1(n+1)
f1(1)
‘‘‘
虽然可以设置,但是因为不是尾递归,仍然要保存栈,内存大小一定,不可能
无限递归,而且无限制地递归调用本身是毫无意义的,递归应该分为两个明确
的阶段,回溯与递推
‘‘‘
递推、回溯
1、递归调用应该包含两个明确的阶段:回溯,递推
- 回溯:
从外向里一层一层递归调用下去,回溯阶段必须要有一个明确地结束条件,每进入下一次递归时,问题的规模都应该有所减少(否则,单纯地重复调用自身是毫无意义的)
- 递推:
从里向外一层一层结束递归
2、示例
# salary(5)=salary(4)+300
# salary(4)=salary(3)+300
# salary(3)=salary(2)+300
# salary(2)=salary(1)+300
# salary(1)=100
#
# salary(n)=salary(n-1)+300 n>1
# salary(1) =100 n=1
def salary(n):
if n == 1:
return 100
return salary(n-1)+300
print(salary(5))
递归效率低(无尾递归优化)
python中的递归:
python中的递归效率低,需要在进入下一次递归时保留当前的状态,
在其他语言中可以有解决方法:尾递归优化,即在函数的最后一步(而非最后一行)调用自己,但是python又没有尾递归,且对递归层级做了限制
总结递归的使用
- 必须有一个明确的结束条件
- 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
- 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)
二分法(递归应用)
想从一个按照从小到大排列的数字列表中找到指定的数字,遍历的效率太低,用二分法(算法的一种,算法是解决问题的方法)可以极大低缩小问题规模
类似in的效果
l=[1,2,10,30,33,99,101,200,301,311,402,403,500,900,1000] #从小到大排列的数字列表
def search(n,l):
print(l)
if len(l) == 0:
print(‘not exists‘)
return
mid_index=len(l) // 2
if n > l[mid_index]:
#in the right
l=l[mid_index+1:]
search(n,l)
elif n < l[mid_index]:
#in the left
l=l[:mid_index]
search(n,l)
else:
print(‘find it‘)
search(3,l)
类似l.index(30)的效果
l=[1,2,10,30,33,99,101,200,301,402]
def search(num,l,start=0,stop=len(l)-1):
if start <= stop:
mid=start+(stop-start)//2
print(‘start:[%s] stop:[%s] mid:[%s] mid_val:[%s]‘ %(start,stop,mid,l[mid]))
if num > l[mid]:
start=mid+1
elif num < l[mid]:
stop=mid-1
else:
print(‘find it‘,mid)
return
search(num,l,start,stop)
else: #如果stop > start则意味着列表实际上已经全部切完,即切为空
print(‘not exists‘)
return
search(301,l)
以上是关于函数-入门的主要内容,如果未能解决你的问题,请参考以下文章
Cg入门20:Fragment shader - 片段级模型动态变色(实现汽车动态换漆)