归纳整理XLOOKUP函数用法(xlookup的使用方法)
497
2022-05-30
10 函数
函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
函数能提高应用的模块性,和代码的重复利用率。你已经知道Python提供了许多内建函数,比如print()。但你也可以自己创建函数,这被叫做用户自定义函数。
10.1 定义函数
语法:
def 函数名(参数列表): 函数体
例:
# 定义一个函数,能够完成打印信息的功能 def printInfo(): print ('------------------------------------') print (' 人生苦短,我用Python') print ('------------------------------------')
定义函数的规则:
函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()。
任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
函数内容以冒号 : 起始,并且缩进。
return [表达式] 结束函数,选择性地返回一个值给调用方,不带表达式的 return 相当于返回 None。
10.2 调用函数
定义了函数之后,就相当于有了一个具有某些功能的代码,想要让这些代码能够执行,需要调用它
调用函数很简单的,通过 函数名() 即可完成调用,例:
# 定义完函数后,函数是不会自动执行的,需要调用它才可以 printInfo()
10.3 函数的注释
在函数定义下面使用'''进行注释,使用help函数可以查看函数的注释。
def test(a1, a2): ''' 用来完成对2个数求和" :param a1:第一个参数 :param a2:第二个参数 :return:无 ''' print("%d" % (a1 + a2)) print(help(test))
运行结果:
test(a1, a2) 用来完成对2个数求和" :param a1:第一个参数 :param a2:第二个参数 :return:无 None
10.4 函数的参数
10.4.1 参数的定义
参数分形参、实参
形参:函数定义时括号内的参数
实参:函数调用时括号内的参数
形参相当于变量,实参相当于变量的值。
def add2num(a, b): c = a + b print(c) add2num(11, 22) # 调用带有参数的函数时,需要在小括号中,传递数据
a,b为形参;11,12为实参
形参:
只在被调用时,才分配内存单元。调用结束,立刻释放所分配的内存。
只在函数内部有效。
实参:
可以是:常量、变量、表达式、函数。
进行函数调用时,实参必须是确定的值。
10.4.2 参数的分类
Python的函数除了正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码。
1)位置参数
位置形参:函数定义时,从左往右写的参数,比如上面的 a, b
位置实参:函数调用时,从左往右写的参数, 比如上面的 11,12
位置形参定义多少个,调用时位置实参必须写上多少个,多一个少一个都不行。
2)关键参数:
正常情况下,给函数传参数,要按顺序。如果不想按顺序,就用关键参数。
指定参数名的参数,就叫做关键参数。
函数调用时:func(a=1, b=2), 这种指定了参数名的参数,就是关键参数。
调用函数时,关键参数可以和位置参数一起用,但是关键参数必须在位置参数的后面。不然会报错。
例:
def add2num(a, b): c = a + b print(c) #正确的调用方式 add2num(11, b=22) # 调用带有参数的函数时,需要在小括号中,传递数据 add2num(a=11, b=22) add2num(b=11, a=22) # 错误的调用方式。 #add2num(22, a=22) #add2num(b=11,22)
3)默认参数
函数定义时,默认参数必须在位置形参的后面。
函数调用时,指定参数名的参数,叫关键参数。
而在函数定义时,给参数名指定值的时候,这个参数叫做默认参数。
关键参数,和默认参数两个参数写法一样,区别在于:
关键参数是在函数调用时,指定实参的参数名,也可以说指定值的参数名。
默认参数是在函数定义时,指定参数名的值。
定义时,有默认参数的话,调用时,这个实参可以不写。如果实参不写的话,这个形参的参数值是他的默认值。
例:
def add2num(a, b=100): c = a + b print(c) add2num(11, b=22) # 调用带有参数的函数时,需要在小括号中,传递数据 add2num(11)
运行结果:
33 111
4 ) 动态参数:*args **kwargs
*args
针对函数定义时的*:
def func(a, b, *args):
pass
*args会接收函数调用时,传入的多余的位置实参。
*args 是一个元组
例子:
func(1, 2, 3, 4, 5, 6) 函数调用,因为函数定义时,*args前面有形参a, 形参b, *args就接收调用时多余的位置实参
a为1, b为2, *args 为: (3, 4, 5, 6),是一个元组。
针对函数调用时的 *:
func(1, 2, *[1, 2, 3, 4]) == func(1, 2, 1, 2, 3, 4)
函数调用时有*, 就应该立马将*后面的列表,元组,字符串之类的迭代器,打散成位置参数来看。
注意,如果 *后面是一个字典的话,那么打散成的位置参数就是字典的key
*可以看做是for循环。
形参中 *args 只能接收多余的位置实参,成为一个元组。不能接收关键实参。
例:
def calc(*numbers): sum = 0 for n in numbers: sum = sum + n * n return sum nums = [1, 2, 3] # 调用方式1 print(calc(nums[0], nums[1], nums[2])) #调用方式2 print(calc(*nums))
运行结果:
14 14
**kwargs:
针对函数定义时,站在形参的角度看 **:
接收多余的关键实参,成为一个字典dict。
字典的key是关键实参的变量名,字典的value是关键实参的值。
将字典交给**后面的变量名,这里是kwargs
站在实参的角度看 ** :
d = {‘x’:1, ‘y’:2, ‘z’:333}
func(**d) # 等价于func(x=1,y=2,z=333)
函数调用时,后面可以接一个字典,然后会把字典打散成关键参数的形式,也就是key=value的形式。
例:
def person(name, age, **kw): print('name:', name, 'age:', age, 'other:', kw) #调用方式1 print(person('Michael', 30)) #调用方式2 print(person('Bob', 35, city='Beijing')) #调用方式3 print(person('Adam', 45, gender='M', job='Engineer'))
运行结果:
name: Michael age: 30 other: {} None name: Bob age: 35 other: {'city': 'Beijing'} None name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'} None
5)混合参数时,参数顺序
函数定义时:
从左往右:位置形参 > *args > 默认参数 > **kwargs
位置形参 > 默认参数 > *args > **kwargs 也可以。
因为函数调用时给的实参满足了位置形参、默认参数之后,会把多余的位置实参给args。这样并不会报错。
但是 **kwargs 必须在 *args后面
默认形参必须在位置形参后面
函数调用时:
从左到右:位置实参 > *args > 关键参数 > **kwargs
因为 * args 在函数调用时,会被打散成位置实参,而关键参数必须在位置实参的后面,否则会报错。SyntaxError: positional argument follows keyword argument
*args 必须在 **kwargs后面, 否则会报语法错误:SyntaxError: iterable argument unpacking follows keyword argument unpacking
总结
Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。
默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!
要注意定义可变参数和关键字参数的语法:
*args是可变参数,args接收的是一个tuple;
**kw是关键字参数,kw接收的是一个dict。
以及调用函数时如何传入可变参数和关键字参数的语法:
可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3));
关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})。
使用*args和**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。
命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。
定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。
10.4.3 参数的传递
Python参数传递采用的肯定是“传对象引用”的方式。这种方式相当于传值和传引用的一种综合。如果函数收到的是一个可变对象(比如字典或者列表)的引用,就能修改对象的原始值--相当于通过“传引用”来传递对象。如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用,就不能直接修改原始对象--相当于通过“传值’来传递对象。
例:
attrList = [0, 1, 2, 3, 4, 5] print("改变前————————————————————————————————") print(attrList) def func(i, attrList): attrList[i] = attrList[i] * 10 print("改变后————————————————————————————————") for i in range(3): func(i, attrList) print(attrList)
运行结果:
改变前———————————————————————————————— [0, 1, 2, 3, 4, 5] 改变后———————————————————————————————— [0, 10, 20, 3, 4, 5]
可以看到,List发生了改变,如果传入不可变的元素呢?
a = 10 print("改变前————————————————————————————————") print(a) def func(c): print(id(c)) c += 2 print(id(c)) print("func函数的c值:", c) print(id(a)) func(a) print("改变后————————————————————————————————") print(a) print(id(a))
运行结果:
改变前———————————————————————————————— 10 140724141828160 140724141828160 140724141828224 func函数的c值: 12 改变后———————————————————————————————— 10 140724141828160
将a变量作为参数传递给了func函数,传递了a的一个引用,把a的地址传递过去了,所以在函数内获取的变量c的地址跟变量a的地址是一样的,但是在函数内,对c进行赋值运算,c的值从10变成了12,实际上10和12所占的内存空间都还是存在的,赋值运算后,c指向12所在的内存。而a仍然指向10所在的内存,所以后面打印a,其值还是10.
10.5 函数的返回值
想要在函数中把结果返回给调用者,需要在函数中使用return,例:
def add2num(a, b): c = a + b return c print(add2num(1, 2))
可以返回多个返回值,例:
def divid(a, b): shang = a // b yushu = a % b return shang, yushu sh, yu = divid(5, 2)
10.6 局部变量与全局变量
局部变量就是定义在一个函数体内部的变量
全局变量是定义在外面的变量
例:
a = 1 def f(): b = 2
其中a就是全局变量,而b是局部变量。局部变量只在函数体内部有效,出了函数体,外面是访问不到的,而全局变量则对下面的代码都有效。
全局变量可以直接在函数体内容部使用的,你可以直接访问,但是注意的是,如果对于不可变类型的数据,如果在函数里面进行了赋值操作,则对外面的全局变量不产生影响,因为相当于新建了一个局部变量,只是名字和全局一样,而对于可变类型,如果使用赋值语句,同样对外部不产生影响,但是使用方法的话就会对外部产生影响。
g_b = 3 g_l1 = [1, 2] g_l2 = [1, 2, 3] def t1(): g_b = 2 g_l1 = [] g_l2.append(7) t1() print(g_b, g_l1, g_l2)
运行结果:3 [1, 2] [1, 2, 3, 7]
global关键字
上面说到,如果使用的是赋值语句,在函数内部相当于新建了一个变量,并且重新给了指向,但是有时候我们想把这个变量就是外部的那个全局变量,在赋值操作的时候,就是对全局变量给了重新的指向,这个时候可以通过global关键字表示我在函数里面的这个变量是使用的全局那个。使用方法如下:
g_b = 3 def t1(): global g_b g_b = 2 t1() print(g_b)
运行结果:2
这个时候你会发现全局变量g_b也重新指向了,这是因为global g_b表示指定了函数中的g_b就是外面的那个。
10.7 递归函数
如果一个函数在内部调用自身本身,这个函数就是递归函数。例:
def fact(n): if n == 1: return 1 return n * fact(n - 1) print(fact(5))
运行结果:120
可以根据函数定义看到计算过程如下:
===> 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)
可以看到,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.8 匿名函数
10.8.1 定义
用lambda关键词能创建小型匿名函数。这种函数得名于省略了用def声明函数的标准步骤。
lambda函数的语法只包含一个语句,如下:
lambda [arg1 [,arg2,.....argn]]:expression
如下实例:
sum = lambda a1, a2: a1 * a2 # 调用sum函数 print("Value of total : ", sum(10, 20)) print("Value of total : ", sum(20, 20))
以上实例输出结果:
Value of total : 200 Value of total : 400
Lambda函数能接收任何数量的参数但只能返回一个表达式的值
匿名函数不能直接调用print,因为lambda需要一个表达式
10.8.2 应用场合
例1:自己定义函数作为参数传递
def fun(a, b, opt): print("a =", a) print("b =", b) print("result =", opt(a, b)) fun(1, 2, lambda x, y: x + y)
运行结果:
a = 1 b = 2 result = 3
例2:作为内置函数的参数
想一想,下面的数据如何指定按age或name排序?
stus = [ {"name":"zhangsan", "age":18}, {"name":"lisi", "age":19}, {"name":"wangwu", "age":17} ]
按name排序:
stus.sort(key=lambda x: x['name']) print(stus)
运行结果:
[{'name': 'lisi', 'age': 19}, {'name': 'wangwu', 'age': 17}, {'name': 'zhangsan', 'age': 18}]
按age排序:
stus.sort(key=lambda x: x['age']) print(stus)
运行结果:
[{'name': 'wangwu', 'age': 17}, {'name': 'zhangsan', 'age': 18}, {'name': 'lisi', 'age': 19}]
11 文件操作
11.1 打开与关闭
11.1.1 打开文件
在python,使用open函数,可以打开一个已经存在的文件,或者创建一个新文件
open(文件名,访问模式)
示例如下:
f = open('test.txt', 'w')
说明:
11.1.2 关闭文件
close( )
示例如下:
# 新建一个文件,文件名为:test.txt f = open('test.txt', 'w') # 关闭这个文件 f.close()
11.2 文件的读写
11.2.1 写数据(write)
使用write()可以完成向文件写入数据,例:
f = open('test.txt', 'w') f.write('hello 大家好, 我是AI浩') f.close()
运行现象:
注意:
如果文件不存在那么创建,如果存在那么就先清空,然后写入数据
11.2.2 读数据(read)
使用read(num)可以从文件中读取数据,num表示要从文件中读取的数据的长度(单位是字节),如果没有传入num,那么就表示读取文件中所有的数据,例:
f = open('test.txt', 'r') content = f.read(5) print(content) print("-"*30) content = f.read() print(content) f.close()
运行结果:
hello ------------------------------ 大家好, 我是AI浩
注意:
如果open是打开一个文件,那么可以不用谢打开的模式,即只写 open('test.txt')
如果使用读了多次,那么后面读取的数据是从上次读完后的位置开始的
11.2.3 读数据(readlines)
就像read没有参数时一样,readlines可以按照行的方式把整个文件中的内容进行一次性读取,并且返回的是一个列表,其中每一行的数据为一个元素
#coding=utf-8 f = open('test.txt', 'r') content = f.readlines() print(type(content)) i=1 for temp in content: print("%d:%s"%(i, temp)) i+=1 f.close()
运行结果:
11.2.4 读数据(readline)
#coding=utf-8 f = open('test.txt', 'r') content = f.readline() print("1:%s"%content) content = f.readline() print("2:%s"%content) f.close()
运行结果:
1:hello 大家好, 我是AI浩 2:asfsifhiudh
11.3 文件的常用操作
11.3.1 获取当前读写的位置
在读写文件的过程中,如果想知道当前的位置,可以使用tell()来获取,例:
# 打开一个已经存在的文件 f = open("test.txt", "r") str = f.read(3) print("读取的数据是 : ", str) # 查找当前位置 position = f.tell() print("当前文件位置 : ", position) str = f.read(3) print("读取的数据是 : ", str) # 查找当前位置 position = f.tell() print("当前文件位置 : ", position) f.close()
运行结果:
读取的数据是 : hel 当前文件位置 : 3 读取的数据是 : lo 当前文件位置 : 6
11.3.2 定位到某个位置
如果在读写文件的过程中,需要从另外一个位置进行操作的话,可以使用seek()
seek(offset, from)有2个参数
offset:偏移量
from:方向
0:表示文件开头
1:表示当前位置
2:表示文件末尾
例1:把位置设置为:从文件开头,偏移5个字节
# 打开一个已经存在的文件 f = open("test.txt", "r") str = f.read(30) print("读取的数据是 : ", str) # 查找当前位置 position = f.tell() print("当前文件位置 : ", position) # 重新设置位置 f.seek(5, 0) # 查找当前位置 position = f.tell() print("当前文件位置 : ", position) f.close()
例2:把位置设置为:离文件末尾,3字节处
# 打开一个已经存在的文件 f = open("test.txt", "rb") print("读取的数据是 : ", str) position = f.tell() print("当前文件位置 : ", position) # 重新设置位置 f.seek(-3, 2) # 读取到的数据为:文件最后3个字节数据 str = f.read() print("读取的数据是 : ", str) f.close()
运行结果:
读取的数据是 :
11.3.3 文件重命名
os模块中的rename()可以完成对文件的重命名操作
rename(需要修改的文件名, 新的文件名)
import os os.rename("test.txt", "test-1.txt")
11.3.4 删除文件
os模块中的remove()可以完成对文件的删除操作
remove(待删除的文件名)
import os os.remove("test.txt")
11.4 文件夹的相关操作
实际开发中,有时需要用程序的方式对文件夹进行一定的操作,比如创建、删除等
就像对文件操作需要os模块一样,如果要操作文件夹,同样需要os模块
11.4.1 创建文件夹
import os os.mkdir("aa")
11.4.2 获取当前目录
import os os.getcwd()
11.4.3 改变默认目录
import os os.chdir("../")
11.4.4 获取目录列表
import os os.listdir("./")
11.4.5 删除文件夹
import os os.rmdir("张三")
11.4.6 检测文件夹是否存在
import os if not os.path.exists(path): os.makedirs(path)
11.4.7 创建多级文件夹
import os os.makedirs(opDir)
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。