函数

函数

python函数

  1. 由若干语句组成的语句块、函数名称、参数列表构成,它是组织代码的最小单元
  2. 完成一定的功能
  3. 可调用函数 callable

函数的作用

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

函数的分类

  1. 内建函数 : 如max(), reversed()
  2. 库函数 : 如math
  3. 自定义函数: 使用def关键字定义

函数定义

def 函数名(参数列表):
    函数体(代码块)
    [return  返回值]
  1. 函数名就是标识符,命名要求一样
  2. 语句块必须缩进,约定4个空格
  3. python的函数若没有return语句,会隐式的返回一个None值
  4. 定义中的参数列表称为形式参数,只是一种符号表达(标识符),简称形参

函数调用

  1. 函数定义,只是声明了一个函数,它不能被执行,需要调用执行

  2. 调用的方式,就是函数名后加小括号,如有必要在括号内填写上参数

  3. 调用时写的参数是实际参数,是实实在在传入的值,简称实参

def add(x,y):  # 函数定义
    result = x+y  # 函数体
    return result  # 返回值

out = add(4,5)  # 函数调用,可能有返回值,使用变量接收这个返回值
print(out)  # print函数加上括号也是调用

9

函数参数

函数在定义时要约定好形式参数,调用时也提供足够的实际参数,一般来说,形参和实参个数要一致(可变参数除外)

传参方式

  1. 位置传参
    • 定义时def fn(x,y,z),调用使用fn(1,3,5),按照参数定义顺序传入实参
  2. 关键字传参
    • 定义时def fn(x,y,z),调用使用fn(x=1,y=3,z=5),使用形参的名字来传入实参的方式,如果使用了形参名字,那么传参顺序就可和定义顺序不同

要求位置参数必须在关键字参数之前传入,位置参数是按照位置对应的

def fn(x,y,z):
    pass

fn(1, 3, z=5)
fn(z = None, y=10, x=[1])
fn((1,), z=6, y=4.1)
fn(y=5, z=6, 2)  #错误

参数缺省值

缺省值也称为默认值,可以在函数定义时,为形参增加一个缺省值

作用:

  1. 参数的默认值可以在未传入足够的实参的时候,对没有给定的参数赋值为默认值
  2. 参数非常多的时候,并不需要用户每次都输入所有的参数,简化函数的调用

可变参数

  1. 可变位置参数:def 函数名(* 形参):

    • 只接受位置传参,不接受关键字传参

    • *args

    • 将收集到的实参组织到一个tuple中

      def sun(*iterable):
          print(type(iterable))
          s = 0
          for x in iterable:
              s += x
      	return s 
      sum()  ---->  <class 'tuple'>   0       
      sum(1,2,3)   ---->  <class 'tuple'>   6
      sum(range(5))   ---->  <class 'tuple'>   TypeError: unsupported operand type(s) for +=: 'int' and 'range' 
      
  2. 可变关键字参数:def函数名(**形参):

    • 只接受关键字传参,不接受位置传参
    • **kwargs
    • 将收集到的实参组织到一个dict中
    • 关键字任意定义
    def test(**kwargs):
        print(kwargs)
        kwargs['c'] = 1000
        print(kwargs)
        
    test(),test(a = 1,b = 20),test(c = 40)
    --->{'c': 1000}
        {'a': 1, 'b': 20, 'c': 1000}
        {'c': 1000}
    

keyword-only参数

keyword-only 参数: 在形参定义时,在一个*之后,或一个可变位置参数(*args)之后,出现的普通参数,就已经不是普通的参数了,称为keyword-only参数

def fn(*args,x):
    print(args,x)
   
fn(3,5)     # ------》TypeError  
fn(3,5,7)   # ------》TypeError  
fn(3,5,x=7) # ------》(3, 5) 7

# x就是keyword-only 参数  ,必须使用关键字传参的方式
# 可以认为args可变位置参数已经截获了所有的位置参数,其后的变量x 不可能通过位置传参传入了

keyword - only 参数另一种形式:

*后所有的普通参数都变成了keyword - only 参数

def fn(*,x, y):
    print(x,y)
    
fn(x=6, y=7)    # ------》正确
fn(y=3, x=8)    # ------》正确

参数的混合使用

一般顺序: 普通参数、缺省值参数、可变位置参数、keyword-only 参数(可带缺省值)、可变关键字参数

​ a, b , c = 200 , *args , d , e = 300 , **kwargs

**kwargs之后不允许出现任何参数

*args之后可以跟keyword-only参数

混合使用时,普通参数需要放在参数列表前面,

参数解构

  1. 在给函数提供实参的时候,可以在可迭代对象之前使用*或者**来进行结构的解构,提取出其中所有元素作为函数的实参
  2. 使用* 解构成位置传参
  3. 使用** 解构成关键字传参
  4. 提取出来的元素数目要和参数的要求匹配
def add(x,y):
    print(x,y)
    return x+y


add(4,5)    
add((4,5))
t=4,5
add(t[0],t[1])
add(*t)
add(*(4,5))
add(*[4,5])
add(*{4,5})
add(*range(4,6))

add(*{'a':10,'b':13}) #----》a b  'ab'
add(**{'a':10,'b':13})  #---->TypeError: add() got an unexpected keyword argument 'a'
add(**{'x':10,'y':13})  #--->10 13   23
def add(*iterable):
    result = 0
    for x in iterable:
        result += x
    return result


add(1,2,3)     #----->6
add(*[1,3,5])   #----->9
add(*range(5))   #----->10

函数返回值

  1. python函数使用return语句返回“返回值”

  2. 所有函数都有返回值,如果没有return语句,隐式调用 return None

  3. 一个函数可以存在多个return语句,但是只要有一条可以执行。如果没有一条return语句被执行到,隐式调用return None

  4. 如果有必要,可以显示调用return None,可以简写成return

  5. 如果函数执行了return函数,函数就会返回,当前被执行的return语句之后的其他语句就不会被执行了

  6. 返回值的作用:结束函数调用,返回“返回值”

  7. 函数不能同时返回多个值

    def fn():
        return 1,3,5  #看似返回多个值,其实隐式的被python封装成了一个元祖
    fn()
    
    
    (1, 3, 5)
    

函数作用域***

作用域

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

函数会开辟一个作用域,X变量被限制在这个作用域中,所以在函数外部 X 变量不可见

作用与分类

  1. 全局作用域
    • 在整个程序运行环境中都可见
    • 全局作用域中的变量称为全局变量
  2. 局部作用域
    • 在函数、类等内部可见
    • 局部作用域中的变量称为局部变量,其使用范围不能超过其所在的局部作用域
    • 也称为本地作用域
#局部变量
def fn1():
    x = 1
    
def fn2():
    print(x)  #不可打印
    
print(x)  # 不可打印
#全局变量
x = 5
def fn():
    print(x)  #可见
   
fn()  #---->  5

一般来说,外部作用域变量 可以在函数内部可见,可以使用

反过来,函数内部的局部变量,不能在函数外部看到

函数嵌套

在一个函数中定义另一个函数

def outer ():
    def inner():
        print("inner")
    print("outer")
    inner()
    
    
outer()  #----->  outer  inner

inner()  #----->NameError: name 'inner' is not defined

内部函数inner不能在外部被直接使用,会抛异常NameError,因为它在函数外部不可见

其实inner就是一个标识符,就是一个函数outer内部定义的变量而已

嵌套结构的作用域

def outer ():
    o = 65
    def inner():
        print("inner{}".format(o))
    	print(chr(o))
        
    inner()
    print("outer{}".format(o))
    
    
outer() 

------>inner65
A
outer65
def outer ():
    o = 65
    def inner():
        o = 97
        print("inner{}".format(o))
    	print(chr(o))
        
    inner()
    print("outer{}".format(o))
    
    
outer() 



----->inner97
a
outer65
  1. 外层变量在内部作用域可见
  2. 内层作用域inner中,如果定义了 o = 97 ,相当于在当前函数inner作用域中重新定义了一个新的变量o ,但是,这个o 并不能覆盖掉外部作用域outer2中的变量o 。只不过对于inner函数来说,其实能可见自己作用域中定义的变量o 了。

一个赋值语句的问题

函数1

x = 5
def foo():
    print(x)
    
    
foo()   #---->5

函数2

x = 5
def foo():
    x +=1    #  ----> 报错
    print(x)
    
foo()   #----->UnboundLocalError: local variable 'x' referenced before assignment

原因分析:

  1. x += 1 其实就是 x = x +1
  2. 只要有"x="出现,这就是赋值语句。相当于在foo内部定义了一个局部变量x, 那么foo内部所有x 都是这个局部变量x 了
  3. x = x +1 相当于使用了局部变量x ,但是这个x还没有完成赋值,就被右边拿来做加1 操作了

解决方案如下:global

global语句

x = 5
def foo():
    global x   #全局变量
    x +=1   
    print(x)
    
foo()  ----->6
  1. 使用global关键字的变量,将foo内的x 声明为使用外部的全局作用域中定义的x
  2. 全局作用域中必须有x 的定义
def foo():
    global x
    x +=1   
    print(x)
    
foo() 
# NameError: name 'x' is not defined
def foo():
    global x
    x = 10
    x +=1   
    print(x) # ----->11
    
foo() 
print(x) # ----->11

使用global关键字定义的变量,虽然在foo函数中声明,但是这将告诉当前foo函数作用域,这个x变量将使用外部全局作用域中的x

即使是在foo中又写了 x = 10 ,也不会在foo这个局部作用域中定义局部变量x 了

使用global,foo中的x 不再是局部变量了,它是全局变量

总结

  1. x+=1 这种是特殊形式产生的错误的原因? 先引用后赋值,而python动态语言是赋值才定义,才能被引用。解决办法,在这条语句前增加z = 0,之类的赋值语句,或者使用global告诉内部作用域,去全局作用域中查找定义变量
  2. 内部作用域使用x = 10 之类的赋值语句会重新定义局部作用域使用的变量x ,但是,一旦这个作用域中使用global声明x 为全局的,那么x= 5 相当于在为全局作用域的变量x 赋值
global使用原则
  1. 外部作用域变量会在内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的目的就是为了封装,尽量与外界隔离
  2. 如果函数需要使用外部全局变量,请尽量使用函数的形参定义,并在调用实参的时候解决

闭包***

  1. 自由变量:未在本地作用域中定义的变量。例如定义在内层函数外的外层函数的作用域中的变量。
  2. 闭包:就是一个概念,内部函数用到了外部函数的自由变量,形成闭包。
def fn():
    x = 1  # x=1相当于fn1()的自由变量
    def fn1():
        y = x + 1  # 本行用到了自由变量,形成闭包
        print(y)
    fn1()
   
fn()
--->2

nonlocal语句

内层函数往外找,找临近的,但不能是在全局里找

非全局的,但又不是该函数本地的,嵌套函数专用

#nonlocal语句
def counter ():
    count = 0
    def inc():
        nonlocal count  # 声明变量count不是本地变量
        count += 1
        return count
    return inc

foo = counter()
print(foo(),foo(),foo())

1  2  3


# count是外层函数的局部变量,被内部函数引用
# 内部函数使用nonlocal关键的声明count变量在上级作用域而非本地作用域中定义
# 代码中内层函数引用外部局部作用域中的自由变量,形成闭包

默认值的作用域

# 默认值的作用域
def foo(xyz = 1):
    print(xyz)
    
foo()   #---->1
foo()   #---->1
print(xyz)   #---->name 'xyz' is not defined因为这个print()是在函数之外,不能看到函数里边的变量
def foo(xxx = []):
    xxx.append(1)
    print(xxx)
    
foo()   #---->[1]
foo()   #---->[1,1]
foo()   #---->[1,1,1]
print(xxx)#---->name 'xxx' is not defined因为这个print()是在函数之外,不能看到函数里边的变量

#为什么第二次调用foo函数打印的是[1,1]
# 1.因为函数也是对象,每个函数定义被执行之后,就生成了一个函数对象,和函数名这个标识符关联
# 2,函数是对象,有属性。python把函数的默认值放在了函数对象的属性中,这个属性就伴随着这个函数对象的整个生命周期
#  3.查看foo._defaults_属性,他是个元组


def foo (xyz=[],m=123,n='abc'):
    xyz.append(1)
    print(xyz)
    
foo()    #--->[1]
print(id(foo),foo.__defaults__) #--->97936704 ([1,1], 123, 'abc')
foo()    #--->[1,1]
print(id(foo),foo.__defaults__) #--->97936704 ([1,1], 123, 'abc')
foo()    #--->[1,1,1]
print(id(foo),foo.__defaults__) #--->97936704 ([1,1,1], 123, 'abc')
# 函数地址并没有变,就是说foo这个函数对象的没有变过,调用它,它的属性_defaults_中使用元组保存默认值xyz默认值是引用类型就,;引用类型的元素变化,并不是元组的变化
#  foo.__defaults__查看函数缺省值
#非引用类型缺省值
def foo(xxx,m=123,n='aaa'):
    m=456
    n='sss'
    print(xxx)
  

print(foo.__defaults__)  #--->(123, 'aaa')
foo('qqq')  #--->qqq
print(foo.__defaults__)  #--->(123, 'aaa')
# 属性__defaults__中使用元组保存所有位置参数默认值,它不会因为在函数体内改变了局部变量(形参)的值而发生改变
#keyword-only参数的缺省值
def foo(zzz,m=123,*,n='abc',t=[1,2]):
    m=456
    n='qqq'
    t.append(xyz,m,n,t)
    
   
print(foo.__defaults__,foo.__kwdefauts__) #---->(123,) {'n': 'abc', 't': [1, 2]}
foo('qwer') #---->qwer 456 qqq [1, 2, 300]
print(foo.__defaults__,foo.__kwdefaults__) #---->(123,) {'n': 'abc', 't': [1, 2, 300]}


#属性__defaults__中使用元组保存所有位置参数默认值
#属性__kwdefaults__中使用字典保存所有keyward-only参数的默认值

def x(a=[]):
    a += [5]  #相当于a.extend([5]),就地修改
    print(a)
print(x.__defaults__) #--->([],)
x() #--->[5]
x() #--->[5, 5]
print(x.__defaults__) #--->([5, 5],)



def y(a=[]):
    a = a + [5]
    print(a)
print(y.__defaults__)#--->([],)
y()#--->[5]
y()#--->[5]
print(y.__defaults__)#--->([],)

# 两者不同之处,都在第二行 a += [5] 与 a=a+[5]
# + 表示两个列表合并并返回了一个全新的列表
# += 表示 就地修改前一个列表,在其后面追加一个列表。相当于extend方法

变量名解析原则LEGB***

  • Local,本地作用域、局部作用域的local命名空间。函数调用时创建,调用结束消亡
  • Enclosing,Python2.2引入嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间
  • Global,全局作用域,即一个模块的命名空间。模块被import时创建,解释器退出时消亡
  • Build-in,内置模块的命名空间,生命周期从python解释器启动时创建,到解释器退出时消亡

函数的销毁

  1. 定义一个函数就是生成一个函数对象,函数名指向的就是函数对象
  2. 可以使用del语句删除函数,使其引用计数减1
  3. 可以使用同名标识符覆盖原有定义,本质上也是使其引用计数减1
  4. python程序结束时,所有对象销毁
  5. 函数也是对象,也不例外,是否销毁,还是要看引用计数是否减为0

匿名函数

匿名:隐匿名字,即没有名称

匿名函数:没有名字的函数

lambda表达式

使用lambda表达式构建匿名函数

lambda x:x**2  #定义
(lambda x:x**2)(3)  #调用

--->9
foo = lambda x,y:(x+y)**2
foo(1,2)

--->9

#等价于
def foo(x,y):
    return(x+y)**2
foo(1,2)
  • 使用lambda关键字定义匿名函数,格式为 lambda [参数列表] : 表达式

  • 参数列表不需要小括号。无参就不写参数

  • 冒号用来分割参数列表和表达式部分

  • 不需要使用return。表达式的值,就是匿名函数的返回值。表达式中不能出现等号

  • lambda表达式(匿名函数)只能写在一行上,也称为单行函数

匿名函数往往用在为高阶函数传参时,使用lambda表达式,往往能够简化代码

#返回常量的函数
print((lambda :0)())  #---->0

#加法匿名函数,带缺省值
print((lambda x,y=3:x+y)(5))  #---->8
print((lambda x,y=3:x+y)(5,6))  #---->11

#keyword-only参数
print((lambda x,*,y=30: x+y)(5))  #---->35
print((lambda x,*,y=30: x+y)(5,y=10))  #---->15

#可变参数
print((lambda *args :(x for x in args))(*range(5)))  #---->生成器对象<generator object <lambda>.<locals>.<genexpr> at 0x0602E270>
print((lambda *args :[x+1 for x in args])(*range(5)))  #---->[1, 2, 3, 4, 5]
print((lambda *args :{x%2 for x in args})(*range(5)))  #---->{0, 1}

递归函数

递归Recursion

  • 函数直接或者间接调用自身就是递归
  • 递归需要有边界值、递归前进段、递归返回段
  • 当递归条件不满足时,递归前进
  • 当边界条件满足时,递归返回
  • 递归一定要有退出条件,递归调用一定要执行到这个退出条件。没有退出条件的递归调用,就是无限调用
  • 递归调用的深度不宜过深
    • python对递归调用的深度做了限制,以保护解释器
    • 超过深度限制,抛出RecursionError

例子 : 斐波那契数列

  • 斐波那契数列:1,1,2,3,5,8,13,21,34,····

  • 如果设F(n) 为该数列的第n项,那么这句话可以写成如下形式:F(n)=F(n-1) +F(n-2)

  • F(0)=0,F(1)=1,F(n)=F(n-1) +F(n-2)

  • #循环
    a = 0
    b = 1
    n = 5
    for i in range(n-1):
        a,b = b,a+b
    else:
        print(b)
        
    #--->5
    
  • #递归  F(0)=0,F(1)=1,F(n)=F(n-1) +F(n-2)
    def fib(n):
        return 1 if n<3 else fib(n-1) + fib(n-2)
    fib(5)
    
    #--->5
    
    #fib(5)解析:
    #fib(4)+fib(3)
    #fib(4) 调用 fib(3)+fib(2)
    #fib(3) 调用 fib(2)+fib(1)
    #fib(2) 、fib(1)是边界 return 1,所有函数调用逐层返回
    
    #性能太低,重复计算的量太多
    

递归的性能

  • 循环稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果
  • fib函数代码极简易懂,但是只能获取到最外层的函数调用,内部递归结果都是中间结果。而且递归次数太多,深度越深,效率越低
  • 递归还有深度限制,如果递归复杂,函数反复压栈,栈内存很快就会溢出
def fib(n,a=0,b=1):
    a,b=b,a+b
    if n == 1:
        return a
    return fib(n-1,a,b)

fib(5)
#--->5

#上述代码与循环类似
#参数n是边界条件,用n来计数
#上一次的计算结果直接作为函数的实参
# 效率很高

间接递归

def foo1():
    foo2()
def foo2():
    foo1()
    
foo1()

#间接递归,是通过别的函数调用了函数自身
#但是,如果构成了循环递归调用是非常危险的,但是往往这种情况在代码复杂的情况下,还是可能发生的

n 1 if n<3 else fib(n-1) + fib(n-2)
fib(5)

#—>5

#fib(5)解析:
#fib(4)+fib(3)
#fib(4) 调用 fib(3)+fib(2)
#fib(3) 调用 fib(2)+fib(1)
#fib(2) 、fib(1)是边界 return 1,所有函数调用逐层返回

#性能太低,重复计算的量太多


##  递归的性能

- 循环稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果
- fib函数代码极简易懂,但是只能获取到最外层的函数调用,内部递归结果都是中间结果。而且递归次数太多,深度越深,效率越低
- 递归还有深度限制,如果递归复杂,函数反复压栈,栈内存很快就会溢出

```python
def fib(n,a=0,b=1):
  a,b=b,a+b
  if n == 1:
      return a
  return fib(n-1,a,b)

fib(5)
#--->5

#上述代码与循环类似
#参数n是边界条件,用n来计数
#上一次的计算结果直接作为函数的实参
# 效率很高

@[toc]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值