爆肝六万字整理Python基础,快速入门python的首选(下)(Python干货)

网友投稿 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)。

解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。

爆肝六万字整理的python基础,快速入门python的首选(下)(Python干货)

尾递归是指,在函数返回的时候,调用自身本身,并且,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()

运行结果:

1:hello 大家好, 我是AI浩

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()

运行结果:

读取的数据是 : 当前文件位置 : 0 读取的数据是 : b'ddf'

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小时内删除侵权内容。

上一篇:使用 scikit-learn 的 train_test_split() 拆分数据集(使用驱动器u盘之前需要格式化)
下一篇:excel做利润表的方法步骤详解(excel中利润表怎么做)
相关文章