函数

4. 函数

title: 函数
author: wickyo
tags:

  • python

categories:

  • python

date: 2021-06-10 20:11:00

函数的定义

  • 在程序中,函数就是具备某一功能的工具,需要先定义,后调用
  • 函数定义过程只检测语法,不执行代码
  • 使用函数可以解决这三个问题:
    • 代码不冗长
    • 代码的扩展性好
    • 代码的可读性好
'''
def 函数名(等同于变量名):
    """对函数的描述信息"""
    代码块
'''
# 定义函数
def guess():
    """给定两个数,打印较大的数"""
    s = 10
    y = 20

    if s > y :
        print(s)
    else:
        print(y)

# 调用函数      
guess()   

print(guess._doc_)  # 打印注释内容
print(len._doc_)  # 打印len内置方法的作用    

定义函数的三种形式

空函数

占位作用

def func():
    pass

无参函数

函数定义阶段括号内没有参数的函数

def guess():
    """给定两个数,打印较大的数"""
    s = 10
    y = 20

    if s > y :
        print(s)
    else:
       print(y)

有参函数

函数定义阶段括号内有参数的函数,调用时必须传入参数

def guess(x,y):
    """给定两个数,打印较大的数"""
    if x > y :
        print(x)
    else:
        print(y)

函数的返回值

返回值就是函数内部代码经过一些逻辑处理后得到的结果,通过return将值返回出去

  • return会终止函数,不运行下面代码,假设有多个return,遇到第一个就会终止
  • 没有return默认返回None,没有返回值也返回None
  • return可以返回多个值(逗号分隔),返回值以元组接收
    • 0个:返回None
    • 1个:该值本身(可以是任意数据类型)
    • 多个:返回值是元组
def guess():
    """给定两个数,打印较大的数"""
    s = 10
    y = 20
    if s > y :
        return s
    else:
        return y

num = guess()
print(num)  # 20

函数的调用

函数名加括号即为调用函数,不加括号为函数地址

guess()  # 调用函数 
print(guess)  # 函数的地址

函数的参数

形参

函数定义阶段的产物,可以接收实参,具有描述意义

位置形参

从左到右,依次接收实参的值

默认形参

  • 如果调用时不传值,使用默认值,如果传值了,使用传的值
  • 默认形参必须放在位置形参后面

实参

函数调用阶段的产物,传给形参一个具体的值,具有具体的值(可以为任意数据类型)

位置实参

从左到右,依次给位置形参传值,一一对应

关键字实参

  • 按照形参名给形参传值(使用情况基本没有)
  • 关键字实参必须在位置实参后面

函数的参数一般0~3个,一般不超过3个

可变长参数

可变长参数是指在调用函数时,传入的参数个数可以不固定

可变长形参

  • *:形参中的*会将多余的位置实参以元组形式接收并存储,再赋值给*后面的参数,注意:*后的参数名约定俗成为args

    def func(*args):
        print(args)
    
    func(1,2,3)  # (1, 2, 3)
    
  • **:形参中的**会将多余的关键字实参以字典形式接受并存储,再赋值给**后面的参数,注意:**后的参数名约定俗成为kwargs

    def func(**kwargs):
        print(kwargws)
    func(a=5)  # {'a':5}
    
    def f1(*args,*kwargs):  # 可接受所有类型
        print(args,kwargs)
    

可变长实参

  • *:实参中的*会将*后参数(元组或列表)的值循环取出,打散成位置实参

    def func(x, y, z, ):
        print(x, y, z)
    
    lt = (1,2,3)    
    func(*lt)  # 1 2 3
    
  • **:实参中的**会将**后参数(字典)的值循环取出,打散成关键字实参

    def func(x, y, z,):
        print(x, y, z)
    
    dic = {'x':1,'y':2,'z':3}    
    func(**dic)  # 1 2 3
    

函数对象

python中一切皆对象,函数也是对象,对象的四大功能(函数对象为例):

引用

def f1():
    print('from f1')

# 函数对象 = 函数名
func = f1
func()  # f1()

容器元素

lt = [f1,1,2,3]
lt[0]()  # f1()

作为函数的实参

def f2(f2_f1):
    print(f2_f1)
    f2_f1()

f2(f1)
print(f1)

作为函数的返回值

def f2(f2_f1):
    return(f2_f1)

res = f2(f1)  #f1
res()  # f1()

函数嵌套

函数内部定义的函数,无法在函数外部使用

def f1():
    def f2():
        print('from f2')
    f2()

f2()  # NameError: name 'f2' is not defined

名称空间和作用域

内存中存储变量名和变量间绑定关系的空间叫做名称空间

内置名称空间

  • 存放python解释器自带的名字,如int、float、len
  • 生命周期:在解释器启动时生效,关闭时失效

全局名称空间

  • 除了内置和局部的名字之外,其余的都存放在全局名称空间中,如下面代码中的x、func、l、z
  • 生命周期:在文件执行时生效,执行结束后失效
x = 1
l = [1, 2]

if 3 > 2:
    if 4 > 3:
        z = 3

def func():
    pass        

局部名称空间

  • 用于存放函数调用期间函数体产生的名字,如下面代码的f2
  • 生命周期:在文件执行时函数调用期间生效,函数执行结束后失效
def f1():
    def f2():
        print('from f2')
    f2()

f1() 
  • 名称空间的生成顺序
    1. 内置名称空间:python解释器启动就会有
    2. 全局名称空间:执行代码时候才会有
    3. 局部名称空间:函数调用的时候才会有局部
  • 搜索顺序
    先从当前所在位置搜索,如果当前所在的位置为局部名称空间,则查找顺序为:局部--》全局--》内置,不会逆向寻找

作用域

  • 全局作用域:全局有效,包含内置名称空间和全局名称空间

  • 局部作用域:局部有效,只包含局部名称空间

  • 规则一:全局作用域的x和局部作用域的x没有关系

    x = 1  # 全局作用域
    
    def bar():
        x = 2  # 局部作用域
    
    print(x)  # 1
    
  • 规则二:局部作用域1的x和局部作用域2的x也没有关系,即使局部作用域1和局部作用域2在同一个局部

    def f1():
        def f2():
            def f3():
                x = 1
            x = 2
            f3()
            print(x)
        f2()
    
    f1()  # 2
    
  • global关键字
    修改全局作用域中的变量,让其成为全局变量,打破了上述规则一

    x= 10 
    
    def f1():
        global x  # 让global以下局部的x变成全局
        x = 20
    
    f1()
    print(x)  # 20
    
  • nolocal关键字
    修改局部作用域中的变量,让其成为上一层函数的局部,打破了规则二

    def f1():
        def f2():
            def f3():
                nonlocal x  # nonlocal让x成为上一层函数的局部,但是不能成为全局
                x = 1
            x = 2
            f3()
            print(x)
        f2()
    f1()  # 1    
    
  • python3中所有可变数据类型打破上述一切规则

    lt = [10]
    def f1():
        lt[0] = 11
    
    f1()
    print(lt)   # [11]
    
  • 思考题

    def f1(i,lt=[]):
        lt.append(i)
        print(lt)
    
    for i in range(3):
        f1(i)  # [0] [0,1] [0,1,2]
    
    def f1(i,lt):
        lt.append(i)
        print(lt)
    
    fori in range(3):
        f1(i,[]) # [0] [1] [2]
    

闭包函数

  • 闭包是指:将函数内部的变量和内部的函数两者包裹在一起,然后通过返回值返回出去(函数内部函数对外部作用域而非全局作用域的引用)
  • 闭包函数返回的是一个函数外包裹了一层作用域的函数对象,函数在定义阶段不执行代码,因此可以用用于延迟计算,爬虫领域
def outter():
    x = 1
    def inner():
        print(x)
    return inner

f = outter() # inner函数内存地址
f()  # 1

# 外部传参
def outter(x):
    def inner():
        print(x)
    return inner

f = outter(1)  # inner函数内存地址
f()  # 1

装饰器

装饰器是指在遵循以下原则的前提下为被装饰对象添加额外功能的函数(通过闭包函数的原理),本质上还是是函数

  • 装饰器遵循的原则

    • 不修改被装饰对象的源代码
    • 不修改被装饰对象的调用方式
  • 装饰器模板

    • 无参装饰器(两层装饰器)

      def deco(func):
          def wrapper(*args,**kwargs):
              # 功能
              res = func(*args,**kwargs)
              return res
          return wrapper
      
    • 有参装饰器(三层装饰器)

      def outter(param):
          def deco(func):
          def wrapper(*args,**kwargs):
                 # 功能,基于param的逻辑处理判断
                res = func(*args,**kwargs)
                 return res
            return wrapper
          return deco
      
  • 装饰器语法糖(使用方法)
    在被装饰函数正上方,并且是单独一行写上@装饰器名

    @deco  # 相当于index = deco(index)
    def index():
        pass
    

迭代器

迭代可以理解成,基于上一次结果的重复循环

可迭代对象

  • 具有__iter__方法的对象
  • 除了数字类型和函数外都是可迭代对象

迭代器对象

  • 含有__iter____next__方法的对象

  • 只有文件本身就是迭代器

  • 可迭代的对象执行__iter__方法得到的返回值就是迭代器对象

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

  • for循环原理
    for循环称为迭代器循环,in后必须是可迭代的对象

    # 底层实现原理,__next__可以迭代取值(可以不依赖索引)
    dic = {'a':1,'b':2,'c':3}
    dic_iter = idc.__iter__()
    while True:
        try:
            print(dic_iter.__next__())
        except Exception as e:
            break
    
  • range

    • Python2中range生成的是一个列表
    • Python3中range生成的是一个可迭代对象,更加节省内存资源

三元表达式

三元表达式只支持双分支结构

# 条件成立时的返回值 if 条件 else 条件不成立时的返回值
x = 10
y = 20
print(x if x > y else y)

列表推导式

lt = [i for i in range(4)]  # 0,1,2,3
lt = [i**2 for i in range(4)]  # 0,2,4,9

字典生成式

字典表达式一般与zip(拉链函数)列表里面包了元组连用

dic = {'a':1, 'b':2}
dic1 = {i for i in dic}  # {'a','b'}
dic2 = {k*2:v**2 for k,v in dic.items()}  # {'aa':2, 'bb':4}

# 拉链函数:
z = zip([1,2,3,4],[1,2,3,4])  # list(z):[(1, 1), (2, 2), (3, 3), (4, 4)]
dic = {k**2:v**2 for k,v in z}  # {1:1,2:2,3:3,4:4}

生成器

含有yield关键字的函数就叫做生成器,生成器的本质就是迭代器,生成器提供了非常方便的自定义迭代器的途径

  • yield关键字的特性
    1. yield会暂停函数,当运行下一次next时才会继续运行下面的代码
    2. 可以通过next获取值
  • return的特性
    1. 会终止函数
    2. 通过调用函数获取值
  • 生成器表达式
    • 把列表推导式的[]换成()就是生成器表达式
    • 节省内存,一次只产生一个值在内存中

递归

  • 函数递归就是函数内部直接或间接的调用了它本身,每一次递归不会结束函数,并且会开辟新的空间
  • 递归必须要有两个明确的阶段:
    • 递推:一层一层递归调用下去,进入下一层递归问题规模都将会减小
    • 回溯:递归必须要有一个明确的结束条件,在满足该条件开始一层一层回溯
# 递归的精髓在于通过不断地重复逼近一个最终的结果
count = 0
def a():
    global count
    count += 1
    print(count)
    if count == 100:
        return
    a()
a()

# 小明20岁,后面的同学依次大2岁,求第六位的同学的年龄
def age(x):
    if x == 0:
        return 18
    x -= 1
    return age(x) + 2

res = age(6)
print(res)  # 32

匿名函数

匿名函数没有绑定名字,使用一次就被收回,加括号可以直接运行,关键字:lambda

lambda 参数:<代码块>

匿名函数一般不单独使用,通常与内置函数filter、map、sorted和列表的sort方法联用

  • sort/max /min

    dic = {'a':100,'b':20,'c':50,'d':200}
    a = list(dic.items())
    a.sort(key=lambda i:i[1])
    
  • sorted
    重新创建一个列表排序

    lt = sorted(dic.items(),key=lambda i:i[1],reverse=True)
    
  • filter:过滤筛选,返回的是一个迭代器
    判断匿名函数的返回值得真假,真则留下

    print(list(filter(lambda i:i[1] > 100,dic.items())))  # ['d':200]
    
  • map:处理映射,返回的是一个迭代器

    print(list(map(lambda i:i[1]+2,dic.items())))  # [102,22,52,202]
    

内置函数

更多内置函数:https://docs.python.org/3/library/functions.html?highlight=built#ascii

  1. bytes 转换二进制串
bytes('中国',encoding = 'utf8')
  1. chr/ord
print(chr(97))  # a
print(ord('a'))  # 97
  1. divmod
print(divmod(10,4))  # 取整/取余
  1. enumerate
lt = [1,2,3]
for i,j in enumerate(lt):
    print(i,j)  # 索引/值
  1. eval 把字符串的引号去掉,留下的是什么就是什么
s = '[1,2,3]'
print(eval(s))  # [1,2,3] type:str
  1. hash 可变不可哈希
print(hash(1))  # 判断是否可哈希,可哈希返回原值,否则报错
方法 作用
abs 绝对值
all 如果全为真则为True,否则为False
any 只有有一个为真,则为真,否则为假
bin 转换为二进制
oct 转换为八进制
hex 转换为十六进制
dir 列出模块所有方法
frozenset 不可变集合
globals/loals 列出所有全局变量/当前位置所有变量
pow
round 四舍五入
slice 切片
sum 求和
__import__ 通过字符串导入模块

posted on 2025-12-05 11:19  wickyo  阅读(1)  评论(0)    收藏  举报