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"})

3)返回值

  • 函数若没有使用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.2 函数的注释

在函数中对函数的参数、返回值进行注释说明。

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.3 名称空间

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.4 局部变量&全局变量

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.5 函数的递归

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.6 匿名函数

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 三个常用的高阶函数

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

3)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 可迭代对象(Iterator)

迭代器协议:某对象必须提供一个__next__()方法,执行方法要么返回迭代中的下一项,要么引起一个Stopiteration异常,以终止迭代(只能往后走,不能往前退)

协议是一种规范,可迭代对象实现了迭代器协议,python的内部工具(如for循环、sum、min、max函数),使用迭代器协议访问对象

可迭代对象(Iterator):实现了迭代器协议的对象(如何实现:对象内部定义了一个__iter__()方法),也就是可迭代对象内部要包含__iter__() 函数

迭代器(Iterator):内部包含了__iter()__() ,同时也包含__next__()

4.2 for循环的工作机制

先通过__iter__()方法将可迭代对象转换为迭代器,再调用迭代器中的__next__()方法遍历,最后再抓取结束时的异常

print("解析for循环的工作机制:")
num = [1, 2, 3, 4, 5]
for i in num:           # 工作机制:  num.__iter__()  ------>  num.__next__()
    print(i)

4.3 可迭代对象与迭代器

1)可迭代对象与迭代器之间的关系

  • 可迭代对象包含迭代器
  • 如果一个对象拥有__iter__方法,其就是可迭代对象(可以被for循环迭代);如果一个对象拥有next方法,其是迭代器
  • 定义可迭代对象,必须事先__iter__方法;定义迭代器,必须实现__iter__和next方法。

2)迭代器的特点

  • 节省内存
  • 惰性机制
  • 不能反复,只能向下执行

3)isinstance()判断对象是否为Iterable对象:

from collections import Iterable
print(isinstance([], Iterable))

4.4 迭代器协议的使用示例

print("使用while循环遍历一个列表:")
index = 0
while index < len(num):
    print(num[index])
    index += 1

print("利用迭代器协议遍历一个列表:")
iter = num.__iter__()
print(iter.__next__())
print(iter.__next__())
print(iter.__next__())

print("解析文件操作中对于文件内容的遍历:")
f = open("test.txt", "r")
f_iter = f.__iter__()      # 这里先将整个文件转换为一个迭代器,之后对迭代器调用__next__()方法,只在有需要的时候才加载文件一行内容
# 当取出一行内容时,因为没有赋值给任何变量,所以占用的内存会被python的自动回收机制回收,所以这种遍历文件的方式只会动态的占用一小块内存
print(f_iter.__next__(), end="")
print(f_iter.__next__(), end="")
print(f_iter.__next__(), end="")
print(f_iter.__next__(), end="")
print(f_iter.__next__(), end="")
f.close()

print("next()方法:")     # next()方法--->调用__next__()方法
the_num = [1, 2, 3]
the_iter = the_num.__iter__()   # 也可以直接使用iter(the_num)方法
print(next(the_iter))     # next(the_iter)  ----->  调用 the_iter.__next__()

5. 生成器

5.1 生成器概述(generator)

生成器(generator)就是可迭代对象(它在内部实现了迭代器协议)

生成器在python中的两种表达形式:

  • 生成器表达式
  • 生成器函数

触发生成器的方式:

  • 通过调用__next__()方法,相当于send(None)
  • 通过调用send()方法

5.2 生成器函数

  • 只要函数中包含yield关键字,则此函数就是一个生成器函数
  • 每调用一次 __next__(),yield后面的值就会被返回一次,且保留此次的状态,下一次调用从此位置开始

1)生成器函数

def func():
    print("现在开始执行生成器函数:")
    print("First----->")
    yield "第一步"
    print("Second----->")
    yield "第二步"
    print("Third")
    yield "第三步"
    print("End")
    yield "生成器函数调用完毕"

f = func()
print(f.__next__())    # __next__()函数调用接受到的返回值就是yield后面的值
print(f.__next__())
print(f.__next__())
print(f.__next__())

2)用yield方式的生成器实现字典中文件的内容查找

def get_num():
    f = open("population.txt", "r", encoding="utf-8")  # 这个文件中的每一行都是一个字典
    for i in f:
        yield i                                        # 这里返回的 i 为字符串类型

population = eval(get_num().__next__())                # 这里通过eval()函数重新恢复了文件中数据的数据类型,恢复成了字典
print("%s的人口数为%s" % (population["city"], population["population"]))
# 注意:生成器只能遍历一次

# 用生成器表达式的方式实现字典中文件数字的求和
f = open("population.txt", "r", encoding="utf-8")
p = sum(eval(i)["population"] for i in f)
print(p)

3)send()方法的使用

def my_func():
    print("First")
    send1 = yield 1     # yield后面的值是next方法调用的返回值,而前面的值是yield接收值之后赋值的变量
    print("Second")
    print("此次send传送的值为:", send1)
    send2 = yield 2
    print("Third")
    print("此次send传送的值为:", send2)
    yield 3
    print("End")
  
m = my_func()           # 这里并不会执行这个函数,因为只要函数内部定义了yield关键字,函数就变成了生成器,只有通过next方法调用
print(m.__next__())

print(m.send(None) )    # 这里的send(None),就相当于__next__()方法,触发一次生成器
# send()函数将值传递给当前停留位置的yield,之后这个yield会将值传递给前面的变量
print(m.send("这是第二次send"))

4)yield from

  • yield from可以直接把迭代对象中的每一个数据作为生成器的结果进行返回
  • 因为yield from是将列表中的每一个元素返回,所以写两个yield from并不会产生交替的效果
def func():
    lst1 = ['卫龙','老冰棍','北冰洋','牛羊配']
    lst2 = ['馒头','花卷','豆包','大饼']
    yield from lst1
    yield from lst2
g = func()
for i in g:
    print(i)
    
# 卫龙
# 老冰棍
# 北冰洋
# 牛羊配
# 馒头
# 花卷
# 豆包
# 大饼

5)额外

def func3():
    for i in range(10):
        yield i

t = func3()
# for i in t:            # 因为for循环的工作机制就是使用迭代器协议调用next方法,所以会触发生成器函数
#     print(i)

li1 = (i for i in t)     # 这里li1会得到一个生成器的地址
li2 = (i for i in li1)   
print(type(li1))
print(list(li1))         # 这里的list函数中会用for循环机制取值,从而触发生成器,现在这个位置li1中的值已经被取完了
print(list(li2))         # 因li1中的值已经被取完了,所以li2现在已经取不到值了

5.3 推导式

  • 推导式有:列表推导式、字典推导式、集合推导式
  • 没有元组推导式

1)列表推导式

  • 三元表达式
    name = "hgzero"
    ret = "me" if name == "hgzero" else "other"    # 若name等于hgzero,则返回me,否则返回other
    print(ret)
  • 列表推导式(列表解析)
    list1 = [i for i in range(10)]                 # 列表解析会将生成的列表直接放于内存中,会占用较大的内存
    print(list1)
    list2 = ["数字%d" % i for i in range(10)]       # for循环处理后将i依次往前面传递
    print(list2)
  • 筛选模式(三元表达式和列表解析的使用示例)
    print("三元表达式和列表解析结合")
    list3 = ["数字%d" % i for i in range(10) if i > 5]   # 这里不能使用else,因为已经构成了三元表达式了
    print(list3)

2)生成器推导式

  • 将列表推导式外围的中括号换成小括号就变成了一个生成器推导式
  • 生成器推导式也可以使用筛选模式
print("生成器表达式:")
list2 = ("数字%d" % i for i in range(10))    # 将列表解析表达式外围的中括号换成小括号就变成了一个生成器
# 生成器表达式更节省内存
print(list2)
print(list2.__next__())
print(list2.__next__())
print(list2.__next__())
print(list2.__next__())

# 可以将生成器表达式传递给sum()等函数,以节省占用内存的大小
print(sum(i for i in range(100000)))        
# 这里的sum函数里面的生成器表达式不需要加括号
# 如果直接在sum中传入一个非常大的列表,会造成占用太多内存而导致机器卡死,而用生成器的方式传入则会动态的占用很小的一片内存

3)字典推导式

lst1 = ['jay','jj','huazai']
lst2 = ['周杰伦','林俊杰','刘德华']
dic = {lst1[i]:lst2[i] for i in range(len(lst1))}
print(dic)

4)集合推导式

  • 集合推导式可以直接生成一个集合
  • 集合的特点:无需、不重复,所以集合推导式自带去重功能
lst = [1,2,3,-1,-3,-7,9]
s = {abs(i) for i in lst}
print(s)

5.4 生产者消费者模型

import time

# def producer():
#     ret = []
#     for i in range(100):
#         time.sleep(0.1)
#         ret.append("包子%s" % i)
#     return ret
#
# def consumer(ret):
#     for index, baozi in enumerate(ret):
#         time.sleep(0.1)
#         print("第%d个人来了,吃了第%s个包子" % (index, baozi))
#
# p = producer()
# consumer(p)

def producer():
     c1 = consumer("hgzero")     
     c2 = consumer("wuzhihao")
     c1.__next__()
     c2.__next__()
     for i in range(10):
         time.sleep(1)
         print("现在包子 %s 已经做好了!" % i)
         c1.send("第%s个包子" % i)
         c2.send("第%s个包子" % i)

def consumer(name):
    print("吃包子开始!")
    while True:
        baozi = yield
        time.sleep(1)
        print("我是 %s , 现在开始吃 %s 包子" % (name, baozi))

producer()

5.5 闭包

在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用,这样就构成了一个闭包。简单来说,闭包就是内层函数,对外层函数变量的引用。

一般情况下,如果一个函数执行结束,则这个函数的内部的变量以及局部命名空间中的内容都会被释放掉。但是在闭包中,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。也就说,使用闭包,可以保证外部函数中的变量在内存中常驻,以供后面的程序使用。

其实闭包中外部函数的返回值定义成内部函数的地址,是为了防止内部函数被垃圾回收机制回收。

def func1():  
    def func2(): 
        s = '地主家的傻儿子'
        def func3():       
            print(s)      
        return func3 
    return func2
func1()()()

5.6 解压序列

# 解压序列:
a, *b, c = [1, 2, 3, 4, 5, 6, 7, 8, 9]    # 这里的 * 号表示所有的意思
print(a)
print(b)
print(c)


# 交换两个变量的值:
a = 1
b = 2
print("通过中间变量的形式交换:")
c = a
a = b
b = c
print(a, b)

print("另外两种不引入额外变量实现交换的方式:")
f1 = 1
f2 = 2
f1, f2 = f2, f1
print(f1, f2)

n1 = 1
n2 = 2
n1 = n1 + n2
n2 = n1 - n2
n1 = n1 - n2
print(n1, n2)

6. 装饰器

6.1 装饰器基础

1)装饰器概述

  • 装饰器:本质上就是函数,功能是为其他函数添加附加功能
  • 原则:
    • 不修改被修饰函数的源代码
    • 不修改被修饰函数的调用方法
  • 装饰器 = 高阶函数 + 函数嵌套 + 闭包

2)装饰器的实现方式

  • 用高阶函数的方式实现装饰器的功能不合格,因为这种方式会使工作函数调用了两次
    import time
    def work_func():
        time.sleep(1)
        print("这里是work_func函数")
    
    def dec_func(func):
        start_time = time.time()
        func()
        finish_time = time.time()
        print("这个函数运行的时间是%d" % (finish_time-start_time))
        return func
    
    work_func = dec_func(work_func)   # 这里在赋值之前就已经执行了dec_func(work_func)函数
    work_func()                       # 这里又执行了一便work_func函数
    View Code
  • 用高阶函数 + 函数嵌套 + 闭包的方式实现装饰器
    import time
    def work_func2():
        time.sleep(1)
        print("这里是work_func2函数")
    
    def dec_func2(func):
        def wapper():
            start_time = time.time()
            func()
            finish_time = time.time()
            print("这个函数运行的时间是%d" % (finish_time - start_time))
        return wapper
    
    work_func2 = dec_func2(work_func2)   # 这里执行dec_func2函数时遭遇一个闭包,得到的是闭包wrapper函数的内存地址(并没有执行它)
    work_func2()                         # 这里执行上面得到的闭包函数,即执行wapper()函数
  • 用“语法糖”的形式实现装饰器
    import time
    def dec_func3(func):
        def wapper():
            start_time = time.time()
            func()
            finish_time = time.time()
            print("这个函数运行的时间是%d" % (finish_time - start_time))
        return wapper
    
    @dec_func3     # 用“语法糖”的形式实现装饰器 ,相当于 work_func3 = dec_func3(work_func3)
    def work_func3():
        time.sleep(1)
        print("这里是work_func3函数")
    work_func3()   
    
    
    def dec_func4(func):   # 装饰器改进,添加返回值和多参数功能
        def wapper(*args, **kwargs):
            start_time = time.time()
            res = func(*args, **kwargs)  # func()函数的返回值就是work_func函数中的返回值,用res接收之后,再返回出去
            finish_time = time.time()
            print("这个函数运行的时间是%d" % (finish_time - start_time))
            return res
        return wapper
    
    @dec_func4     # work_func4 = dec_func3(work_func3) 这时执行work_func4函数就是执行wapper函数,因为dec_func3函数返回的就是wapper的地址
    def work_func4(name, age):
        time.sleep(1)
        print("这里是work_func4函数")
        print("Name:【%s】,Age:【%d】" % (name, age))
        return 12345                      # 这时,work_func函数的返回值就是dec_func函数的闭包---> wapper函数的返回值
    work_func4("hgzero", 21)

6.2 带认证功能的装饰器

import time

user = [
    {"username": "hgzero", "passwd": "123"},
    {"username": "wzh", "passwd": "123"},
    {"username": "hg", "passwd": "123"},
    {"username": "zero", "passwd": "123"},
    {"username": "abc", "passwd": "123"}
]

login_dict = {"username": None, "login": False}    # 这里用这个字典模拟session的功能

def login_func(func):
    def wrapper(*args, **kwargs):
        if login_dict["username"] and login_dict["login"]:
            ret = func(*args, **kwargs)
            return ret
        while True:
            username = input("请输入用户名:").strip()
            passwd = input("请输入密码:").strip()
            for dict in user:
                if dict["username"] == username and dict["passwd"] == passwd:
                    login_dict["username"] = username
                    login_dict["login"] = True
                    ret = func(*args, **kwargs)
                    return ret
            else:
                print("用户名或密码错误!")
                time.sleep(1)
                print("请重新输入----->")
    return wrapper

@login_func
def index():
    print("欢迎来到zero的网站主页!")

@login_func
def home(name):
    print("【%s】,欢迎来到个人中心!" % (name))

@login_func
def shopping(name):
    print("【%s】,现在您的购物车中有 %s , %s , %s " % (name, "牛奶", "香蕉", "芒果"))

index()
home("python工程师")
shopping("python工程师")

6.3 带参数的装饰器

import time
user = [
    {"username": "hgzero", "passwd": "123"},
    {"username": "wzh", "passwd": "123"},
    {"username": "hg", "passwd": "123"},
    {"username": "zero", "passwd": "123"},
    {"username": "abc", "passwd": "123"}
]

login_dict = {"username": None, "login": False}    # 这里用这个字典模拟session的功能

def func(db_type):
    def login_func(func):
        print("这个数据库的类型是:", db_type)
        def wrapper(*args, **kwargs):
            if login_dict["username"] and login_dict["login"]:
                ret = func(*args, **kwargs)
                return ret
            while True:
                username = input("请输入用户名:").strip()
                passwd = input("请输入密码:").strip()
                for dict in user:
                    if dict["username"] == username and dict["passwd"] == passwd:
                        login_dict["username"] = username
                        login_dict["login"] = True
                        ret = func(*args, **kwargs)
                        return ret
                else:
                    print("用户名或密码错误!")
                    time.sleep(1)
                    print("请重新输入----->")
        return wrapper
    return login_func


@func("MySQL")  #  这里先执行func("MySQL")  --->   index = login_func(index)
# 这里因为后面加了括号,所以是执行了func函数,执行了func函数所得到的返回值就是login_func函数的地址
# func()函数执行得到的地址就是login_func,所以这里就等价于是 @login_func,同时也将参数传进去了,巧妙的运用了闭包的原理
def index():
    print("欢迎来到zero的网站主页!")

@func("Mongodb")
def home(name):
    print("【%s】,欢迎来到个人中心!" % (name))

@func("Redis")
def shopping(name):
    print("【%s】,现在您的购物车中有 %s , %s , %s " % (name, "牛奶", "香蕉", "芒果"))

index()
home("python工程师")
shopping("python工程师")

7. 函数使用练习

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

7.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
" % (" "*8, data[0]["record"]["server"],
                                                            data[0]["record"]["weight"], data[0]["record"]["maxconn"])
                        # 因为列表中的每一项内容都自动在尾部添加了一个换行符,所以这里也要在尾部添加上一个换行符

    new_record_data = "%sserver %s weight %s maxconn %s
" % (" "*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
" % 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}}]

8. 异常处理

8.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')

8.2 自定义异常

class HgzeroException(BaseException): # 这里要继承BaseException
    def __init__(self, msg):
        self.msg = msg

    def __str__(self):
        return self.msg

# raise TypeError('类型错误')
raise HgzeroException('自己定制的异常')

8.3 断言

断言:在程序的某个位置,判断一下结果是不是想要的值,若不是想要的值,就抛出一个异常。

def test1():
    res = 1
    return 1
res1 = test1()

assert res1 == 1 # 在程序的这个位置判断res1是否等于1,若不等于1,则抛出一个AssertionError的异常

# 下面这两行的代码和上面的断言语句等价
if res1 != 1:
    raise AssertionError
原文地址:https://www.cnblogs.com/hgzero/p/9010562.html