三.Python函数

目录

  • 函数概述
  • 函数的作用
  • 函数分类
  • 函数定义
  • 函数的参数
  • 函数的作用域
  • 函数的销毁
  • 变量名解析原则LEGB
  • 递归函数
  • 匿名函数
  • 生成器函数

01函数概述

1)数学定义 y=f(x) ,y是x的函数,x是自变量
2)Python函数:
若干语句组成的语句块、函数名称、参数列表构成,它是组织代码的最小单元
完成一定的功能

02函数的作用:

结构化变成对代码的最基本封装,一般按照功能组织一段代码;
封装的目的是为了复用,减少冗余代码;
代码更加简洁美观,可读易懂;

03函数的分类:

  • 内建函数
    • 标识id id()
    • 哈希 hast() 返回一个对象的哈希值
    • 类型 type() 返回对象的类型
    • 类型转换
      float() int() bin() hex() oct() bool() list()
      tuple() dict() set() complex() bytes() bytearray()
    • 输入 input()
    • 打印 print()
    • 对象长度 len(s) 返回一个集合类型的元素个数
    • isinstance(obj, class_or_tuple)
      判断对象obj是否属于某种类型,返回bool类型
    • issubclass(cls, class_or_tuple)
      判断类型cls是否是某种类的子类,返回bool
    • 绝对值 abs(x)
    • 最大值 max() 最小值 min()
      返回可迭代对象中最大或最小值
      返回多个参数中最大或最小值
    • round(x) 四舍六入五取偶
    • pow(x, y) 等价于x**y
    • range(stop_num)
    • divmod(x, y) 等价于tuple(x//y, x%y)
    • sum(iterable[,start])对可迭代对象的所有元素的值求和
    • chr(i) 将一定范围内的整数返回对应ascii
    • ord(c) 返回字符对应的整数
    • str() repr() ascii()
    • sorted(iterable[,key[,reverse]]) 返回一个新列表,默认升序
    • reversed(seq) 返回一个翻转元素的迭代器
    • enumerate(seq, start=0) 返回索引数字和序列元素的二元组
  • 库函数

    04 函数的定义:

    def语句定义函数
        def 函数名(参数列表):
            函数体(代码块)
            [return 返回值]
        函数名必须是合法标识符,约定见名知意,尽量使用英文缩写
        语句块必须缩进,约定4个空格
        python的函数没有return语句,默认返回一个None值
        定义的参数列表成为形式参数,只是一种符号表达,简称形参
        调用:
        函数定义,只是声明了一个函数,它不会被执行,需要调用
        调用的方式,就是函数名加上小括号,括号内写上参数。
        调用时写的参数就是实际参数,为实参

05 函数的参数

参数调用时,传入的参数个数要和定义的个数相匹配(可变参数例外)

  • 位置参数:
    def f(x, y, z) 调用时,使用f(1, 3, 5)
    按照参数的定义顺序传入实参
  • 关键字参数:
    def f(x, y, z) 调用时,使用f(x=1,y=3,z=5)
  • 传参:
    f(z=None, y=10, x=[1])
    f((1,),z=6, y=4.1)
    f(y=5,z=6,2) #错误的示范,位置参数必须在关键字参数之前
    要求位置参数必须在关键字参数传入之前传入,位置的参数是按照位置对应的
  • 参数默认值:
    定义时,在形参后跟一个值
  • 可变参数:
    一个形参可以匹配任意个参数
    位置参数的可变参数:
    def add(*nums):
        sum = 0
        print(type(nums))
        for x in nums:
            sum+=x
        print(sum)

在形参前加*表示该参数是可变参数,可以接收多个实参,收集多个实参为一个tuple
可变关键字参数:

 def showconfig(**kwargs):
       for k, v in kwargs.items():
            print('{}={}'.format(k,v))
   #  形参使用**符号,表示可接收多个关键字参数
   # 收集的实参名称和值组成一个字典
# 可变参数混合使用:
def showconfig(username, password, **kwargs):
     pass
def showconfig(username, *args, **kwargs):
    pass

总结:
位置参数放在关键字参数之前
可变参数放到参数列表最后,普通参数放到参数列表前,位置可变参数放到关键字可变参数之前

举例:

    def fn(x, y, *args, **kwargs):
        print(x)
        print(y)
        print(args)
        print(kwargs)
    fn(3,5,7,9,10,a=1,b='python')
    fn(3,5)
    fn(3,5,a=1,b='python')

参数总结:
1)参数规则
参数列表一般顺序是普通参数、缺省参数,可变位置参数,keyword-only参数,可变关键字参数
2)注意
代码应该易读易懂,而不是为难别人
请按照书写习惯定义参数

  • 参数解构
    举例:
    def add(x, y):
        return x+y
    add(4,5) #直接调用
    t = (4,5)
    add(*t) 或者 add(*(4, 5))
    add(*range(1,3)) #同样可以使用可迭代对象传参
   #  前提是参数个数相同

给函数提供实参的时候,可以在集合类型前使用*或者**,把集合类型的结构解开,提取出所有元素作为函数的实参:
1)非字典使用*
2)字典类型使用**
提取出来的元素个数要和参数的要求匹配,类型也要匹配

06作用域:

一个标识符的可见范围,一般常说是变量的作用域。

  • 全局作用域:
    在整个运行环境中都可见
    局部作用域:
    在局部变量使用范围不能超过其所在的局部范围
    示例:
x = 5
def foo():
    y = x + 1
    x += 1  #错误,赋值即定义,相当于x未定义就直接使用
    print(x)
  • 全局变量global
x = 5
def foo():
    global x
    x += 1
    print(x)
foo()
  • 闭包
    自由变量:未在本地作用域中定义的变量。例如定义在内存函数外的外层函数中作用域的变量
    闭包:就是一个概念,出现在嵌套函数中,指的是内层函数引用了外层函数的自由变量,就形成了闭包。
def counter():
    c = [0]
    def inc():
        c[0] += 1 # 这里不会报错,因为是c[0] 而不是c,这是对c中的元素重新赋值
        return c[0]
    return inc  #返回是的一个函数引用,而不是函数调用
foo = counter()
print(foo(), foo())  #打印1和2
c = 100
print(foo())  #打印3

上面是python2中实现闭包的方式,Python3中还可以使用关键字nonlocal

def counter():
    c = 1
    def innter():
        nonlocal c
        c += 1
        return c
    return innter
foo = counter()
print(foo(), foo())
c = 100
print(foo())

nonlocal关键字:将变量标记为在上级的局部作用域定义,但不能是全局作用域中定义。

  • 默认值的作用域
def foo(l=[]):
    l.append(1)
    print(l)
foo() #打印[1]
foo() #打印[1,1]
#原因是函数也是对象,python把函数的对象的默认值放在了属性中,这个属性就伴随着这个函数对象的整个生命周期

查看foo.__defaults__

def foo(l=[], u='abc', z=123):
    l.append(1)
    print(l)
print(foo(), id(foo))
print(foo.__defaults__)
print(foo(), id(foo))
print(foo.__defaults__)
# 函数两次打印id相同,表明函数地址没有发生改变,就是说函数这个对象没有变,调用它,它的属性__defaults__中使用元组保存所有默认值。
# 其中l默认值是引用类型,引用类型元素变动,并不是元组的变化

非引用类型中,

def foo(l=1, u='abc', z=123):
    u = ‘xyz’
    z = 789
    print(l, u, z)

print(foo.__defaults__) #打印(1, 'abc', '123')
print(foo(), id(foo))
print(foo.__defaults__) #打印(1, 'abc', '123')

可变类型默认值,如果使用这个默认值,就可能修改这个默认值
有时候这个特性是好的,有的时候这种特性是不好的,有副作用
如何做到按需修改?看下面这两种方法

def foo(x=[], u='abc', z=123):
    x = x[:]
    x.append(1)
    print(x)
foo()
print(foo.__defaults__)
foo()
print(foo.__defaults__)
foo([10])
print(foo.__defaults__)
foo([10,5])
print(foo.__defaults__)
def foo(x=None, u='abc', z=123):
    if x is None:
        x = []
    x.append(1)
    print(x)
foo()
print(foo.__defaults__)
foo()
lst=[10]
foo(lst)
print(lst)  #lst发生改变
print(foo.__defaults__)
foo([10,5])
print(foo.__defaults__)

一般函数中不使用打印语句,结尾使用return
第一种方法:
使用影子拷贝创建一个新的对象,永远不能改变传入的参数
第二种方法:
通过值的判断就可以灵活的选择创建或者修改传入对象
这种方式灵活,应用广泛。
很多函数的定义,都可以看到使用None这个不可变的值作为默认参数,这是一种惯用法

07函数的销毁

  • 全局函数
    重新定义同名函数
    del 语句
    程序结束时

  • 局部函数
    重新定义同名函数
    del 语句
    上级作用域销毁

08 变量名解析原则LEGB

Local,本地作用域,局部作用域的local命名空间,函数调用时创建,调用结束消亡
Enclosing,Python2.2引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间
GLobal,全局作用域,一个模块的命名空间,模块被导入时创建,解释器退出时消亡
Build-in,内置模块的命名空间,生命周期,从Python解释器启动时创建到解释器退出时消亡
所以一个名词的查找顺序是LEGB

09 递归函数

del foo1(b,b1=3):
    print("foo1 called", b, b1)
del foo2(c):
    foo3(c)
    print("foo2 called", c)
def foo3(d):
    print("foo3 called", d)
def main():
    print("main callled")
    foo1(100,101)
    foo2(200)
    print("main ending")
  • 全局帧中生成foo1,foo2,foo3,main函数对象
    1)main函数调用
    2)main查找内建函数print压栈,将常量字符串压栈,调用函数,弹出栈顶。
    3)main全局查找函数foo1压栈,将常量100,101 压栈,调用函数foo1,创建栈帧。print函数压栈,字符串和变量b、b1压栈,调用函数,弹出栈顶,返回值
    4)类似上,最后main函数返回

  • 递归Recursion:
    函数直接或者间接调用自身就是递归
    递归需要有边界、递归前进段、递归返回段
    递归一定要有边界条件
    当边界条件不满足的时候,递归前进
    当边界条件满足的时候,递归返回

  • 间接递归
    是通过别的函数调用了函数自身。
    但是构成了循环递归调用是非常危险的,但是往往这种情况在代码复杂的情况下,还是可能发生这种调用。要用代码规范来避免这种递归调用的发生。
    注意:慎用递归

  • 递归一定要有退出条件,递归调用定要执行到这个退出条件。
    递归调用深度不宜过深
    查看递归限制层数:
    import sys
    print(sys.getrecursionlimit())

  • 递归的性能:
    循环稍微复杂一些,但只要不是死循环,可以多次迭代直至算出结果。
    fib递归函数代码精简易懂,但是只能获取到最外层的函数滴啊用,内部递归结果都是中间结果。而且给定一个n都要进行2n次递归,深度越深,效率越低。

  • 总结:
    递归是一种很自然的表达,符合逻辑思维
    递归相对运行效率低,每一次调用函数都要开辟栈帧。
    递归有深度限制,如果递归层次太深,函数反复压栈,栈内存很快就溢出了
    如果有限次数的递归,可以使用递归调用,或者使用循环代替,循环代码稍微复杂一些,但只是不是死循环,可以多次迭代直至算出结果。
    绝大数递归,都可以使用循环实现
    即使递归代码简洁,但是也要慎重使用递归

递归练习:
求n的阶乘

def fun(n):
    if n == 1:
        return 1
    return n*fun(n-1)

print(fun(3))

10 匿名函数:

  • Python借助lambda表达式构建匿名函数
    格式:
    lambad 参数列表:表达式
lambda x:x*2
(lambda x:x*2)(4) #调用
foo = lambda x,y:(x+y)**2 #不推荐这么用
foo(2,1)
  • 使用lambda关键字来定义匿名函数
    参数列表不需要小括号
    冒号是用来分割参数列表和表达式的
    不需要return,表达式的值,就是函数返回值
    lambda 表达式只能写在一行

  • 用途:
    高阶函数传参时,使用lambda表达式,往往能简化代码

11Python生成器函数

** 生成器generator
生成器是指生成器对象,可以由生成器表达式得到,也可以用yield关键字得到一个生成器函数,调用这个函数得到一个生成器对象

  • 生成器函数:
    函数体中包含yield语句的函数,返回生成器对象
    生成器对象,是一个可迭代对象,也是迭代器
    生成器对象,延迟计算,惰性求值
    普通函数调用fn(),函数会立即执行完毕,但是生成器函数可以使用next函数多次执行
    生成器函数等价于生成器对象,只不过生成器函数可以更加复杂。
def gen():
    print("line 1")
    yield 1
    print("line 2")
    yield 2
    print("line 3")
    return 3
next(gen()) #line 1
next(gen()) #line 1
g = gen()
print(next(g)) #line1
print(next(g)) #1ine2
print(next(g))  #line 3 StopIteration
print(next(g,'End')) #生成器到尾了,返回默认值‘End’

yield 本意是让出的意思

  • 总结:
    包含yield语句的生成器函数生成生成器对象时候,生成器的函数体不会立即执行
    next调用,到头后抛出异常,StopIteration,可以使用默认值
 def inc():
     def counter():
             i=0
             while True:
                     i+=1
                     yield
     c = counter()
     return lambda : next(c)  #嵌套函数闭包

    foo = inc()

猜你喜欢

转载自www.cnblogs.com/luckyleaf/p/12112405.html
今日推荐