06 python开发之函数

06 python开发之函数

目录

6 函数

6.1 基本使用

6.1.1 基本概念

  • 函数就是用来盛放代码的容器

    ​ 具备某一功能的工具–>工具

    ​ 实现准备好工具的过程–>函数的定义

    ​ 遇到应用场景,拿来就用

  • 使用函数可以解决代码可读性差以及拓展性差的问题

  • 先定义后引用

6.1.2 定义函数

  • 定义函数的语法
def 函数名(参数1,参数2,...):
    """文档描述"""
    函数体
    return 值

# def: 定义函数的关键字;
# 函数名:函数名指向函数内存地址,是对函数体代码的引用。函数的命名应该反映出函数的功能;
# 括号:括号内定义参数,参数是可有可无的,且无需指定参数的类型;
# 冒号:括号后要加冒号,然后在下一行开始缩进编写函数体的代码;
# """文档描述""": 描述函数功能,参数介绍等信息的文档,非必要,但是建议加上,从而增强函数的可读性;
# 函数体:由语句和表达式组成;
# return 值:定义函数的返回值,return是可有可无的。
def say_hello():  # say_hello=函数的内存地址
    print("======")
    print("hello world")
    print("======")

print(say_hello)  # <function say_hello at 0x00000241DA0273A0>
say_hello()

x = 10            # x=10的内存地址
print(x)
  • 函数定义阶段与调用阶段发生的事情
# 函数定义阶段:只检测语法,不执行代码
def foo():
    xxx
    print('from foo')

# 函数调用阶段:执行函数体代码
foo()
  • 示例
示例1
def bar():
    print('from bar')

def foo():
    print('from foo')
    bar()

foo()		# from foo | from bar

示例二
def bar():
    print('from bar')
    foo()

def foo():
    print('from foo')

bar()		# from bar | from foo

示例三
def bar():
    print('from bar')
    foo()

bar()		# 报错,要先定义后引用

def foo():
    print('from foo')
  • 定义函数的三种形式

    • 无参函数
    # 无参函数
    def f1():
        print("hello")
        print("你好")
        print("Hi")
    f1()			# hello | 你好 | Hi
    
    
    def login():
        name = input('username>>>:').strip()
        pwd = input('password>>>:').strip()
        if name == "ccc" and pwd == '111':
            print('ok')
        else:
            print('error')
    
    login()			# username>>>:
    
    • 有参函数
    def login(name,pwd):
        if name == "ccc" and pwd == "123":
            print('ok')
        else:
            print('error')
    
    login("ccc","123")	# ok
    login("ccc","1234")	# error
    
    def f2(x, y):
        print(x)
        print(y)
    
    f2(111, 222)		# 111 | 222
    
    def add(x, y):
        print(x+y)
    
    add(10, 20)	        # 30
    add(30, 40)		# 70
    
    • 空函数
    def login():
        pass
    def transfer():
        pass
    def withdraw():
        pass
    def check_balance():
        pass
    

6.2 调用函数与函数返回值

6.2.1 调用函数三种形式

  • 语句形式
len("hello")
  • 表达式形式
res = len("hello") + 10
print(res)		      # 15
  • 函数调用作为参数的形式
def add(x, y):
    z = x + y
    return z

print(add(5, 5))	      # 10
res = add(add(1, 2), 3)
print(res)		      # 6

6.2.2 函数的返回值return(函数的产品)

  • return作用1:控制返回值
Ⅰ 没有return-->默认返回就是None
def func():
    print(111)

res = func()		      # 111
print(res)		      # None

Ⅱ return 值-->返回就是那一个值
def max2(x, y):
    if x > y:
        return x
    else:
        return y

res = max2(100, 200) * 5
print(res)		      # 1000

Ⅲ return 值1,值2,值3--->返回的是一个元组
def func():
    return [11, 22], 2, 3

res = func()
print(res, type(res))         # ([11, 22], 2, 3) <class 'tuple'>
  • return作用2:函数内可以有多个return,但只要执行一个就会立即种植函数的运行,并且会将return后的值当做本次调用的产品返回
def func():
    print(111)
    return 2201202
    print(2222)
    return 11112222
    print(3333)

res = func()
print(res)          # 111 2201202
  • return就是函数的处理结果

    函数内没有return关键字,或者说return后面没有值–>None

  • return 值
    return 值1,值2,值3—>(值1,值2,值3)

  • 可以有多个return,但只要执行一次整个函数就会立即结束,并且返回值

def f1():
    x = 10
    def f2():
        print('from f2')
    return f2

res = f1()  # 即res=f2的内存地址
print(res)  # <function f1.<locals>.f2 at 0x000001F5886D8700>
res()       # from f2

6.3 Type hinting

6.3.1 定义

  • python3新增类型提示功能,例如我们可以为函数增加类型提示信息,而不影响函数本身的执行:

  • 注释的一般规则是参数名后跟一个冒号(:),然后再跟一个expression,这个expression可以是任何形式。

6.3.2 使用

# Type hinting
def add(x: int, y: int) -> int:
    res = x + y
    return res

print(add.__annotations__)
# {'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}

6.4 函数参数

6.4.1 函数分类

  • 形参

    在定义函数时,在括号内指定参数/变量名,称之为形式参数,简称形参

    形参的本质就是变量名

  • 实参

    在调用函数时,括号内传入的值,称之为实际参数,简称实参

    实参的本质就是变量值

  • 实参与形参的关系

    在调用函数时,实参的值会绑定给形参,该绑定关系可以在函数内使用

    在函数调用结束后,会解除绑定关系

6.4.2 参数详解

6.4.2.1 位置形参

  • 在定义函数时,按照从左往右的顺序依次定义的变量名,称之为位置形参

  • 特点:必须被传值,多一个少一个都不行

def func(x, y):
    print(x, y)
func(1, 2)
func(1, 2, 3)       # 报错
func(1)             # 报错

6.4.2.2 位置实参

  • 在调用函数时,按照从左往右的顺序依次传入的值,称之为位置实参

  • 特点:按照位置与形参一一对应

def func(x, y):
    print(x, y)
func(1, 2)
func(2, 1)

6.4.2.3 默认形参

  • 在定义函数时就已经为某个形参赋值了,该形参就称为默认参数

  • 特点:在调用阶段可以不用为默认形参传值

def func(x, y=111):
    print(x, y)
func(1, 2)          # 1 2
func(1)             # 1 111
def register(name, age, gender="male"):
    print(name, age, gender)
register("egon", 18)
register("lxx", 38)
register("hxx", 32, "female")
register("李建国", 30)
register("alex", 31)
register("xxx", 18)

# egon 18 male
# lxx 38 male
# hxx 32 female
# 李建国 30 male
# alex 31 male
# xxx 18 male

6.4.2.4 关键字实参

  • 在调用函数时,按照key=value的形式指定的实参,称之为关键字实参

  • 特点:可以打乱顺序,仍能指名道姓为指定的形参赋值

def func(x, y=2222):
    print(x, y)
func(y=222, x=1212)       # 1212 222
func(x=111)               # 111 2222

6.4.3 实参的混用

  • 位置实参和关键字实参可以混用,注意点:

    Ⅰ 位置实参必须放在关键字实参前面

    Ⅱ 不能为同一个形参重复赋值

def func(x, y=2222):
    print(x, y)
func(1, y=2)              # 1 2
func(y=2, 1)              # 报错
func(1, y=2, x=3)         # 报错

6.4.4 形参的混用

  • 位置形参和默认形参可以混用,注意点:

    位置形参必须放在默认形参前面

def func(x, y=111):
    print(x, y)

def func(y=111, x):     # 报错
    print(x, y)

6.4.5 默认形参使用的注意点

  • 默认形参的值最好是不可变类型
m = 222
def func(x, y=m):
    print(x, y)
m = 666
func(111)               # 得到111 222
def register(name,hobby,hobbies=[]):
    hobbies.append(hobby)
    print('%s 的爱好是 %s' %(name, hobbies))
def register(name, hobby, hobbies=None):
    if hobbies is None:
        hobbies=[]
    hobbies.append(hobby)
    print('%s 的爱好是 %s' %(name, hobbies))
register("egon", "smoke")
register("lxx", "dance")
register("hxx", "drink")

6.5 可变长函数(*与**的应用)

6.5.1 由来

  • 可变长函数是指在调用函数时,传入参数个数不固定,而实参是为形参赋值的

  • 因此必须有对应格式的形参来接受溢出的实参

6.5.2 在形参中使用

6.5.2.1 *args的使用

  • 在形参中带*,*会将溢出的位置实参汇总成元组,然后赋值给其后变量名args
def func(x, y, *z):
    print(x, y, z)
func(1, 2, 3, 4, 5)      # 1 2 (3, 4, 5)
func(1, 2)               # 1 2 ()
func(1)                  # 报错

def my_sum(*args):
    res = 0
    for i in args:
        res += i
    print(res)
my_sum(1)               # 1
my_sum(1, 2)            # 3
my_sum(1, 2, 3)         # 6

6.5.2.2 **kwargs的使用

  • 在形参中带**,**通常会将溢出的关键字实参汇总成字典,然后赋值给其后变量名kwargs
def func(x, y, **kwargs):
    print(x, y, kwargs)
func(1, y=2, a=1, b=2, c=3)        # 1 2 {'a': 1, 'b': 2, 'c': 3}

6.5.3 在实参中使用

6.5.3.1 *的使用

  • 在实参中带*:*会将紧跟其后的实参打散成位置实参
  • 注意*后跟的应该是一个可以被for循环循环的类型
def func(a, b, c, d):
    print(a, b, c, d)
func(*"hello")                          # 报错,不对应
func(*"hell")                           # h e l l
func(*[11, 22, 33, 44])                 # 11 22 33 44
func(11, 22, *[33, 44])                 # 11 22 33 44
func(11, 22, *{"k1": 111, "k2": 222})   # 11 22 k1 k2

6.5.3.2 **的使用

  • 在实参中带**😗*会将紧跟其后的实参打散成关键字实参

  • 注意**后面跟的必须是一个字典

def func(a, b, c, d):
    print(a, b, c, d)
func(**{"k1": 333, "k2": 444})                              # 报错
func(**{"d": 333, "b": 444, "a": 111, "c": 222})            # 111 444 222 333
func(**[("d", 333), ("b", 444), ("a", 111), ("c", 222)])    # 报错

6.5.4 混用

  • 在形参中,*必须在**前

  • 在实参中,*必须在**前

def index(x, y, z):
    print('index------>', x, y, z)
def wrapper(*arges, **kwargs):
    index(*arges, **kwargs)

# wrapper(1, 2, 3, 4, a=1, b=2, c=3)      # 不能一一对应 报错
wrapper(1, 2, 3)                          # index------> 1 2 3
wrapper(z=3, y=2, x=1)                    # index------> 1 2 3


def wrapper(*arges,**kwargs):
    print(arges)
    print(kwargs)

wrapper(1, 2, a=1,b=2,c=3)      # (1, 2) {'a': 1, 'b': 2, 'c': 3}

6.5.5 命名关键字形参

  • 在*与**之间定义的形参称为关键字形参

  • 特点:必须按照key=value的形式传值

def func(x, y=1, *args, a=666, b, **kwargs):
    print(x)
    print(y)
    print(args)
    print(a)
    print(b)
    print(kwargs)
func(1, 2, 3, 4, 5, 6, 7, a=111, b=222, c=333)      # 1 2 (3, 4, 5, 6, 7) 111 222 {'c': 333}
func(1, 2, 3, 4, 5, 6, 7, b=222, c=333)             # 1 2 (3, 4, 5, 6, 7) 666 222 {'c': 333}
func(1, 2, 3, 4, 5, 6, 7, b=222, 333)               # 报错

6.6 函数对象

  • 函数是第一等公民(充当变量)
def func():
    print('from func')

6.6.1可以赋值

def func():
    print('from func')

f = func
f()                 # from func

6.6.2 可以当作参数传给另一个函数

def func():
    print('from func')

def bar(x):
    print(x)
mmm = 11111
bar(mmm)           # 11111
bar(func)          # <function func at 0x000002D4F29B63A0>

6.6.3 可以当一个函数的返回值

def add(x):        # x=函数func的内存地址
    return x       # return 函数func的内存地址
res = add(func)    # res=add(func的内存地址)
print(res)         # <function func at 0x000002D4F29B63A0>

6.6.4 可以当容器类型的元素

x = 10
l = [x, func]
print(l)           # [10, <function func at 0x000002C48C4463A0>]
l[-1]()            # from func

6.6.5 示例

# 新的功能只需要在字典中加入即可,无需动循环
def login():
    print('login')
def register():
    print('register')
def transfer():
    print('transfer')
def withdraw():
    print('withdraw')

func_dic = {
    "1": [login, "登录"],
    "2": [register, "注册"],
    "3": [transfer, "转账"],
    "4": [withdraw, "提现"]
}

while True:
    print("0 退出")
    for k in func_dic:
        print(k, func_dic[k][-1])
    choice = input("请输入操作编号:").strip()
    if choice == "0":
        break
    if choice in func_dic:
        func_dic[choice][0]()
    else:
        print("输入的操作不存在")

6.7 函数嵌套

6.7.1 函数嵌套定义

def f1():
    print('from f1')
    def f2():
        print('from f2')
    print(f2)
    f2()
    x=1111
f1()

# from f1
# <function f1.<locals>.f2 at 0x00000274E0EB8700>
# from f2
from math import pi

def circle(radius, mode=0):
    def perimiter(radius):
        return 2 * pi *radius
    def area(radius):
        return pi * (radius ** 2)
    if mode == 0:
        return perimiter(radius)
    elif mode == 1:
        return area(radius)
res1 = circle(10, mode=0)
print(res1)                 # 62.83185307179586
res2 = circle(10, mode=1)
print(res2)                 # 314.1592653589793

6.7.2 函数嵌套调用

def max2(x, y):
    if x > y:
        return x
    else:
        return y

def max4(a, b, c, d):
    res1 = max2(a, b)
    res2 = max2(res1, c)
    res3 = max2(res2, d)
    return res3


res = max4(1, 2, 3, 4)
print(res)              # 4

6.8 名称空间与作用域

6.8.1 名称空间

  • 名称空间namespace

    名称空间就是存放名字的地方

  • 三类名称空间(内置名称空间、全局名称空间、局部名称空间)

    Ⅰ 内置名称空间:存放内置的名字

    ​ 随着解释器启动就产生,解释器关闭就销毁

    ​ print

    ​ input

    ​ len

    Ⅱ 全局名称空间:存放的是顶级的名字

    ​ 运行顶级代码前产生,文件运行完毕则销毁

    Ⅲ 局部名称空间:在函数内定义的名字

    ​ 调用函数则产生一个函数的局部名称空间,该函数调用结束则立即销毁

名称空间示例
x = 10              # 全局名称空间

def f1():           # f1本身是全局名称空间
    y = 2           # 局部名称空间

if 1 > 0:
    z = 3           # 全局名称空间
    if 3 > 1:
        m = 333     # 全局名称空间

6.8.2 名字的查找顺序

  • 局部名称空间–>全局名称空间–>内置名称空间

  • Tips:在全局无法查看局部的,在局部可以看全局的

优先级示例
x = 111
def f1():
    print(x)
    x = 222
f1()		# 报错

x = 111
def f1():
    x = 222
    print(x)
f1()		# 222

6.8.3 global与nonlocal

  • 名称空间的嵌套关系是在函数定义阶段,即检测语法时确定的

    与调用函数的位置无关,与函数定义位置有关

  • global 局部对全局变量修改

x = 111
def f1():
    global x
    x = 222

f1()
print(x)        # 222
  • nonlocal 声明的是来自外部嵌套函数定义的作用域(非全局)
x = 111
def f1():
    x = 222
    def f2():
        nonlocal x
        x = 333
    f2()
    print(x)
f1()		# 333

6.8.4 作用域

  • 作用域即范围

    全局范围(内置、全局名称空间属该范围):全局存活,全局有效

    局部范围(局部名称空间属该范围):临时存活,局部有效

  • 作用域关系是在函数定义阶段固定的,与函数调用位置无关

  • 查看作用域

    LEGB代表名字查找顺序:locals -> enclosing function -> globals -> _builtings_

    locals 是函数内的名字空间,包括局部变量和形参

    enclosing 外部嵌套函数的名字空间(闭包中常见)

    globals 全局变量,函数定义所在模块的名字空间

    builtings 内置模块的名字空间

6.9 闭包函数

6.9.1 简介

  • 闭函数

    该函数就是定义在一个函数内部的函数

def outter():
    def wrapper():
        pass
  • 包函数

    闭函数引用了一个来源于外层函数的变量

def outter():
    x = 111
    def wrapper():
        print(x)

    wrapper()

x = 222         # 不妨碍
outter()        # 111

6.9.2 意义与应用

  • 意义:

    返回的函数对象外部还包了一层作用域

    该函数无论在何处调用都优先使用自己外层包裹的作用域

  • 应用:

    延迟计算

from urllib.request import urlopen

def index(url):
	def get():
		return urlopen(url).read()
	return get

baidu=index('//www.baidu.com')
print(baidu().decode('utf-8'))

6.10 装饰器

6.10.1 简介

  • 什么是装饰器

    器—->工具

    装饰–>添加新功能

    装饰器就是定义个函数,用该函数去为其他函数添加新功能

  • 为什么用装饰器

    开放封闭原则:针对上线的功能对拓展是开放的,但是对修改源代码以及调用方式是封闭的

6.10.2 无参装饰器

  • 需求:为index加上新功能–>统计其运算时间

    import time

    def index():

    ​ time.sleep(1)

    ​ print(‘from index’)

    index()

# 1、首先考虑直接在运行的代码体上下方加入时间模块
import time
def index():
    start = time.time()
    
    time.sleep(1)
    print('from index')
    
    stop = time.time()
    print('run time is %s' % (stop - start))
index()
# 但是这样改修改了源代码
# 2、不修改源代码
import time
def index():
    time.sleep(1)
    print('from index')

start = time.time()
index()
stop = time.time()
print('run time is %s' % (stop - start))
# 这样修改每次调用函数都要输重复代码,不方便
# 3、把调用放进函数
import time
def index():
    time.sleep(1)
    print('from index')
    
def wrapper():
    start = time.time()
    index()
    stop = time.time()
    print('run time is %s' % (stop - start))
wrapper()
# 调用方便,但是改变了调用方式
# 3.1改进一
import time
def index():
    time.sleep(1)
    print('from index')
    
def wrapper(f):
    start = time.time()
    f()
    stop = time.time()
    print('run time is %s' % (stop -start))
wrapper(index)
# 3.2改进二
import time
def index():
    time.sleep(1)
    print('from index')
    
def outter(f):
    def wrapper():
        start = time.time()
        f()
        stop = time.time()
        print('run time is %s' % (stop - start))
    return wrapper
index = outter(index)
index()
# 添加新功能home
import time
def index():
    time.sleep(1)
    print('from index')
    
def home(name):
    time.sleep(1)
    print('homepage welcome %s' % name)


def outter(f):
    def wrapper(*args, **kwargs):
        start = time.time()
        f(*args, **kwargs)
        stop = time.time()
        print('run time is %s' % (stop - start))
    return wrapper
index = outter(index)
index()
home = outter(home)
home('ccc')
# 最终版
import time
def index():
    time.sleep(1)
    print('from index')
    
def home(name):
    time.sleep(1)
    print('homepage welcome %s' % name)
    return 123


def outter(f):
    def wrapper(*args, **kwargs):
        start = time.time()
        res = f(*args, **kwargs)
        stop = time.time()
        print('run time is %s' % (stop - start))
        return res
    return wrapper
index = outter(index)
home = outter(home)

res = index()
print(res)		# None

res = home('ccc')
print(res)		# 123

6.10.3 装饰器的语法糖@

  • 在被装饰对象的正上方单独一行添加@timer,当解释器解释到@timer时就会调用timer函数,且把它正下方的函数名当做实参传入,然后将返回的结果重新赋值给原函数名

  • 原函数

import time
def index():
    time.sleep(1)
    print('from index')
    
def home(name):
    time.sleep(1)
    print('homepage welcome %s' % name)
    return 123


def outter(f):
    def wrapper(*args, **kwargs):
        start = time.time()
        res = f(*args, **kwargs)
        stop = time.time()
        print('run time is %s' % (stop - start))
        return res
    return wrapper
index = outter(index)
home = outter(home)

res = index()
print(res)		# None

res = home('ccc')
print(res)		# 123
  • 用语法糖@
import time

def timmer(f):
    def wrapper(*args, **kwargs):
        start = time.time()
        res = f(*args, **kwargs)
        stop = time.time()
        print('run time is %s' % (stop - start))
        return res
    return wrapper


@timmer  # index = timmer(index)
def index():
    time.sleep(1)
    print('from index')


@timmer  # home = time(home)
def home(name):
    time.sleep(2)
    print('welcome to %s' % name)
    return 123


index()
res = home("ccc")
print(res)

# from index
# run time is 1.0003390312194824

# welcome to ccc
# run time is 2.0008108615875244
# 123

6.10.4 有参装饰器

  • 在函数func外部再包一层函数auth,专门用来接收额外的参数,保证在auth函数内无论多少层都可以引用到参数
def auth(driver):
    def func(x):
        ...
    return func

@auth(driver='file')
def index():
    pass
@auth(driver='mysql')
def home():
    pass
import time

def login(x, engine='file'):
    def auth(func):
        def wrapper(*args, **kwargs):
            print("========>", x)
            inp_user = input("username>>>>:").strip()
            inp_pwd = input("password>>>:").strip()

            if engine == "file":
                print("基于file认证")
                if inp_user == "egon" and inp_pwd == "123":
                    print('login successful')
                    res = func(*args, **kwargs)
                    return res
                else:
                    print('username or password error')
            elif engine == 'mysql':
                print("基于mysql认证")
            elif engine == "ldap":
                print("基于ldap认证")
            else:
                print("未知认证来源")
        return wrapper
    return auth


@login(11, engine='file')  # @auth  # index = auth(index)  # index=wrapper
def index():
    time.sleep(1)
    print('from index')


@login(22, engine='file')  # @auth  # home=auth(home)  # home=wrapper
def home(name):
    time.sleep(2)
    print('home page,welcome %s' % name)
    return 123


index()
home('egon')

# ========> 11
# username>>>>:egon
# password>>>:123
# 基于file认证
# login successful
# from index

# ========> 22
# username>>>>:egon3
# password>>>:123
# 基于file认证
# login successful
# home page,welcome egon

6.10.5 叠加多个装饰器

  • 加载顺序(outter函数的调用顺序):自下而上

  • 执行顺序(wrapper函数的执行顺序):自上而下

    @deco1
    @deco2
    @deco3
    def foo():
    pass
    相当于

    foo=deco1(deco2(deco3(foo)))

def outter1(func1):  # func1 = wrapper2的内存地址
    print('============>outter1')
    def wrapper1(*args, **kwargs):
        print('=============wrapper1')
        res1 = func1(*args, **kwargs)
        return res1
    return wrapper1

def outter2(func2):  # func2 = wrapper3的内存地址
    print('============>outter2')
    def wrapper2(*args, **kwargs):
        print('=============wrapper2')
        res2 = func2(*args, **kwargs)
        return res2
    return wrapper2

def outter3(func3):  # func3 = index的内存地址
    print('============>outter3')
    def wrapper3(*args, **kwargs):
        print('=============wrapper3')
        res3 = func3(*args, **kwargs)
        return res3
    return wrapper3

          # index = wrapper1的内存地址
@outter1  # outter1(wrapper2的内存地址)-->wrapper1的内存地址
@outter2  # outter2(wrapper3的内存地址)-->wrapper2的内存地址
@outter3  # outter3(index的内存地址)-->wrapper3的内存地址
def index():
    print('from index')


print('*'*25)
# print(index)
index()

# ============>outter3
# ============>outter2
# ============>outter1
# *************************
# =============wrapper1
# =============wrapper2
# =============wrapper3
# from index
import time
def timmer(func):
    def wrapper1(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        stop = time.time()
        print('run time is:%s' % (stop - start))
        return res
    return wrapper1

def login(x, engine='file'):
    def auth(func):
        def wrapper2(*args, **kwargs):
            print("========>", x)
            inp_user = input("username>>>>:").strip()
            inp_pwd = input("password>>>:").strip()

            if engine == "file":
                print("基于file认证")
                if inp_user == "egon" and inp_pwd == "123":
                    print('login successful')
                    res = func(*args, **kwargs)
                    return res
                else:
                    print('username or password error')
            elif engine == 'mysql':
                print("基于mysql认证")
            elif engine == "ldap":
                print("基于ldap认证")
            else:
                print("未知认证来源")
        return wrapper2
    return auth


# 场景一
@timmer
@login(11, engine='file')
def index():
    time.sleep(1)
    print('from index')

index()
# ========> 11
# username>>>>:egon
# password>>>:123
# 基于file认证
# login successful
# from index
# run time is:9.147817134857178


# 场景二
@login(11, engine='file')
@timmer
def index():
    time.sleep(1)
    print('from index')

index()
# ========> 11
# username>>>>:egon
# password>>>:123
# 基于file认证
# login successful
# from index
# run time is:1.0001623630523682

6.10.6 wraps装饰器

  • functools模块下提供一个装饰器wraps专门用来帮我们保留原函数的文档和函数名属性,修正装饰器
import time
from functools import wraps


def timmer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        stop = time.time()
        print('run time is:%s' % (stop - start))
        return res
    return wrapper


@ timmer
def index():
    """index函数"""
    time.sleep(1)
    print('from index')


help(index)

6.10.7 匿名函数lambda

def func(x, y):  # func=函数的内存地址
    return x+y

匿名函数:没有名字的函数
应用场景:临时用一次,通常用于与其他函数配合使用
# 无用调用方式
f = lambda x, y: x+y
print(f)
res = f(1, 2)
print(res)

# 调用方式1
res = (lambda x, y: x+y)(1, 2)
print(res)

# 调用方式2
salaries = {
    "egon": 1000,
    "alex": 2000,
    "jack": 3000,
    "rose": 4000
}
# 求薪资最高的那个人的名字

def get_salary(name):
    return salaries[name]

print(max(salaries, key = get_salary))  # rose
print(max(salaries, key=lambda name: salaries[name]))  # rose
print(min(salaries, key=lambda name: salaries[name]))  # egon
print(sorted(salaries))  # ['alex', 'egon', 'jack', 'rose']
print(sorted(salaries, key=lambda name: salaries[name]))  # ['egon', 'alex', 'jack', 'rose']
print(sorted(salaries, key=lambda name: salaries[name], reverse=True))  # ['rose', 'jack', 'alex', 'egon']

6.11 内置函数

# abs 返回绝对值
print(abs(-11))  # 11
print(abs(0))    # 0
print(abs(11))   # 11

# all 如果是空或布尔值为真则返回True
print(all(''))   # True
print(all([]))   # True
print(all([11, 22, 333, 0]))  # False

# any 只要有一个值的布尔值为真就返回True
print(any(''))   # False
print(any([]))   # False
print(any([0, None, '', 1]))  # True
print(any([0, None, '']))     # False

# chr 对比ASCⅡ表:65-90 A-Z
print(chr(65))   # 数字->字母A
print(ord('A'))  # 字母->数字65
print(chr(90))   # Z

# bin/oct/hex 进制转换
print(bin(11))   # 十进制->二进制 0b1011
print(oct(11))   # 十进制->八进制 0o13
print(hex(11))   # 十进制->十六进制 0xb

# 工厂函数
int
float
str
list
tuple
dict
set
bool
bytes

# callable查看对象是否可调用
print(callable(len))  # True

# 面向对象重点
classmethod
staticmethod
setattr
getattr
delattr
hasattr
dir
exec

# eval 读取内容代码时执行
l = eval("[1, 2, 3]")
print(l[0])  # 1
with open('user1.txt', mode='wt', encoding='utf-8') as f:
    dic = {"ccc": "123", "zzz": "456", "yyy": "789"}
    f.write(str(dic))

with open('user1.txt', mode='rt', encoding='utf-8') as f:
    data = f.read()
    print(data, type(data))  # {'ccc': '123', 'zzz': '456', 'yyy': '789'} <class 'str'>
    dic = eval(data)
    print(dic["ccc"])  # 123
# 1、map 映射
names = ["egon", "lxx", "hxx"]
res = (name if name == "egon" else name+"_sb" for name in names)
print(list(res))  # ['egon', 'lxx_sb', 'hxx_sb']

用map实现
names = ["egon", "lxx", "hxx"]
res = map(lambda name:name if name == "egon" else name+"_sb", names)
print(res)  # <map object at 0x0000020E897D5B50>
print(list(res))  # ['egon', 'lxx_sb', 'hxx_sb']


# 2、filter 过滤
names = ['egon', 'lxx_sb', 'hxx_sb']
res = (name for name in names if name.endswith("sb"))
print(list(res))  # ['lxx_sb', 'hxx_sb']

用filter实现
names = ['egon', 'lxx_sb', 'hxx_sb']
res = filter(lambda name:name.endswith("sb"), names)
print(list(res))  # ['lxx_sb', 'hxx_sb']


# 3、reduce
from functools import reduce
res = reduce(lambda x,y:x+y, "hello", "xxx")
print(res)  # xxxhello

from functools import reduce
res = reduce(lambda x,y:x+y, [1, 2, 3], 100)
print(res)  # 106


# 4、set可变集合/frozenset不可变集合
s = frozenset({1, 2, 3})   # 不可变
s1 = set({1, 2, 3})        # 可变
s1.add(4)
print(s1)   # {1, 2, 3, 4}


# 5、locals局部变量/globals全局变量
x = 3333
def func():
    x = 1
    y = 2
    print(locals())
    print(globals())
func()  # {'x': 1, 'y': 2}
	# {'__name__': '__main__', ...}

a = 1111
print(locals())  # {'__name__': '__main__', ...}
print(globals()) # {'__name__': '__main__', ...}
print(locals() is globals())  # True


# 6、hash返回散列值
print(hash("asaasdf"))  # -9196404338305892359


# 7、pow(base ** exp % mod)
res = pow(10, 3, 3)     # 10 ** 3 % 3
print(res)              # 1


# 8、reversed 反序
res = reversed([11, 333, 222, 77])
print(list(res))       # [77, 222, 333, 11]没有排序,直接反序


# 9、round 四舍五入
print(round(3.5))      # 4


# 10、slice 切片
l = [11, 22, 33, 44, 55, 66, 77, 88, 99]
res = l[0: 7: 2]
print(res)  # [11, 33, 55, 77]

用slice实现
l = [11, 22, 33, 44, 55, 66, 77, 88, 99]
s = slice(0, 7, 2)
res = l[s]
print(res)  # [11, 33, 55, 77]


# 11、zip 拉链
s1 = "hello"
l = [11, 22, 33, 44, 55, 66]
res1 = zip(s1, l)
print(list(res1))  # [('h', 11), ('e', 22), ('l', 33), ('l', 44), ('o', 55)]
res2 = zip(l, s1)
print(list(res2))  # [(11, 'h'), (22, 'e'), (33, 'l'), (44, 'l'), (55, 'o')]
zip多的元素会直接丢掉

6.12 迭代器

6.12.1 迭代器概述

  • 迭代器指的是迭代取值的工具

  • 迭代是一个重复的过程(不是单纯重复),每次重复都是基于上一次的结果进行的

    count = 1

    while count < 5:

    ​ print(count)

    ​ count += 1

  • 使用迭代器的目的

    Ⅰ 为了找到一种通用的迭代取值的方案

    Ⅱ 节省内存

6.12.2 使用迭代器

  • 可迭代对象

    内置有__iter__方法的类型都称之为可迭代对象

    但凡调用了__iter__方法,就会将该类型转换成迭代对象

    ​ res = 值.__iter__() 或 res = iter(值)

  • 迭代器对象

    Ⅰ 内置有__next__方法

    Ⅱ 内置有__iter__方法(为了方便for循环)

  • 迭代器对象一定是可迭代对象

  • 可迭代对象不一定是迭代器对象

6.12.3 迭代器优缺点

  • 优点:

    Ⅰ 提供了一种不依赖索引的迭代取值方案

    Ⅱ 惰性计算,节省内存

  • 缺点:

    Ⅰ 取值麻烦

    Ⅱ 无法预测值的长度

    Ⅲ 是一次性的

6.12.4 可迭代对象

"hello".__iter__()              # 字符串
[].__iter__()                   # 列表
(11,).__iter__()                # 元组
{"k1": 12, }.__iter__()         # 字典
{11, 22}.__iter__()             # 集合
f = open("a.txt", mode="wt")    # 文件
f.__iter__()

6.12.5 迭代器对象

l = [11, 22, 33]
iter_l = l.__iter__()
print(iter_l.__next__())    # 11
print(iter_l.__next__())    # 22
print(iter_l.__next__())    # 33

info = {'name': "ccc", "age": 18, "gender": 'male'}
iter_info = info.__iter__()
print(iter_info.__next__())     # name
print(iter_info.__next__())     # age
print(iter_info.__next__())     # gender
print(iter_info.__next__())     # 抛出异常StopIteration
迭代器的iter与本身
print(iter_info.__iter__().__iter__().__iter__() is iter_info)  # True
l = [11, 222, 333, 444, 555]
iter_l = iter(l)  # l.__iter__
while True:
    try:
        print(next(iter_l))
    except StopIteration:
        break
# 11
# 222
# 333
# 444
# 555

l = {'name': "ccc", "age": 18, "gender": 'male'}
iter_l = iter(l)
while True:
    try:
        print(next(iter_l))
    except StopIteration:
        break

print('--------------------')
while True:
    try:
        print(next(iter_l))
    except StopIteration:
        break
# name
# age
# gender
# --------------------

l = {'name': "ccc", "age": 18, "gender": 'male'}
iter_l = iter(l)
while True:
    try:
        print(next(iter_l))
    except StopIteration:
        break

print('--------------------')
iter_l = iter(l)
while True:
    try:
        print(next(iter_l))
    except StopIteration:
        break
# name
# age
# gender
# --------------------
# name
# age
# gender

l = [11, 222, 333, 444, 555]
iter_l = iter(l)
for item in iter_l:
    print(item)
print('====================')
for item in iter_l:
    print(item)
# 11
# 222
# 333
# 444
# 555
# ====================

l = [11, 222, 333, 444, 555]
iter_l = iter(l)
for item in iter_l:
    print(item)
print('====================')
iter_l = iter(l)
for item in iter_l:
    print(item)
# 11
# 222
# 333
# 444
# 555
# ====================
# 11
# 222
# 333
# 444
# 555
  • for循环原理
for循环原理:
1、调用可迭代对象.__iter__(),拿到一个迭代器对象
2、调用next(迭代对象),将返回值赋值变量item
3、循环往复,直到抛出异常Stopiteration,for会检测异常然后结束循环

f = open('a.txt', mode='rt', encoding='utf-8')
for line in f:
    print(line)
print('=======')
for line in f:
    print(line)
f.close()
# 111
#
# 222
#
# 333
#
# 444
# =======

6.13 生成器

6.13.1 基本概念

  • 生成器:生成器就是一种自定义迭代器(生成器内置iter和next方法)

  • 使用生成器是为了省内存

  • 使用生成器的方式:

    函数体内但凡出现yield关键字,调用函数不会触发函数体代码的运行,而是会返回一个生成器对象

  • yield与return

    相同点:在返回值角度的用法一致

    不同点:Ⅰ yield可以返回多次值,return只能返回一次

    ​ Ⅱ yield可以暂停函数,然后可以用next方法触发函数体代码的运行->协程(或用list)

6.13.2 基本使用

def func():
    print("111")
    yield 1
    print("222")
    yield 2
    print("333")
    yield 3
    print("444")
    
g = func()	      # 生成器本质就是一个迭代器
print(g)	      # <generator object func at 0x0000024D38B57900>

res = next(g)	      # 111
print(res)	      # 1
res = next(g)	      # 222
print(res)	      # 2
res = next(g)	      # 333
print(res)	      # 3
next(g)		      # 444 检测异常StopIteration
g = func()
print(g)
for i in g:
    print(i)
# <generator object func at 0x0000020C4D217900>
# 111
# 1
# 222
# 2
# 333
# 3
# 444
def my_range(start, stop, step=1):
    while start < stop:
        yield start
        start += step

for i in my_range(0, 5, 2):
    print(i)
# 0
# 2
# 4

6.14 三元表达式

6.14.1 语法

res = "条件成立时返回的值" if "条件" else "条件不成立时返回的值"

6.14.2 使用场景

def max2(x, y):
    if x > y:
        return x
    else:
        return y
res = max2(1, 2)
# 可用三元表达式一行解决
x = 1
y = 2
res = x if x > y else y

6.14.3 生成式

[表达式 for i in 可迭代对象 if 条件]
{k:v for i in 可迭代对象 if 条件}
{k for i in 可迭代对象 if 条件}

6.14.3.1 列表生成式

l = []
for i in range(8):
    l.append(i)
print(l)  # [0, 1, 2, 3, 4, 5, 6, 7]

# 可用列表生成式一行解决
l = [i for i in range(10)]
print(l)  # [0, 1, 2, 3, 4, 5, 6, 7]
names = ["egon", "lxx_sb", "hxx_sb"]
name = []
for item in names:
    if item.endswith('sb'):
        name.append(item)
print(name)  # ['lxx_sb', 'hxx_sb']

# 可用列表生成式一行解决
names = ["egon", "lxx_sb", "hxx_sb"]
name = [name for name in names if name.endswith('sb')]
print(name)  # ['lxx_sb', 'hxx_sb']

6.14.3.2 字典生成式

res = {i: i for i in range(5) if i > 2}
print(res)  # {3: 3, 4: 4}

6.14.3.3 集合生成式

res = {i for i in range(5) if i > 2}
print(res, type(res))  # {3, 4} <class 'set'>

6.14.4 生成器表达式

6.14.4.1 创建生成器对象的两种方式

  • 调用带yield的函数

  • 生成器表达式,将列表生成式的[]换成()即可

(expression for item in iterable if condition)

6.14.4.2 较列表生成式的比较

  • 列表生成式返回的是一个列表,生成器表达式返回的是一个生成器对象

  • 生成器表达式更加节省内存(一次只产生一个值在内存中)

  • 如果要读取一个大文件的字节数应该是基于生成器表达式的方式完成的

6.14.4.3 使用

res = (i for i in range(5))
print(res)		# <generator object <genexpr> at 0x00000274941E7900>
print(next(res))	# 0
print(next(res))	# 1
print(next(res))	# 2
print(next(res))	# 3
print(next(res))	# 4
print(next(res))	# 抛出异常StopIteration
with open(r'a.txt', mode='rt', encoding='utf-8') as f:
    res = 0
    for line in f:
        res += len(line)
    print(res)		# 15
    
# 生成式表达式
with open(r'a.txt', mode='rt',encoding='uyf-8') as f:
    res = sum(len(line) for line in f)
    print(res)		# 15	依次执行next(res),然后累加到一起得到结果

6.14.5 附录a.txt

111
222
333
444

6.15 面向过程编程

6.15.1 面向过程简介

  • 核心是过程二字

  • 过程指的是解决问题的步骤,即先干什么后干什么再干什么

  • 基于面向过程开发程序好比是在设计一条条流水线,是一种机械式的思维方式,正好契合计算机的运行原理

  • 计算机运行原理:任何程序的执行最终都要转换成cpu的指令流水按过程调度执行

    即无论采用什么语言、何种编程范式设计出的程序,最终的执行都是过程式的

6.15.2 总结

  • 优点:将复杂的问题流程化,进而简单化

  • 缺点:程序的可扩展性差

6.15.3 应用场景

面向过程的程序设计一般用于那些功能一旦实现之后就很少需要改变的场景,
如果你只是写一些简单的脚本,去做一些一次性任务,用面向过程去实现是极好的,
但如果你要处理的任务是复杂的,且需要不断迭代和维护,那还是用面向对象最为方便。

6.16 函数递归

6.16.1 基本概念

  • 函数递归调用:在调用一个函数的过程中又调用了自己

  • 本质:循环的过程–>用函数来实现的循环

  • 递归调用必须在满足某种条件下结束,不能无限递归调用下去

  • python不是函数式编程语言,无法对递归进行尾递归优化

  • 递归的两个阶段:

    Ⅰ 回溯–>一层层往下爬

    Ⅱ 递推–>一层层往上爬

6.16.2 示例

def f1():
    print("from f1")
    f1()

f1()  # 先递归执行,超过最大限制后报错RecursionError
# 可用getrecursionlimit修改限制
import sys
print(sys.getrecursionlimit())		# 默认限制是1000
sys.setrecursionlimit(2000)		# 可修改,但一般不修改(受主机操作系统栈大小限制)
print(sys.getrecursionlimit(2000))      # 此时修改为2000

6.16.3 回溯与递推

# 问题
某公司四个员工坐在一起,问第四个人薪水,他说比第三个人多1000,问第三个人薪水,第他说比第二个人多1000,问第二个人薪水,他说比第一个人多1000,最后第一人说自己每月5000,请问第四个人的薪水是多少?

# 思路解析
要知道第四个人的月薪,就必须知道第三个人的,第三个人的又取决于第二个人的,第二个人的又取决于第一个人的,而且每一个员工都比前一个多一千,数学表达式即
salary(4)=salary(3)+1000 
salary(3)=salary(2)+1000 
salary(2)=salary(1)+1000 
salary(1)=5000
总结为: 
salary(n)=salary(n-1)+1000 (n>1) 
salary(1)=5000 (n=1)

# 回溯阶段
要求第n个员工的薪水,需要回溯得到(n-1)个员工的薪水,以此类推,直到得到第一个员工的薪水,此时,salary(1)已知,因而不必再向前回溯了。然后进入递推阶段:从第一个员工的薪水可以推算出第二个员工的薪水(6000),从第二个员工的薪水可以推算出第三个员工的薪水(7000),以此类推,一直推算出第第四个员工的薪水(8000)为止,递归结束。需要注意的一点是,递归一定要有一个结束条件,这里n=1就是结束条件。

# 代码实现
def salary(n):
    if n == 1:
        return 5000
    return salary(n-1)+1000

s=salary(4)
print(s)  # 8000

# 程序分析
在未满足n==1的条件时,一直进行递归调用,即一直回溯。而在满足n==1的条件时,终止递归调用,即结束回溯,从而进入递推阶段,依次推导直到得到最终的结果。
items=[[1, 2], 3, [4, [5, [6, 7]]]]
def foo(items):
    for i in items:
        if isinstance(i, list):  #满足未遍历完items以及if判断成立的条件时,一直进行递归调用
            foo(i) 
        else:
            print(i, end=' ')

foo(items)                       #打印结果1 2 3 4 5 6 7

6.13.4 总结

使用递归,我们只需要分析出要重复执行的代码逻辑,然后提取进入下一次递归调用的条件或者说递归结束的条件即可,代码实现起来简洁清晰

Tags: