12 - 函数

一.定义函数

  • 定义函数的格式如下:
def 函数名():
    代码
def 函数名(参数1,参数2,参数3...):
    代码
  • 示例:
# 定义一个函数,能够完成打印信息的功能
def printInfo():
    print('------------------------------------')
    print('         人生苦短,我用Python')
    print('------------------------------------')

二.函数定义及函数的调用

  • 函数定义好后会加载到内存中,给函数分配一块内存地址空间

  • 如果使用函数名,会返回地址值

  • 如果使用函数名(),表示调用函数,会执行函数中的代码

    import random
    def generateCode():
        s = 'skfjsdkjlkdjoppawituvmz1249596892034jjff2939jmvmd'
        code = ''
        for i in range(4):
            r = random.choice(s)
            code += r
        print(code)
    
    print(generateCode)    #<function generateCode at 0x00000229A5E826A8>s
    generateCode()         #k4j9
    
    • 函数定义好以后,函数体里的代码并不会执行,如果想要执行函数体里的内容,需要手动的调用函数。
    • 每次调用函数时,函数都会从头开始执行,当这个函数中的代码执行完毕后,意味着调用结束了。
    • 如果函数中执行到了return也会结束函数。

三.函数的参数

1.形参与实参

  • 在定义函数的时候,小括号里写等待赋值的变量名----函数定义时的参数叫“形参”

  • 在调用函数的时候,给参数赋值----调用函数式传递给函数的参数称为"实参"

2.函数调用时给参数赋值(位置参数,关键字参数)

  • 位置参数:调用函数时传入实际参数的数量和位置都必须和定义函数时保持一致。

    def test(a,b):
        print(a,b) 
    
    test(1,2)  #位置参数
    1 2
    
  • 关键字参数赋值:调用函数时,通过指明参数名进行赋值

    使用情景:可以在调用函数时不按定义函数时参数的顺序进行赋值

    def info(a,b,c=1,d='haha'):
    	print(a,b,c,d)
    
    info(1,2,'heihei')    #1 2 heihei haha  就算c有默认值,但是也要挨着顺序才能给d赋值,如果想不按顺						序赋值,就可以使用关键字参数。不然第三个赋值'heihei'就赋值到c形参
    info(1,2,d="heihei")   #1 2 1 heihei
    info(d='heihei',a=1,b=2)   #1 2 1 heihei
    

    注意:调用时,如果按照定义时参数的顺序赋值,一定是依次按顺序赋值,不能跳

3.函数定义时给形参赋值(缺省参数)

  • 默认值参数:在定义时就给形参赋值。在调用时可以给默认值参数赋值也可以不用,但是一定要给没有默认值的参数赋值

    def info(a, b, c = 1):
    	print(a + b + c)
        
    info(2,3)      #当调用时没有给默认值参数赋值时,参数使用默认值
    info(2,3,4)    #当调用时给默认值参数赋值,会覆盖参数的定义时的默认值
    

    注意:在定义函数时,普通参数一定要在默认值参数的前面

4.可变形参(底层的过程)

  • 可变参数--*:*args(名字可以改)

    • 装包----可以理解为一个容器,多个数据以元组的方式存储----将参数封装成元组

      • 定义时:def 函数名(*args) / 调用时:输入多个值

        def get_sum(*args):
          print(args)    #args就是元组
          s = 0
          for i in args:  #因为*args就是将多个数据存到列表中,就可以用列表的遍历操作
              s += i
          print(s)
        
        get_sum(1)        #(1,)
        get_sum(1,2)      #(1, 2)
        get_sum(1,2,3)    #(1, 2, 3)
        
    • 拆包:在调用函数时给列表类型变量名加上*,当我们传入的实参为一个列表时,给列表名前面加上*,那么传到形参去,以拆包的方式将列表中的元素装入可变形参中。然后可变形参再进行装包,将一个一个的元素存到到一个元组中

      • 调用时:函数名(*list)/函数名(*tuple)/函数名(*set) 比如*ran_list

        def get_sum(*args):
          print(args)
          s = 0
          for i in args:
              s += i       #如果args变量收到的是ran_list传来的值,那么会把ran_list中的值当做整体,此题中会将整个列表当做整体存放在一个元组中作为元组的一个元素,那么取args中的元素--列表整体,做运算肯定会报错
          print(s)
        
        ran_list = [23,3,4,33,7,6]
        
        get_sum(*ran_list)   #如果在变量前加*号,那么该变量会自动拆箱传入args元组中,那么元组中就是一个一个的ran_list列表中的元素--数字,就可以做运算
        get_sum(ran_list)   #但如果不加`*`号,那么就不会自动拆箱,会当成一个列表整体传入
        
  • 可变形参--**:**kwargs(名字可以换)

    • 函数定义时,给形参前面加**,那么在函数调用时,必须用关键字参数的方式赋值,因为加**的可变形参,收到实参传来的值时,在内存中以字典类型存储的。

    • 装包:在调用函数时,以关键字参数的方式赋值,赋值后将多个值传给变量kwargs,在内存中以字典的形式存储

      def show_book(**kwargs):
      	print(kwargs)         #将多个值封装为字典
      
      show_book()              #{} 
      show_book(bookname = '西游记',author = '吴承恩',number = 5)   #{'bookname': '西游记', 														'author': '吴承恩', 'number': 5}
      show_book(bookname = '水浒传')   #{'bookname': '水浒传'}
      
    • 拆包:在调用函数时,给字典类型变量加上**,实参为字典类型,会将字典类型拆成一对一对的的关键字类型变量,再传给形参,形参收到后再装包成字典,只是字典中的元素是拆散的一对一对的键值对,就可以用字典的方法进行查找遍历等。如果没有拆散传进去,那么字典中存的就是字典类型的book整体---将一个字典类型变量存入到字典中

      def show_book(**kwargs):
      	print(kwargs)         #将多个值封装为字典
          for k,v in kwargs.items():    #就可以用字典的方法
              print(k,v)
      
      book = {'bookname':'红楼梦','author':'曹雪芹'}
      show_book(book)            #{'bookname': '红楼梦', 'author': '曹雪芹'}
      
  • *不使用于形参时的原理:加*的变量,可以理解为一个容器,可以传入多个值,存储在列表

    a,*b,c = 1,2,3,4,5
    print(a)     #1
    print(b)     #[2,3,4]
    print(c)     #5
    
    

5.可变形参的总结:

  • 1.定义函数时:无法确定参数的数量,就需要用到可变形参

    • 当需要传入的实参为关键字类型method(bookname = '西游记',author = '吴承恩',number = 5)或就是字典类型dict1 = {'bookname':'红楼梦','author':'曹雪芹'} 、 method(dict1)时,就需要用的**这种可变形参
    • 当需要传入的实参为非字典类型如method(1,2)list1 = [23,3,4,33,7,6] 、 method(list1)时,就可以用*这种可变形参
  • 2.(已经确定下来形参为可变形参时)调用函数时:

    • 如果实参为不可迭代的(一个一个的)比如method(1,2)method(bookname = '水浒传'),直接正常写入即可。(注意:如果形参为**类型,那么实参一定用关键字参数传入)
    • 如果实参为可迭代的元素比如list1 = [23,3,4,33,7,6] 、 method(*list1)或者list1 = {'bookname':'红楼梦','author':'曹雪芹'} 、 method(**list1) 就要在变量名前加*或者**,加哪一个根据形参是哪个来确定。如果不加,那么实参就会作为一个整体传给形参,形参会将整体当成一个元素加载到代码内部。因为我们关心的是如本例子中列表中的元素,23,3,4...等元素,而不关心列表整体,所以要拆包传给形参;关心的是字典中的每对键值对,所以也要拆包,将一对一对的键值对传给可变形参

6.不同类型形参顺序

  • 如果很多个值都是不定长参数,那么这种情况下,可以将缺省参数放到 *args的后面, 但如果有**kwargs的话,**kwargs必须是最后的
  • 总结:普通参数,*args,默认值参数(缺省参数),**kwargs

四.函数的返回值

  • return可以返回一个或多个值,也可以单独使用return表示立即结束函数的调用

  • 如果return多个值,那么会将多个值封装到一个元组中,将元组作为整体返回

  • return后面可以是元组,列表、字典等,只要是能够存储多个数据的类型,就可以一次性返回多个数据。

    def function():
    	# return [1, 2, 3]
    	# return (1, 2, 3)
    	return {"num1": 1, "num2": 2, "num3": 3}
    
    
  • 如果接收多个返回值:1.用一个变量接---返回的是元组类型的 2.用对应数量的变量去接收---元组中的多个值,会对应赋值给多个变量。

    def maxmin():
        list1 = [1, 4, 3, 2, 6, 4, 8]
        list1.sort()
        min = list1[0]
        max = list1[-1]
        return min, max       
    
    a, b = maxmin()    #a = 1,b = 8
    r = maxmin()       #(1, 8)
    print(a,b,r)      
    

五.全局和局部变量

1.可变和不可表类型的变量

  • 不可变类型当改变了变量的值时,地址发生改变

    如:int str float bool tuple

  • 可变类型里面的内容发生变化,但是地址没有发生变化

    如:list dict set (因为这些类型都有添加的方法,使用添加方法后,地址没有改变,原地操作)

  • 所谓可变类型与不可变类型是指:数据能够直接进行修改,如果能直接修改那么就是可变,否则是不可变

  • 注意一个特例:在交互模式下(集成模式下无影响),[-5,256]这个区间外的数值型地址即使数值一样地址也不相同

    a = -6
    b = -6
    a is b   #在交互模式下为False
    

2.局部变量

  • 局部变量,就是在函数内部定义的变量
  • 其作用范围是这个函数内部,即只能在这个函数中使用,在函数的外部是不能使用的
  • 因为其作用范围只是在自己的函数内部,所以不同的函数可以定义相同名字的局部变量
  • 局部变量的作用:为了临时保存数据需要在函数中定义变量来进行存储
  • 当函数调用时,局部变量被创建,当函数调用完成后这个变量就被销毁
  • 内部函数也可以看成一个局部变量

3.全局变量

  • 在函数外边定义的变量叫做全局变量,既能在一个函数中使用,也能在其他的函数中使用,这样的变量就是全局变量
  • 如果全局变量和局部变量同名:函数内部中的 变量名 = 数据 此时理解为定义了一个局部变量,而不是修改全局变量的值

注意:如果在函数内部没有用关键字global修饰全局变量,那么只能查看全局变量,不能修改全局变量

a = 100   #全局变量
b = 20    #全局变量
def test1():
	b = 2       #局部变量
	b += 10     #可以修改,这是对局部变量进行修改
	a += 10     #此时编译不通过
	print(a,b)

4.修改全局变量(global)

  • 如果在函数中出现global 不可变类型全局变量的名字 那么这个函数中即使出现和全局变量名相同的变量名 = 数据 也理解为对全局变量进行修改,而不是定义局部变量
  • 如果在一个函数中需要对多个全局变量进行修改,那么可以一次性全部声明,也可以分开声明global a, b
a = 100   #全局变量
b = 20    #全局变量
def test1():
	global a,b   #声明全局变量
	a += 10     #此时修改的就是全局变量
	b += 30
	print(a,b)

如果在函数中声明了global 的变量,函数中就不要再定义同名的变量了,不然会冲突

5.global关键字的添加:

  • 只有不可变的类型才需要添加global
  • 可变类型不需要添加global

6.查看所有的全局变量和局部变量

  • Python提供了两个内置函数globals()和locals()可以用来查看所有的全局变量和局部变量。
def test():
    a = 100
    b = 40
    print(locals())  # {'a': 100, 'b': 40}

test()

x = 'good'
y = True
print(globals())  # {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x101710630>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/jiangwei/Desktop/Test/test.py', '__cached__': None, 'test': <function test at 0x101695268>, 'x': 'good', 'y': True}

六.引用和函数参数的传递

  • 引用:如果地址空间被其他变量的指针所指,那么就说这个地址被引用

  • sys.getrefcount(变量):可以用来查看一个变量的引用次数,即它所指的地址被几个指针指着

    a = 10000000.5511311523145
    b = 10000000.5511311523145
    c = a
    d = a
    print(id(c),id(a),id(d),id(b))   #四个变量地址相同       
    
    list1 = [1,2]
    list2 = list1
    list3 = list1
    list4 = list1
    print(sys.getrefcount(list1))   #5 list1的地址被list1、list2、list3、list4和这个方法的指针引用
    
  • 函数的引用:(参数的值传递机制)

    • 实参向形参的值传递要分清是可变类型还是不可变类型
      • 如果实参是不可变类型:int str float bool tuple ,那么传递给形参进入方法执行完后,不影响实参的值
      • 如果实参是可变类型:list dict set ,那么传递给形参进入方法执行完后,会影响实参的值

七.python函数的嵌套

  • 函数中可以调用其他函数

  • 函数中可以声明其他函数(比较特殊)

  • 内部函数不能修改外部函数的变量,如果硬是想修改外部函数的变量,需要在内部函数中对变量添加nonlocal修饰(不是local)

    def outer():
    	a = 100
    	
    	def inner():
    		b = 200
    		b = a      #内部函数可以使用外部函数的变量,但是不能修改外部函数的变量
    		nonlocal a  #要修改需要添加nonlocal
    		a += b
    		print(a,b)
    	
        print(a)    #100
    	inner()     #调用内部函数200,100
    

八.函数的闭包

  • 满足以下三点的内部函数就被称为闭包:
    1. 嵌套函数--嵌套在其他函数中
    2. 内部函数引用了外部函数的变量
    3. 返回值是内部函数
def outer(n):
	a = 10
	
	def inner():
		b = a + n
		print('内部函数:',b)
		
	return inner

innerdress = outer(2)   #调用外部函数,现在不会执行内部函数,但是会将内部函数的地址返回
innerdress()   #函数名加括号就表示调用,即调用了内部方法inner
#结果:  内部函数: 12

九.闭包的使用----装饰器

  • 引入装饰器:用来给函数装修的,我们写好了一个函数去实现某种功能,但是现在我想给函数拓展功能,但是我又不想对原函数进行修改和查看,只是想将写好的新增的功能写到一个函数中,然后加入到原函数中去,我们就可以使用装饰器。

  • 装饰器:遵循开放封闭原则,在不改变原函数的情况下,扩展了函数的功能

    • 定义:(满足闭包)
    def aaa(func):
    	def xxx(参数,...):  #参数可有可无
    		...
    		func()
    		...
    		return yyy  #可有可无
    	return xxx
    
    • 装饰:
    @装饰器名            <==>  原函数 = 装饰器(原函数)
    def 原函数():
    	pass
    
  • 装饰器的执行过程

    def decorater(func):
    
        def wrapper():
            func()
            print('新增功能一')
            print('新增功能二')
        return wrapper
    
    @decorater               #等价于 <==> house = decorater(house) 
    def house():
        print('原有的功能一')
        print('原有的功能二')
        
    house()
    
    '''
    #执行结果:
    原有的功能一
    原有的功能二
    新增功能一
    新增功能二
    '''
    

    详细过程:

    1. 按照顺序执行,程序先将decorater函数加载到内存中,给函数赋地址
    2. 然后执行到house函数这一块,将house函数加载到内存中,给house函数赋地址。house函数有@decorater装饰器,相当于会执行decorater函数,decorater函数有参数,且是函数类型,因为装饰器加到house函数上,所以house作为参数传入
    3. 执行decorater函数:然后将内部函数wrapper加载到内存(没有执行wrapper函数!!),wrapper函数内部有func(),现在即为house(),func()也会随着wrapper函数加载到内存,但是没有执行!最后执行完decorater函数返回wrapper(注意:不是返回wrapper(),而是返回wrapper,即它的地址)。有返回值则需要变量去接,所以在底层程序做了此操作:house = wrapper,即把wrapper的地址赋给了house
    4. 程序继续执行到house(),即执行现在的house函数,但现在的house函数的地址其实是wrapper函数的地址,所以执行的就是内存中的wrapper函数
  • 装饰器可以实现的功能

    • 引入日志
    • 函数执行时间统计
    • 执行函数前预备处理
    • 执行函数后清理功能
    • 权限校验等场景
    • 缓存
  • 装饰器示例

    • 无参数的装饰器

      def decorater(func):
      
          def wrapper():  #也无参
              func()
              print('新增功能一')
              print('新增功能二')
          return wrapper
      
      @decorater               #等价于 <==> house = decorater(house) 
      def house():      #原函数没有参数,所以装饰器可以无参
          print('原有的功能一')
          print('原有的功能二')
          
      house()
      
    • 带参数的装饰器(因为需要被修饰的函数可能有不确定个数的参数,赋值的形式也不确定,所以一般有固定的定义格式可以绝对任意类型任意个数的参数,如下)

      def decorater(func):
          def wrapper(*args,**kwargs):  #这样写允许所有可能
              func(*args,**kwargs)      #因为上面可变形参接收实参以后以元组的形式(*args)或者字典的形式(**kwargs)存储的,所以需要用解包的形式将元组或字典中的元素拆开,依次赋给原函数的形参
              pass
          return wrapper
      
      @decorater
      def house(): #无参
          pass
      
      @decorater
      def house(v1):  #一个参数
          pass
      
      @decorater
      def house(v1,v2):  #多个参数
          pass
      
      @decorater
      def house(v1,v2,v3 = 40):  #默认值参数赋值
          pass
              
      

      总结:如果原函数有参数则装饰器内部函数也要有参数

    • 带返回值的装饰器

      def decorater(func):
          def wrapper(*args,**kwargs): 
              r = func(*args,**kwargs)    #func()即为调用原函数,原函数有返回值,所以需要变量接收
              pass
          	return 2000    #也要有返回值,但是具体返回什么需要根据需求决定
          return wrapper
      
      @decorater
      def house(): 
          pass
      	return 1000
      

      总结:原函数有返回值,那么修饰器内部函数也要有返回值,而具体返回的什么,根据需求来决定

    • 装饰器也可以带参数

十.匿名函数

  • 用lambda关键词能创建小型匿名函数。lambda函数的语法只包含一个语句lambda表达式就表示一个函数

  • 什么时候用:一个函数语句很简单,而且有返回值,我们要利用这个函数的返回值,但是只用一次,而且这个函数很简单,没必要再用标准的定义函数的格式去定义,直接用匿名函数定义好,使用一次就可以

  • 定义格式:调用函数名 = lambda 参数列表: 运算表达式

    sum = lambda arg1, arg2: arg1 + arg2 
    
    # 调用sum函数
    print("Value of total : %d" % sum( 10, 20 ))
    
    #等价于
    def sum(arg1,arg2):
        return arg1 + arg2
    

    因为匿名函数你定义好要调用,调用就需要函数名,所以要赋给一个变量,用此变量名来调用lambda匿名函数

  • 注意事项:

    • Lambda函数能接收任何数量的参数但只能返回一个表达式的值

    • 匿名函数可以执行任意表达式(甚至print函数),但是一般认为表达式应该有一个计算结果供返回使用

  • 应用场合举例:

    • lambda函数作为参数传递(最常用)
    def fun(a, b, opt):  #opt是一个函数类型参数,这里的用法就是高阶函数
    	print("a = " % a)
    	print("b = " % b)
    	print("result =" % opt(a, b)) #这里才执行了add函数,得到了它的返回值
    
    add = lambda x,y:x+y
    fun(1, 2, add)    # 把add作为实参传递,只是将函数地址赋给pot,并没有执行此函数
    a = 1
    b = 2
    result = 3
    
    • python中有一些内置的高阶函数和类就使用了匿名函数----函数做为另一个函数的参数
    函数名或类名 功能 参数描述
    sorted函数 用来将一个无序列表进行排序 函数参数的返回值规定按照元素的哪个属性进行排序
    filter类 用来过滤一个列表里符合规定的所有元素,得到的结果是一个迭代器 函数参数的返回值指定元素满足的过滤条件
    map类 将列表里的每一项数据都执行相同的操作,得到的结果是一个迭代器 函数参数用来指定列表里元素所执行的操作
    reduce函数 对一个序列进行压缩运算,得到一个值。python3以后,这个方法被移到了functools模块 函数参数用来指定元素按照哪种方式合并

    注意:filter的匿名函数要求返回值必须是bool类型,只有bool类型结果为True的才是符合过滤条件的

    具体使用说明详见十二

十一.高阶函数

  • 高阶函数的定义:既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,同样,我们还可以把一个函数当做另一个函数的返回值。这种函数的使用方式我们称之为高阶函数。

  • 自定义高阶函数的使用

    • 函数做为另一个函数的参数

      def test(age,action):
          if age < 18:
              print('您还没满十八岁,请退出')
          action()   # 把参数action直接当做一个函数来调用
      
      def smoke():
          print('我已经年满十八岁了,我想抽烟')
      
      my_action = smoke  # 定义一个变量my_action,让它指向smoke函数
      test(21, my_action)  # 将my_action传给 test 函数作为它的参数
      
      test(21,smoke)  # 还可以不再定义一个新的变量,直接传入函数名
      
    • 函数作为另一个函数的返回值

      def test():
          print('我是test函数里输入的内容')
      
      def demo():
          print('我是demo里输入的内容')
          return test  # test 函数作为demo函数的返回值
      
      result = demo()     #执行结果是先打印'我是demo里输入的内容',然后将test函数的地址赋给result
      result()         #执行结果是'我是test函数里输入的内容'
      demo()()
      

    注意:一定要注意使用的是函数名还是函数名(),函数名表示函数的地址,函数名()表示函数的调用!!!

  • python中内置的高阶函数:map、filter,min,max,sorted,reduce

十二.系统的高阶函数中匿名函数的使用举例说明(map、sorter、max、filter)

list1 = [('tom',19),('tony',20),('lily',18)]

#m = min(list1) 因为列表中的元素是以元组的形式存在的,没法比较
mi = min(list1,key = lambda x : x[1])  #key是系统内置函数min函数的参数、key的参数类型为函数类型,lambda刚好是函数类型,赋给key。作用就是min函数将list1列表中的元素--元组依次取出,然后赋值给lambda函数的x,返回每一个元组中的第二个元素--即数字,那么min比较的依据就是每一个元素的第二个元素数字,然后取最小的列表元素--元组返回
print(mi)

ma = max(list1,key = lambda x : x[1])
print(ma)

s = sorted(list1,key = lambda x : x[1],reverse = True) #key为排序的依据
print(s)

#将岁数大于20的元素过滤出来
rr = filter(lambda x : x[i] > 20,list1)  #lambda表达式根据返回值(x[i] > 20)的真假,来实现过滤
print(list(rr))    #需要将过滤返回值强转为list才能查看到结果

#等价于
list2 = []
for i in list1:
    if i[1] > 20:
        list2.append(i)

#map的作用:通过匿名函数指明提取的内容,并对内容进行加工。
ma = map(lambda x : x[0].title(),list1)
print(list(ma))     #['Tom', 'Tony', 'Lily'] 注意要强转为list类型,不然map返回的就是一个地址值

ma2 = map(lambda x : x[1] + 1,list1)
print(list(ma2))    #[20, 21, 19]


#reduce() 函数会对参数序列中元素进行累积。函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。
from functools import reduce

def add(x, y) :            # 两数相加
    return x + y
sum1 = reduce(add, [1,2,3,4,5])   # 计算列表和:1+2+3+4+5
sum2 = reduce(lambda x, y: x+y, [1,2,3,4,5])  # 使用 lambda 匿名函数
print(sum1)
print(sum2)
posted @ 2021-10-14 19:11  深藏Bluee  阅读(78)  评论(0)    收藏  举报