方向不对,努力白费,经验类测试技术才是职场重要保险! | (点击→)【提醒】AI赋能的前提是对常规测试技术非常的熟悉,联系作者vx了解

Python【第四篇】函数、内置函数、递归、装饰器、生成器和迭代器

一、函数(含函数嵌套、异步函数)

函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可

特性:

  1. 减少重复代码
  2. 使程序变的可扩展
  3. 使程序变得易维护

1.定义

def 函数名(参数):        
    ...
    函数体
    ...
    返回值

函数的定义主要有如下要点:

  • def:表示函数的关键字
  • 函数名:函数的名称,可根据函数名调用函数
  • 函数体:函数中进行一系列的逻辑计算
  • 参数:为函数体提供数据
  • 返回值:当函数执行完毕后,可以给调用者返回数据。

2.参数

函数的有三种不同的参数:

  • 普通参数(也称为位置参数)
  • 默认参数
  • 动态参数

普通参数

#定义函数
#n是函数name的形式参数,简称:形参

def name(n):
    print(n)  # jack

#执行函数 
#'jack'是函数name的实际参数,简称:实参
name('jack')

默认参数

def func(name, age = 18):
    print("%s:%s"%(name,age))

# 指定参数
func('jack', 19)  # 上面输出jack:19
# 使用默认参数
func('jack')  # 上面输出jack:18

注:默认参数需要放在参数列表最后

动态参数(*args) 

def func(*args):
    print(args)

# 执行方式一
func(11,22,33,55,66)  # 上面输出(11, 22, 33, 55, 66)

# 执行方式二
li = [11,22,33,55,66]  # 上面输出(11, 22, 33, 55, 66)
func(*li)

动态参数(**kwargs) 

def func(**kwargs):
    print(kwargs)

# 执行方式一
func(name='jack',age=18)  # 上面输出{'name': 'jack', 'age': 18}

# 执行方式二
li = {'name':'jack', 'age':18, 'job':'pythoner'}
func(**li)  # 上面输出{'name': 'jack', 'age': 18, 'job': 'pythoner'}

参数顺序:位置参数、默认参数(即关键字参数,形参中如果默认参数后面有可变位置参数,实参中,这个默认参数不能写成关键字参数样式,只能写一个值,即位置参数的样子)、可变位置参数、可变关键字参数。

def hi(a,*args,**kwargs):
    print(a,type(a))  # 11 <class 'int'>
    print(args,type(args))  # (22, 33) <class 'tuple'>
    print(kwargs,type(kwargs))  # {'k1': 'jack', 'k2': 'tom'} <class 'dict'>
hi(11,22,33,k1='jack',k2='tom')

3.返回值

函数外部的代码要想获取函数的执行结果,就可以在函数里用return语句把结果返回。

def stu_register(name, age, course='python' ,country='CN'):
    print("----注册学生信息------")
    print("姓名:", name)
    print("age:", age)
    print("国籍:", country)
    print("课程:", course)
    if age > 22:
        return False
    else:
        return True

registriation_status = stu_register("老王",22,course="PY全栈开发",country='JP')

if registriation_status:
    print("注册成功")
else:
    print("too old to be a student.")

注意:

  • 函数在执行过程中只要遇到return语句,就会停止执行并返回结果,所以也可以理解为 return 语句代表着函数的结束
  • 如果未在函数中指定return,那这个函数的返回值为None

4.全局、局部变量

在函数中定义的变量称为局部变量,在程序的一开始定义的变量称为全局变量。
全局变量作用域是整个程序,局部变量作用域是定义该变量的函数。
当全局变量与局部变量同名时,在定义局部变量的函数内,局部变量起作用;在其它地方全局变量起作用。

全局变量在函数里可以随便调用,但要修改就必须用 global 声明 

# 全局变量
P = 'jack'

def name():
    global P  # 声明修改全局变量
    P = 'jenny'  # 局部变量
    print(P)  # jenny

def name2():
    print(P)  # jenny

name()
name2()  # jenny

 

函数嵌套:https://chuna2.787528.xyz/uncleyong/p/19505455

 

异步函数:https://chuna2.787528.xyz/uncleyong/p/19516222

 

二、内置函数

Python的内置函数有许多,如下图:

 

print(dir(__builtins__))

  

结果

['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BaseExceptionGroup', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EncodingWarning', 'EnvironmentError', 'Exception', 'ExceptionGroup', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'PythonFinalizationError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '_IncompleteInputError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'aiter', 'all', 'anext', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

 

# 匿名函数,冒号前面是形参,冒号后面是函数体,并将结果return到函数调用处
f = lambda a, b: a + b
print(f(2, 3))  # 5

# abs() 取绝对值
print(abs(-111))  # 111

# all() 循环可迭代对象的每个元素,都为真则返回True,否则返回假
# 0,None ,"",[],(),{} 是假的
print(all([11, 22]))  # True

# any 有一个为真,全部都为真
print(any([0, 0, None]))  # False

# bin 将十进制转换成2进制
# oct() hex()
print(bin(11))  # 0b1011

# chr() 找到数字对应的ascii码
# ord() ascii码对应的数字
# chr ord 只适用于ascii码
print(chr(65))  # A
print(ord('A'))  # 65

# divmod 返回除法的(值,余数)
print(divmod(10, 3))  # (3,1)

# eval 计算器的功能 返回结果
print(eval('a+60', {'a': 90}))  # 150

# exec,执行python代码,没有返回值
exec("for i in range(5):print(i)")  # 直接循环输出0,1,2,3,4


# filter(函数,可迭代的对象)
# 循环可以迭代的对象,传入函数中执行,如果不符合就过滤
def fun(s):  # 定义判断一个数是否是偶数的函数
    if s % 2 == 0:
        return True
    else:
        return False

ret = filter(fun, [1, 2, 3, 4, 5, 6, 7, 8])
for i in ret:
    print(i)  # 打印出2,4,6,8

# 用匿名函数改写一下
ret1 = filter(lambda x: x % 2 == 0, [1, 2, 3, 4, 5, 6, 7, 8])
for i in ret1:
    print(i)  # 2,4,6,8

# map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回
ret = map(lambda x: x + 100, [1, 2, 3])
for i in ret:
    print(i)  # 101,102,103

# globals() 获取当前文件的所有全局变量
# locals()  获取当前文件的所有局部变量
# hash()    获取哈希值
# isinstance 看某个对象是不是某个类创建的

# iter() 创建一个可以被迭代的对象 next()取下一个值
k = iter([1, 2, 3, 4])
print(next(k))  # 1

# pow() 求指数
print(pow(2, 10))  # 1024

# round() 四舍五入
# zip
l1 = [1, 2, 3, 4]
l2 = ['a', 'b', 'c', 'd']
k = zip(l1, l2)
for i in k:
    print(i)  # 打印出(1,a),(2,b)....
a = [1, 2, 3, 4, 5]
b = ['aaa', 'bbb', 'ccc', 'ddd']
c = [111, 222, 333, 444]
for i in zip(a, b, c):
    print(i)
# (1, 'aaa', 111)
# (2, 'bbb', 222)
# (3, 'ccc', 333)
# (4, 'ddd', 444)

for i, j, k in zip(a, b, c):
    print(i, j, k)
# 1 aaa 111
# 2 bbb 222
# 3 ccc 333
# 4 ddd 444

# zip应用
for m, n in zip(title_list, content_list):  # 把标题和图片对个对应
    print('正在下载>>>>>:' + m, n)

三、递归

递归算法是一种直接或者间接地调用自身算法的过程。在计算机编写程序中,递归算法对解决一大类问题是十分有效的,它往往使算法的描述简洁而且易于理解。 
递归算法解决问题的特点:
  • 递归就是在过程或函数里调用自身。
  • 在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。
  • 递归算法解题通常显得很简洁,但递归算法解题的运行效率较低。所以一般不提倡用递归算法设计程序。
  • 递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。所以一般不提倡用递归算法设计程序。

用递归写一个阶乘函数 f(n)算出n的阶乘

def f(n):
    if n==0:  # n=0的话直接返回空,对用户输入的零进行判断
        return None
    elif 1==n: # n=1的话就不再递归
        return n
    else:
        return n*f(n-1)  # 递归在执行f(n-1) 直到f(1)
print(f(5))  # 120
'''
    f(5)的执行过程如下
        ===> f(5)
        ===> 5 * f(4)
        ===> 5 * (4 * f(3))
        ===> 5 * (4 * (3 * f(2)))
        ===> 5 * (4 * (3 * (2 * f(1))))
        ===> 5 * (4 * (3 * (2 * 1)))
        ===> 5 * (4 * (3 * 2))
        ===> 5 * (4 * 6)
        ===> 5 * 24
        ===> 120
'''

利用函数编写如下数列:

斐波那契数列指的是这样一个数列 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368...

用递归获取斐波那契数列中的第10个数

def fun(n): # fun(10)即可计算第十个斐波拉数
    if 1==n : # 直接定义前面两个数为 0 ,1,如果输入的n为1,2的话直接就返回了
        return 0
    elif 2==n:
        return 1
    else:
        return fun(n-1)+fun(n-2) #如果输入的不是1,2则进行递归出来计算他前面两个数的和
 
'''
    fun(5)的执行过程如下(fun(10)的结果为34)
        ===> fun(5)
        ===> fun(4)+fun(3)
        ===> fun(3)+fun(2) + fun(2)+fun(1)
        ===> fun(2)+fun(1)+fun(2)+fun(2)+fun(1)
        ===> 1+0+1+1+1+0
        ===> 3
'''

四、装饰器

本质是函数,装饰其它函数,为其它函数添加附加功能,原则:

  • 不能修改被装饰函数的源代码
  • 不能修改被装饰的函数的调用方式

装饰器对被装饰的函数是透明的,即:被装饰的函数感知不到装饰器的存在,因为没改函数的代码,运行方式也没变

装饰器执行顺序

def login(func): # 1,3
    def inner(arg): # 4,7
        print('yanzheng...') # 8
        func(arg) # 9
    return inner # 5

@login # 2,10
def tv(name):
    print('welcome %s' %name) # 11

tv('qzcsbj') # 6

 

统计函数执行时间

import time
def timmer(func):
    def warpper(*args, **kwargs):
        start_time = time.time()
        func()
        stop_time = time.time()
        print('the func run time is %s' %(stop_time-start_time))
    return warpper

@timmer
def test1():
    time.sleep(1)
    print('in the test1')
test1()

五、生成器和迭代器

生成器

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。要创建一个generator,有很多种方法。

方法一:列表生成式的[]改成()

第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

b = (i * 2 for i in range(5))  # 列表生成式的[]改为(),就变成了生成器
print(b, type(b))
print(next(b))
print(b.__next__())
print('----------------')
for i in b:
    print(i)

  

结果

<generator object <genexpr> at 0x000001DF99378520> <class 'generator'>
0
2
----------------
4
6
8

 

方法二:通过函数写生成器

函数中加yield(暂停),表示可以将函数变成生成器,调用函数时得到一个生成器,yield之后的代码不执行,yield就返回生成器地址,next调用生成器,可以像return一样,返回值,但是不会像return返回一次函数就终止了,而且是在执行过程中,可以多次将数据或者状态返回到next调用处(可以返回函数循环体中每次产生的值),如,读取一个大文件,一边读一边返回。此时脚本中的return信息只在抛出异常的时候打印。

示例一:

# 这段代码实现了一个斐波那契数列的生成器函数
def fib(max):  # max控制生成斐波那契数列的个数
    n, a, b = 0, 0, 1  # n 是计数器,记录当前已生成的数字个数;a, b 分别代表斐波那契数列中的前两个数
    while n < max:  # 当计数未达到最大值时继续执行
        print('before yield')
        # print(b)
        yield b  # 把函数的执行过程冻结在这一步,并且把b的值返回给外面的next(),下一次next()从下面这行继续运行
        a, b = b, a + b
        n = n + 1
    return 'done'


f = fib(3)  # 调用函数,此时函数并未立即执,将函数变成一个生成器,将地址返回给f
print(f)  # 输出生成器对象的内存地址
print(f.__next__())  # f.__next__() 或 next(f) - 触发生成器执行到下一个yield位置
print("----------------")
print(next(f))
print("=================")
print(f.__next__())
print(next(f))


D:\qzcsbj\fast\.venv\Scripts\python.exe D:\qzcsbj\fast\test.py
<generator object fib at 0x000002284A6B5620>
before yield
1
----------------
before yield
1
=================
before yield
2
Traceback (most recent call last):
  File "D:\qzcsbj\fast\test.py", line 20, in <module>
    print(next(f))
          ~~~~^^^
StopIteration: done


# 执行流程分析
# 第一次调用next():打印"before yield",返回第一个数1
# 第二次调用next(f):打印"before yield",返回第二个数1
# 第三次调用f.__next__():打印"before yield",返回第三个数2
# 第四次调用next(f):由于已达到max限制,抛出StopIteration异常

# 生成器: 使用yield关键字的函数会变成生成器,可节省内存空间
# 惰性求值: 值是在需要时才计算,而不是一次性全部生成
# 这种实现方式非常适合处理大量数据,因为不需要将所有结果存储在内存中。

 

示例二:同步生成器(1个或者多个yield)

  • 返回类型:返回普通生成器对象(generator)
  • 调用行为:函数调用返回生成器,可以使用普通 for 循环
  • 消费方式:使用普通 for 循环遍历
  • 执行特点:同步阻塞式执
import time


def stream_generator(text, delay=0.5):
    """流式文本生成器(逐字符)"""
    for char in text:
        yield char  # 每次yield一个字符
        time.sleep(delay)


if __name__ == "__main__":
    text = "hello,qzcsbj"

    res = stream_generator(text)
    print(res, type(res))
    # 消费生成器
    for char in res:
        print(char, end='', flush=True)  # 实时输出
    print("\n完成!")

  

输出结果

<generator object stream_generator at 0x000001C8D4965620> <class 'generator'>
hello,qzcsbj
完成!

  

生成器可以for,也可以next

def f1():
    print('f1...')
    yield 'hello'


if __name__ == '__main__':
    res = f1()
    print(res)
    # print(next(res))  # 等价下面两句
    for i in res:
        print(i)

  

结果:

<generator object f1 at 0x000002330AB45A80>
f1...
hello

 

可以多个yield

def hello():

    print("hello")

    yield "开始。。。。。。。。。。。"

    print("python")
    for i in range(3):
        yield i

    yield "结束。。。。。。。。。。。"

if __name__ == "__main__":
    a = hello()
    print(a,type(a))
    for i in a:
        print(i)

  

返回结果:哪怕函数第一行不是yield语句,调用的时候也不会执行(函数第一行打印在打印a后面执行的)

<generator object hello at 0x0000020D752A6800> <class 'generator'>
hello
开始。。。。。。。。。。。
python
0
1
2
结束。。。。。。。。。。。

 

 

示例三:异步生成器(async + yield)

  • 返回类型:返回异步生成器对象(async generator)
  • 调用行为:函数调用不会立即执行,而是返回一个协程对象
  • 消费方式:必须使用 async for 循环来遍历
  • 执行特点:支持异步非阻塞操作
import time


async def stream_generator(text, delay=0.5):
    """流式文本生成器(逐字符)"""
    for char in text:
        yield char  # 每次yield一个字符
        time.sleep(delay)



if __name__ == "__main__":
    text = "hello,qzcsbj"
    res = stream_generator(text)
    print(res, type(res))

  

输出结果:

<async_generator object stream_generator at 0x000002A2E3D85620> <class 'async_generator'>

  

消费生成器,for前要加async,并且asycn for需要放到另外一个asycn函数中,不在yield异步生成器函数里面

import asyncio
import time


async def stream_generator(text, delay=0.5):
    """流式文本生成器(逐字符)"""
    for char in text:
        yield char  # 每次yield一个字符
        time.sleep(delay)


async def call_stream_generator(text):
    """调用流式文本生成器"""
    stream = stream_generator(text)
    print(stream, type(stream))
    # 消费生成器
    async for char in stream:
        print(char, end='', flush=True)  # 实时输出
    print("\n完成!")


if __name__ == "__main__":
    text = "hello,qzcsbj"
    asyncio.run(call_stream_generator(text))
    # res = stream_generator(text)
    # print(res, type(res))

  

输出结果:

<async_generator object stream_generator at 0x0000014379958BA0> <class 'async_generator'>
hello,qzcsbj
完成!

 

迭代器

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator

 

另外,可以直接作用于for循环的数据类型有以下几种:

  • 一类是集合数据类型,如list、tuple、dict、set、str等;
  • 一类是generator,包括生成器和带yield的generator function。

 

区别:

  • 可迭代对象:Iterable(可以直接作用于for循环)
  • 迭代器:Iterator可以被next()函数调用并不断返回下一个值

 

生成器和迭代器的区别

迭代器(Iterator)

  • 定义方式:通过实现 iter() 和 next() 方法的类来创建
  • 状态管理:需要手动维护内部状态(如索引、计数器等)
  • 内存占用:通常需要预先存储所有数据项
  • 创建复杂度:需要编写完整的类定义和状态管理逻辑

生成器(Generator)

  • 定义方式:使用 yield 关键字的函数,或者生成器表达式
  • 状态管理:Python 自动管理执行状态和局部变量
  • 内存占用:惰性求值,按需生成数据,节省内存
  • 创建复杂度:语法简洁,只需关注业务逻辑

 

代码实现差异

# 迭代器需要完整的类定义
class MyIterator:
    def __init__(self, data):
        self.data = data
        self.index = 0
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.index >= len(self.data):
            raise StopIteration
        value = self.data[self.index]
        self.index += 1
        return value

# 生成器只需要 yield 关键字
def my_generator(data):
    for item in data:
        yield item

  

内存效率

  • 迭代器:可能需要预先创建包含所有数据的列表
  • 生成器:逐个产生值,无需存储整个数据集

执行方式

  • 迭代器:一次性加载到内存
  • 生成器:暂停和恢复执行状态,yield 处暂停,下次调用时从暂停点继续

关系总结

  • 生成器是一种特殊的迭代器:所有生成器都是迭代器,但不是所有迭代器都是生成器
  • 生成器是迭代器协议的简化实现:提供了更简洁的语法来创建迭代器
  • 功能相同但实现方式不同:都能被 for 循环遍历,都支持 next() 函数调用

 

示例:所有生成器是迭代器
from types import GeneratorType
from typing import Iterator

b = (i * 2 for i in range(5))  # 列表生成式的[]改为(),就变成了生成器
print(b, type(b))
print(isinstance(b, GeneratorType))
print(isinstance(b, type((x for x in []))))
print(isinstance(b, Iterator))
print(next(b))
print(b.__next__())
print('----------------')
for i in b:
    print(i)

 

结果:

<generator object <genexpr> at 0x0000017C059CA9B0> <class 'generator'>
True
True
True
0
2
----------------
4
6
8

 

示例:不是所有迭代器都是生成器
# 列表迭代器示例
my_list = [1, 2, 3]
list_iterator = iter(my_list)

print(type(list_iterator))  # <class 'list_iterator'>
print(isinstance(list_iterator, type((x for x in []))))  # False,不是生成器类型

 

 

示例:list、dict、str虽然是Iterable,却不是Iterator(迭代器)。把list、dict、str等Iterable变成Iterator可以使用iter()函数
from collections.abc import Iterator

# 列表是可迭代对象,但不是迭代器,需要通过 iter() 转换才能成为迭代器
a = [1, 2, 3]
print(isinstance(a, Iterator))  # False
b = iter(a)
print(isinstance(b, Iterator))  # True
print(b.__next__())  # 1
print(next(b))
print('-------------')
for i in b:
    print(i)

  

结果:

False
True
1
2
-------------
3  

 

六、练习题

笔试题汇总(linux、shell、mysql、java、python、性能、自动化、docker、k8s等): 

https://chuna2.787528.xyz/uncleyong/p/11119489.html

 

posted @ 2016-10-10 19:51  全栈测试笔记  阅读(1994)  评论(0)    收藏  举报
浏览器标题切换
浏览器标题切换end