Python函数与异常
1. 函数基础
1.1 函数的参数
1)参数说明
形参就是一个变量名,实参就是值,传参就是在赋值。
- 形参:写在函数声明的位置的变量叫形参
- 实参:在函数调用的时候给函数传递的值(实际执行的时候给函数传递的信息)
- 传参:给函数传递信息的时候将实际参数交给形式参数的过程被称为传参
2)参数的分类
- 位置参数、关键字参数
def my_func1(x, y, z): print(x+y+z, "计算结束") my_func1(1, 2, 3) # 位置参数写法 my_func1(x=4, y=5, z=6) # 关键字参数写法 my_func1(7, 8,z=9) # 位置参数要写在关键字参数前面
- 默认参数
def my_func2(a, b=2): # 默认参数,参数b的值可以不传,默认为2,如果传了值,则b的值会覆盖默认值 print("计算结果为:", a+b) my_func2(2) my_func2(1, 9)
- 非定长参数
def my_func3(*args, **kwargs): # 参数中 *args表示列表或元组, **kwargs表示字典 args保存为元组的形式 print(args[0]) print(kwargs["name"]) my_func3(1, 2, 3, name="hgzero", age=21, addr="China") my_func3(*[9, 8, 7], **{"name": "wuzhihao", "age": 22, "addr": "America"})
1.2 函数的返回值
1)返回值
- 函数若没有使用return定义返回值,则返回None。
- 若返回值只有一个,则将返回对象直接返回。
- 若返回值有多个,则以元组的形式将返回值返回。
def func1(): print("hello world") def func2(): return "wuzhihao" def func3(): return 1, 2, 3, ["hgzero", "hg"] print(func1()) # 若无返回值,则返回None print(func2()) # 若返回值只有一个,则将返回对象返回 print(func3()) # 若返回值有多个,则以元组的形式将返回值返回
1.3 函数的注释
在函数中对函数的参数、返回值进行注释说明。
def eat(food,drink): ''' 这里描述这个函数是做什么的.例如这函数eat就是吃 :param food: food这个参数是什么意思 :param drink: drink这个参数是什么意思 :return: 执行完这个函数想要返回给调用者什么东西 ''' print(food,drink) eat('麻辣烫','肯德基')
在外部查看函数的注释 函数名._doc_
print(eat.__doc__) #函数名.__doc__ 结果: 这里描述这个函数是做什么的.例如这函数eat就是吃 :param food: food这个参数是什么意思 :param drink: drink这个参数是什么意思 :return: 执行完这个函数想要返回给调用者什么东西
1.4 名称空间&作用域
1)什么是名称空间(命名空间)
在python解释器开始执行之后,会在内存中开辟一块空间,每当遇到一个变量的时候,就会把变量名和值之间的关系记录下来。
但是当遇到函数定义的时候,解释器只是把函数名读入内存,表示这个函数存在了,至于函数内部的变量和逻辑,解释器是不关心的。
也就是说,一开始的时候函数只是加载进来,仅此而已。只有当函数被调用和访问的时候,解释器才会根据函数内部声明的变量来进行开辟变量的内部空间。随着函数执行完毕,这些函数内部变量占用的空间也会随着函数执行完毕而被清空。
这种存放名字和值的关系的空间就被称为:命名空间;变量在存储的时候就是存储在这片空间中的。
2)命名空间的分类
- 全局命名空间:直接在py文件中,函数外声明的变量都属于全局命名空间;
- 局部命名空间:在函数中声明的变量会放在局部命名空间中;
- 内置命名空间:存放python解释器为我们提供的名字(list,tuple,str,int 这些都是内置命名空间)
3)加载和取值顺序
- 加载顺序
- 内置命名空间
- 全局命名空间
- 局部命名空间(函数被执行的时候)
- 取值顺序
- 局部命名空间
- 全局命名空间
- 内置命名空间
4)作用域&命名空间
- 作用域
- 作用域就是作用范围,按照生效范围来看分为:全局作用域 和 局部作用域
- 全局作用域:包含内置命名空间和全局命名空间,在整个文件的任何位置都可以使用(遵循从上到下原则)
- 局部作用域:在函数内部可以使用
- 作用域命名空间
- 全局作用域:全局命名空间 + 内置命名空间
- 局部作用域:局部命名空间
name = "AAA" def func1(): name = "BBB" def func2(): name = "CCC" def func3(): # name = "DDD" print(name) return func3 return func2 f2 = func1() f3 = f2() f3() # 以上的调用方式等价于: func1()()() name = "hgzero" def outer(func): name = "alex" func() # 相当于执行了show函数,但show函数中调用的name变量还是调用了全局的name变量 def show(): print(name) outer(show) # 这里讲show函数的地址传递给了outer , 但并不代表outer函数和show函数之间有从属关
1.5 局部变量&全局变量
1)全局变量&global 关键字
- 如果函数中变量不定义global关键字,优先读取局部变量,当读取全局变量时,无法重新给其赋值,
- 若函数中变量加了global关键字,则变量表示的就是全局的那个变量,或者直接创建一个全局变量
- 对于可变类型(列表、字典等),在函数内部可以对其进行内部操作(增、删、改)
- 最好将全局变量大写 , 局部变量小写
name = "hgzero" # 定义一个全局的name my_name = ["hgzero", "wuzhihao", "hg", "wu"] def func1(): global name # 指明这里的name就是全局的那个name name = "wuzhihao" print(name) def func2(): global age # 直接定义一个全局的age age = 22 def func3(): my_name.append("hello") # 在函数内部对可变类型的操作会影响到全局 print(my_name) my_name.pop(2) func1() func2() func3() print(name) print(age) print(my_name)
2)nolocal 关键字
nolocal 只修改上一层变量,如果上一层中没有变量就往上找一层,只会找到函数的最外层,不会找到全局进行修改。
like = "AAA" def my_func1(): like = "BBB" def my_func2(): nonlocal like # nonlocal 关键字代指它的上一级变量 like = "CCC" my_func2() print(like) print(like) my_func1() print(like)
1.6 函数的递归
1)递归说明
- 递归一定要设置一个截止条件,且下一次递归的规模要比上一次有所减少。
- python在递归的深度上做了限制(1000),且python的递归效率低且没什么用。
2)递归的尾部优化
递归的尾部优化:
在最后一步进入递归可以不用保存前一步的状态(调用栈)------> 理论
为什么要实现尾部递归优化:
因为函数调用是通过栈的形式实现的,每当进入一个函数调用,那么栈就会增加一层栈帧,每当函数返回,栈就会减少一层栈帧(为了保留上一层的状态等信息)。
由于栈的大小不是无限的,所以,当递归的层数过多时,会导致栈溢出。
尾部递归优化的实现方法:(理论上)
只是理论上的方法,因为python解释器没有对此进行优化。实际上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归也ok。
尾递归是指,在函数返回的时候,调用自己本身,并且,return语句不能包含表达式,这样,编译器或者解释器就可以吧尾递归做优化,使得递归本身无论调用了多少次,都只占用一个栈帧,这样就不会出现栈溢出的情况了。
3)递归使用示例
# 递归计算1+2+3···+99+100的值 def func1(num): if num == 1: return num res = num + func1(num - 1) return res sum = func1(100) print(sum) # 递归计算 1+(1*2)*(1*2*3)+(1*2*3*4)+(1*2*3*4*5)+···+(1*2*3*4···*8*9*10) 的值 def func2(num): mal = 1 if num == 1: return num for i in range(1, num+1): mal = i * mal res = mal + func2(num-1) return res sum1 = func2(10) print(sum1)
1.7 匿名函数
- lambda表达式
# 用普通函数的方式实现两个数字相加 def sum(x, y): return x+y s = sum(1,2) print(s) # 用匿名函数的方式实现两个数字相加 lam = lambda x, y: x + y # 冒号前面的为参数,冒号后面的为对参数的处理以及返回值(返回的是匿名函数的内存地址) sum = lam(2, 3) print(sum)
2. 高阶函数
2.1 高阶函数说明
1)函数式编程:
- 不用变量保存状态,不修改变量,若需要修改变量,则直接在return中返回并修改。
2)高阶函数:
- 函数接受的参数是函数名
- 函数返回值中包含函数
2.2 高阶函数 map()
- 作用:映射函数
- 语法:map(function, iterable)
- 可以对可迭代对象中的每一个元素进行映射,分别执行function
num_li = [1, 2, 3, 4, 5] add_one = map(lambda x: x+1, num_li) # map函数的第一个参数是处理逻辑(可以为普通函数或者匿名函数),第二个参数是可迭代对象 # map函数将后面的可迭代对象迭代作为参数传递给前面的函数作出对应的处理,返回值保存为一个可迭代对象 print(add_one) # map函数返回的是一个可迭代对象 # for i in add_one: # print(i) li = list(add_one) # 可用list() 将可迭代对象转换为一个列表 print(li)
2.3 高阶函数 filter()
- 作用:筛选过滤
- 语法:filter(function, iterable)
- function用来筛选的函数,在filter中会自动的把iterable中的元素传递给function,然后根据function返回的True或者False来判断是否保留此项数据
name_list = ["xiaoming_hg", "xiaohong_hg", "xiaohua_hg", "xiaoqiang"] fi = filter(lambda x: x.endswith("hg"), name_list) # filter()函数与reduce()函数类似,分别传入函数和可迭代对象 # filter 的返回值为符合过滤条件的对象 print(fi) print(list(fi)) # for i in fi: # print(i)
2.4 高阶函数 reduce()
- reduce() 函数要从functools包中引入
# 对于后面可迭代对象中的值 ,将第一个值和第二个值进行函数操作后保存作为第一个值,再将后面的值作为第二个值,反复循环累积,得到一个终值 # reduce()函数要从functools包中引入 from functools import reduce num_list = [1, 2, 3, 4, 5] mal = reduce(lambda x, y: x+y, num_list, 100) # 第三个参数为初值 print(mal)
3. python内置函数
3.1 作用域相关
print(globals()) # globals()函数以字典的方式返回当前位置的全部全局变量 print(locals()) # locals()函数以字典的方式返回跟当前位置统一等级的局部变量
3.2 迭代器相关
## range() 方法 print(range(0, 20, 2)) # 以2为步长(每隔一个数),打印在0到20的范围内(不包括20)的值 # python2中range方法立即创建,而python3中range只在要循环迭代的时候才创建 ## iter() 方法 num = [1, 2, 3] the_iter = iter(the_num) # 也可以直接使用num.__iter__() ## next() 方法 print(next(the_iter)) # next(the_iter) -----> 调用 the_iter.__next__() # next()方法---> 调用__next__()方法
3.3 字符串类型代码的执行
1)eval()
- 执行部分字符串类型的代码,并返回最终结果
- eval() 函数的作用:
- 将字符串中的数据结构给提取出来
- 将字符串中的表达式进行计算
print(str([12, 2, 34])) expression1 = "[1, 2, 3, 4, 5]" expression2 = "1+2+3+(4*5)+9"
ev1 = eval(expression1) print(type(ev1)) ev2 = eval(expression2) print(ev2)
2)exec()
- 执行字符串类型的代码
msg = ''' def func(): print('一切都是刚刚开始') func() ''' exec(msg)
3.4 内存相关
# hash() 获取对象的hash值(int,str,bool,tuple) print(hash("wuzhihao")) # 可hash的数据类型就是不可变数据类型 # id() 获取对象的内存地址 print(id(info_list))
3.5 数学运算相关
print(abs(-2)) # abs()函数返回绝对值 print(round(3.5)) # 对数字进行四舍五入 print(divmod(10, 3)) # 将第一个参数和第二个参数相除,返回一个元组,元组中第一个值为商,第二个值为余数,如这里返回(3,1) print(bool(9)) # 判断布尔值 (空、None、零的布尔值为False,其余全都为True) print(pow(2, 3)) # pow()函数接受两个参数时表示次方,即2的3次方 print(pow(2, 3, 3)) # pow()函数接受三个参数时表示先次方,后取余,即2的3次方后,将得到的结果取余 print(sum([1, 2, 3, 4, 5])) # sum()函数将参数的可迭代对象相加(必须为数字) print(max([12,3,4])) # 寻找最大的数字 print(min([12,3,4])) # 寻找最小的数字
3.6 编码和进制相关
print(bytes("你好", encoding="utf-8").decode("utf-8")) # 编码和解码 print(chr(97)) # 基于ASCII码表进行转换(参数为数字) print(ord("a")) # 打印出字符在ASCII码表中对应的数字 print(bin(3)) # 将十进制转换为二进制,以 0b 开头 print(oct(10)) # 将十进制转换为八进制,以0o开头 print(hex(10)) # 将十进制转换为十六进制,以 0x 开头
3.7 切片&排序
1)slice() 切片
my_name = "welcome" s = slice(1, 4, 2) # 指定切片的范围及步长 1<=slice<4 步长为2 print(my_name[s]) print(s.start) # 打印起始索引 print(s.stop) # 打印终止索引 print(s.step) # 打印步长
2)sorted() 排序
- 语法:sorted(iterable, key=None, reverse=False)
- iterable 是可迭代对象
- key 指定排序规则,在sorted内部会将可迭代对象中的每一个元素传递给这个函数的参数,然后根据函数运算的结果进行排序
- reverse 是否倒叙,True倒叙,False正序
the_my_dict = [ {"name": "hgzero", "age": 25}, {"name": "hg", "age": 14}, {"name": "zero", "age": 23}, {"name": "wzh", "age": 21}, {"name": "zhihao", "age": 26}, ] print(sorted(the_my_dict, key=lambda dict: dict["age"])) # sorted()函数也有一个key的参数,为每个迭代对象要比较的内容
3.8 zip() + max() 使用技巧
# zip()函数接受两个序列类型(包括 列表、元组、字符串)的参数,并将二者一一对应成一个个元组 # 若两组参数中多了值或者少了值,以少的一方为准,多余的值舍弃 first = {"one": 1, "two": 2, "three": 3, "four": 4, "five": 5} print(list(zip("hello", [1,2,3,4,5]))) print(list(zip(first.keys(), first.values()))) # python中的max()函数 # max()函数中传入的必须为一个可迭代类型,且迭代对象之间的数据类型必须相同 the_name = ["a1", "a2", "a3", "b4"] the_dict = {"name1": "hgzero", "name2": "wuzhihao", "age": 21} the_tuple = [(1, "name"), (2, "age"), (3, "gender"), (4, "addr")] print(max(the_name)) print(max(the_dict.keys())) print(max(the_tuple)) # 利用zip和max函数找到value最大的那个键值对: my_dict = {"name": 1, "age": 2, "gender": 3, "addr": 4} print(max(zip(my_dict.values(), my_dict.keys()))) name_dict = [ {"name": "hgzero", "age": 25}, {"name": "hg", "age": 14}, {"name": "zero", "age": 23}, {"name": "wzh", "age": 21}, {"name": "zhihao", "age": 26}, ] print(max(name_dict, key=lambda dict: dict["age"])) # max()函数还接受第二个参数key,为每个迭代对象中的要比较的选项
3.9 其他
1)dir() 查看内置属性
print(dir(list)) # 打印相应的对象下面的所有的方法
2)alll() & any()
print(all(["hello", ""])) # all()函数当它里面的可迭代对象的值全部为True时返回True,或为空时也返回True ,否则返回False print(any(["", 1, 0])) # any()函数当它里面的值有一个为True时,则为True
3)enumerate() 遍历可迭代对象
num = [1, 2, 3] for x, y in enumerate(num): # enumerate()函数接受一个可迭代对象,并遍历返回 每个元素的索引和它对应的值 print(x, y)
4)isinstance() 判断数据类型
# isinstance()函数判断第一个参数是不是第二个参数指定的数据类型 print(isinstance(5, int)) print(isinstance("hgzero", str)) print(isinstance([1,2,3], list))
5)__import__ 导入模块
# import my_test # import不能以字符串类型导入模块 # import 的导入原理为: import ------> system ------> __import__
pkg = __import__("hello") # __import__ 可以以字符串类型导入模块
4. 函数使用练习
4.1 基础练习
1. 写一个函数,根据范围获取其中3和7整除的所有数的和,并返回调用者;符合条件的数字个数以及符合条件的数字的总和
print("递归的方法实现:") def func(start, end, number = 0, n = 0): # 这里定义的关键字参数在下一次调用的时候保证不会使变量清零 if start % 3 == 0 and start % 7 == 0: number += start # 这里的number计算所有符合条件的数字的总和 n += 1 # 这里的n计算所有符合条件的数字的各数 print("找到一个值,它是 %d" % start) if start > end: return n, number ret = func(start+1, end, number, n) return ret f = func(0, 996) # 这里的范围已经为python的最大递归深度 print("所有符合条件的数的个数为 %d ,所有符合条件的数的和为 %d" % (f[0], f[1])) print("用迭代的方法实现:") def func2(start, end): number = 0 n = 0 for i in range(start, end+1): if i % 3 == 0 and i % 7 == 0: print("找到一个值,它是 %d" % i) number += i n += 1 print("所有符合条件的数的个数为 %d ,所有符合条件的数的和为 %d" % (n, number)) f2 = func2(0, 996)
2. 定义一个函数统计一个字符串中大写字母、小写字母、数字的个数,并以字典的形式返回给调用者
def the_count(s, upper=0, lower=0, number=0): for i in s: if i.isupper(): # 判断是否是大写字母 upper += 1 if i.islower(): # 判断是否是小写字母 lower += 1 if i.isdigit(): # 判断是否是数字 number += 1 dic = {"upper": upper, "lower": lower, "number": number} return dic name = "hgzeroWuzhihao123" ret = the_count(name) print(ret)
4.2 基础进阶
- 对文件内容的增删改查
import os def file_hander(string, type, res=None): if type == "search": with open("haproxy.conf", "r", encoding="utf-8") as f: back_data = [] # 设置一个接受列表 flag = False # 设置一个标识,以确定是否查找到要找的内容 for f_line in f: if f_line.strip() == string: # 找到了想要匹配的内容 flag = True continue if flag and f_line.startswith("backend"): # 判断要查找的内容是否已经读取完 # flag = False break if flag: # 将匹配到的内容添加到一个列表中 print(f_line, end="") back_data.append(f_line) return back_data elif type == "mod": with open("haproxy.conf", "r", encoding="utf-8") as f_old, open("haproxy.conf_new", "w", encoding="utf-8") as f_new: flag = False # 判断是否找到指定的行的标识 has_write = False # 判断文件是否已经写入的标识 for line_old in f_old: if line_old.strip() == string: # 找到了标识的内容所在的行,开启flag标识,并进入下一行的读取 flag = True continue if flag and line_old.startswith("backend"): # 判断想要修改位置的内容是否已经读取完 flag = False if not flag: # 进行其他无需修改的内容的写入 f_new.write(line_old) else: if not has_write: # 进行修改内容的写入 for i in res: f_new.write(i) has_write = True print("修改完成") os.rename("haproxy.conf", "haproxy.conf.bak") os.rename("haproxy.conf_new", "haproxy.conf") os.remove("haproxy.conf.bak") def search(data): # 搜索功能 print("这是查询功能,要查找的数据是:", data) string = "backend %s" % data # 将字符串拼接成文件中的一整行 # return back_data # 返回一个包含查到的内容的列表 return file_hander(string, type="search") def add(): pass def mod(data): print("这是修改功能") data = eval(data) # 因为接收到的data是字符串的形式,现在恢复其数据结构 backend = data[0]["backend"] # 就是 www.oldboy1.org string = "backend %s" % backend # 匹配文件中的一整行内容 old_record_data = "%sserver %s weight %s maxconn %s\n" % (" "*8, data[0]["record"]["server"], data[0]["record"]["weight"], data[0]["record"]["maxconn"]) # 因为列表中的每一项内容都自动在尾部添加了一个换行符,所以这里也要在尾部添加上一个换行符 new_record_data = "%sserver %s weight %s maxconn %s\n" % (" "*8, data[1]["record"]["server"], data[1]["record"]["weight"], data[1]["record"]["maxconn"]) res = search(backend) if res and old_record_data in res: # 判断查找返回的列表是否为非空且查找的记录在res列表中 index = res.index(old_record_data) # 找到要查找的记录对应的那个索引 res[index] = new_record_data # 将索引对应的那条数据替换成新的数据 res.insert(0, "%s\n" % string) # 在列表索引开头的位置添加上匹配定位的一整行内容 else: print("要修改的内容不存在!") file_hander(string, type="mod", res=res) def delete(): pass if __name__ == "__main__": Output = """ 1.查找 ---(找到对应的backend下所有的server信息) 2.添加 3.修改 4.删除 5.退出 """ choice_dict = { "1": search, "2": add, "3": mod, "4": delete } while True: print(Output) usr_choice = input("请输入你的操作:").strip() if usr_choice == "5": print("退出!") break if usr_choice in choice_dict.keys(): data = input("请输入你的内容:").strip() ret = choice_dict[usr_choice](data) print(ret) # 这是在调用修改功能是输入的数据内容 # [ # {'backend':'www.oldboy1.org', 'record':{'server':'2.2.2.4','weight':20,'maxconn':3000}}, # {'backend':'www.oldboy1.org', 'record':{'server':'2.2.2.5','weight':30,'maxconn':4000}} # ] # [{'backend':'www.oldboy20.org','record':{'server':'2.2.2.3','weight':20,'maxconn':3000}},{'backend':'www.oldboy10.org','record':{'server':'10.10.0.10','weight':9999,'maxconn':33333333333}}]
5. 异常处理
5.1 错误与异常
try: raise TypeError('类型错误') # 主动触发异常 age = input('>>>:') int(age) except IndexError as e: print(e) except KeyError as e: print(e) except ValueError as e: print(e) # except Exception as e: # 抓取所有的异常 # print('请重新输入',e) else: print('try中的代码没有发生异常就会执行else') finally: print('无论异常与否,都会执行该模块,通常是进行清理工作') print('123456789')
5.2 自定义异常
class HgzeroException(BaseException): # 这里要继承BaseException def __init__(self, msg): self.msg = msg def __str__(self): return self.msg # raise TypeError('类型错误') raise HgzeroException('自己定制的异常')
5.3 断言
断言:在程序的某个位置,判断一下结果是不是想要的值,若不是想要的值,就抛出一个异常。
def test1(): res = 1 return 1 res1 = test1() assert res1 == 1 # 在程序的这个位置判断res1是否等于1,若不等于1,则抛出一个AssertionError的异常 # 下面这两行的代码和上面的断言语句等价 if res1 != 1: raise AssertionError