zoukankan      html  css  js  c++  java
  • python基础之函数详解

    Python基础之函数详解

    一、函数的定义

    到现在为止,我们已经掌握了Python的基本语法和数据类型等相关基础知识了,以进行一个项目的编写了,这个时候,就会发现,很多代码需要我们进行复制粘贴,这简直就是损害我们小文艺的脸面啊,这个时候,我们就需要向函数求助了。

    函数的三大优点:

    1. 代码不冗余
    2. 可阅读性高
    3. 可维护性,可拓展性得到提升。

    函数定义会发生什么呢?

    1. 先向内存申请一块空间将函数体的内容存放进去
    2. 将函数体的内容和函数名绑定一起
    3. 程序在执行到定义函数的阶段,只识别语法,不会执行函数体的内容

    那么函数的语法是什么呢?

    def 函数名(有无参数都可以):
    	"""描述性信息"""
        代码块
    	return 值。
    
    
    1. def:这是定义函数的关键字
    2. 函数名:遵循变量名的定义,最好对函数有一个功能性介绍(动词为主)
    3. 括号:内部可以选填参数
    4. “”“描述性信息”“”:可有可无,但是推荐使用,对函数体的功能进行描述
    5. return:可有可无。给出返回值

    二、函数的调用

    使用函数要遵循一个原则:这个原则就表明了它的内部的一些现象:大家一定要记牢:先定义后使用。是不是很熟悉,没毛病,这就是变量的使用原则。

    函数名实际上跟“变量名”有些相似,都是储存一个内存地址,当我们调用函数的时候,最最基本的方式就是函数名加括号的方式。

    def func():
    	pass
    
    # 最最基本的调用方式。
    func()
    
    
    
    def func(a,b):
        pass
    
    # 函数体有参数的方式
    func(1,2)
    
    
    

    三、函数返回值

    这时候就不得不讲一下return的作用了,如果我们把函数当做一个工厂,那么参数就是我们送到工厂的原材料,而工厂生产出来的东西就是我们return的值了。他可以是任意数据类型,当函数运行结束的时候,我们可以对其进行赋值,就可以得到我们的产品了。

    # 当函数没有return 值的时候,默认返回值为None
    def func():
    	pass
    v= func() 
    print(v)  # None
    
    
    # 当只有一个return,但是并没有写返回值的时候,默认返回值也是None
    def func():
    	return
    	
    v= func() 
    print(v)  # None
    
    # 当函数有返回值的时候,对其调用进行赋值会得到返回值。
    # 返回值可以是单个,也可以是多个
    def num_sum(a,b):
    	return a+b
    
    sums = num_sum(1,2)
    print(sums)  # 3
    

    return 的作用不止如此,他也是函数终结者。当函数体中运行到return的时候,就会终止函数的运行,不会执行return之下的代码。

    当然,我们还可以将函数的返回值当做参数传给另外一个函数,因为返回值的本质就是一个变量值。

    四、函数的参数

    当我们在定义有参函数的时候,括号内部的参数相当于变量名,而在函数调用的时候,传进的值将内存地址绑定给定义阶段的参数。

    在定义阶段,函数括号内的参数称为形参。

    在调用阶段,函数括号内的参数称为实参。

    参数可以有好几种形式表现,比如单个数据类型、key=value、函数等,但是其核心概念就是传入的是个值。

    4.1 位置参数

    位置形参函数在定义的时候是按照从左到右依次定义的参数。特点是必须被传值,不能多也不能少。

    位置实参:函数在调用的时候是按照从左到右依次定义的参数。特点是按顺序与形参一一对应。

    # 特点:必须被传值,多少给参数都不行。
    
    def func(x,y):  # 括号内就是位置形参
        print(x,y)
        
    func(1,2,3) # 过多的位置实参被传
    func(1,)  # 太少的位置实参
    func(1,2)  # 完美
    
    

    4.2 关键字参数

    我们还可以利用key=value的形式进行传参,调用阶段的参数是关键字参数。关键字可以指定形参进行传值,可以不参照顺序。

    # 关键字参数:按照key=value的形式传入当做参数。
    def func(x,y=2): # 默认参数
        print(x,y)
    
    # func(y=2,x=1)  # 关键字实参
    # func(1,2)
    

    默认参数在定义阶段已经被赋值了,所以在调用阶段可以不必为该参数传值,当然,如果要是进行传值的话,会以传的值为准。

    实参:位置实参和关键字参数的混合使用

    混合使用的话,一定要注意一下两点:

    1. 位置实参必须放在关键字实参之前。
    2. 不能为同一个形参重复传参。

    形参:位置形参和默认参数的混合使用

    形参中的混合使用需要注意:

    1. 位置形参必须在默认参数之前。
    2. 默认参数是在定义阶段就被传入的值的内存地址
    3. 建议默认值不推荐使用可变数据类型

    4.3 可变长度参数

    4.3.1 可变长度形参

    如果我们在调用函数的时候,如果传入的实参过多,就回出现错误,但是我们可以使用(*+形参名)或者(**形参名)来接受过多的实参。

    原则上形参名可以是任意名,但是约定俗成通常为(*args)和(**kwargs)。

    1. *args。用来接受位置实参。多出来的实参会被保存成元祖。
    2. **kwargs。用来接受关键字参数。多出来的实参会被保存成字典。

    当然,在函数内部使用的时候,是不带星号的。

    在定义的时候请务必放到默认参数之后。*args在**kwargs之前。

    # *args的使用
    def foo(x,y,*args):
        print(x,y,args)
    
    foo(1,2,3,4,5,6)  # 1 2 (3,4,5,6)
     
        
    # **kwargs的使用
    def foo(x,y,**kwargs):
        print(x,y,kwargs)
        
    foo(1,2,a=3,c=4)  # 1 2 {"a":3,"c":4}
    
    

    4.3.2 星号在实参上的应用

    多出来的实参有*args和**kwargs接受,那么如果在调用的时候就给实参加上星号会有什么结果呢?

    如果我们给实参前加上星号,本质是将该实参进行循环,然后得出的值当做实参传入形参。当然*args后可以跟多个可循环的数据类型,如果是字典只传入key。**kwargs只能使用字典,代表的是将key=value的格式传入。

    # 在字典上的应用。
    def func(x,y,z):
        print(x,y,z)
    
    func(*{'x':1,'y':2,'z':3}) # func('x','y','z')
    func(**{'x':1,'y':2,'z':3}) # func(x=1,y=2,z=3)
    

    当然,传入的实参数量要和形参数量相同,不然会出现错误。

    4.3.3 星号的综合应用

    def index(x,y,z):
    	print(x,y,z)
    
    def foo(*args,**kwargs): # (1,2)传给args,{"z":3}传给kwargs
    	# *args将(1,2)转成1,2。**kwargs将{"z":3}转成z=3,然后传入index函数做参数
        index(*args,**kwargs)  
    	
    foo(1,2,z=3)
    

    当函数定义的参数为*args,**kwargs时,代表函数可以接受任意符合格式的实参,上述的函数调用foo的本质是调用index函数。

    4.4 命名关键字参数

    这个参数使用的比较少,在定义形参的阶段,如果某些参数出现在星号之后,那么这些参数就被称为命名关键字参数。

    # 命名关键字参数
    def func(x,y,*,a,b):  # a,b就是命名关键字参数
        print(x,y,a,b)
        
    def foo(x,y,*,b=1,a):  # 命名关键字参数是不用按key=value在单个值之后的顺序
        print(x,y,a)
    

    注意,由于命名关键字参数位于星号之后,所以如果不用关键字实参的话,其值是传不进去的,因此必须的用指名道姓的方法传参数,而且调用的时候还必须被传值。

    4.5形参和实参的顺序

    形参的顺序:位置形参、默认参数、*args、命名关键字参数、**Kwargs。

    实参的顺序:位置实参、*args、关键字参数、**kwargs。

    五、函数闭包函数

    在了解闭包函数之前,我们是需要一些储备知识的。然后,你不知不觉就一边蒙逼一边明白什么是闭包函数了。

    5.1 函数的应用。

    # 1、函数可以赋值,因为函数名本身引用着函数体的内存地址。
    def foo():
        pass
    
    f = foo
    
    # 2、可以把函数当做参数传递给另外一个函数。
    def func():
        print()
    
    def foo(x):
        print(x)
        
    foo(func)
    
    # 3、可以当作另一个函数的返回值。
    def f1():
        return foo
    
    # 4、可以当作容器类型的一个元素。
    def foo():
        pass
    
    l = [foo,1,a]
    
    
    

    其实看着有这么多应用,但是大家发现没有,函数名其实就是一个变量名,凡是变量名能做到的它都能做到。这点很重要哦。

    5.2 函数的嵌套

    函数的嵌套共有两种。

    # 1、函数的嵌套调用。即在调用一个函数的时候,在其内部又调用另外一个函数。
    def foo():
    	pass
    def func():
    	foo()
    	
    # 2、函数的嵌套定义。即在定义一个函数的时候,在其内部又定义另外一个函数。
    
    def func():
    	def foo():
    		print(1)
    
    

    5.3 闭包函数

    首先上面都是闭包函数的铺垫。

    明确一个概念:闭包函数= 名称空间与作用域+函数嵌套+函数对象。

    1. 闭:该函数是内嵌函数。这字说明了该函数所处的位置,肯定是嵌套函数中的定义函数。

    2. 包:指该函数包含对外层函数作用域名字的引用。注意,自己本身不含有,只有外部函数有。

    是不是看不懂,接下来看个例子、

    # 闭函数:名称空间与作用域+函数嵌套
    def outer():
    	x = 1111
    	def inter():
    		print(x)
    	inter()
    x = 22222		
    outer()
    

    当我们在外界去调用inter函数的时候,由于名称空间与作用域的查找关系(按照定义的时候的查找顺序)的原因,不管我们在外界定义的是否含有x ,他都会使用outer内定义的变量。

    # 闭包函数:名称空间与作用域+函数嵌套+函数对象
    def outer():
    	x = 1111
    	def inter():
    		print(x)
    	return inter		
    f1 = outer()
    
    f1()
    

    注意,是把函数名当做变量来用,return后返回的inter是一个函数的内存地址,如果加上括号就是执行函数功能的意思。所以一定不能加括号。在运行Outer的时候,返回一个内部函数名,然后复制给一个全局变量,然后该全局变量就被绑定了inter函数的内存地址,当该函数名后加括号的话,就相当于执行了inter()。

    这样我们就可以在任意地方调用函数的局部名称空间。

    当然,你也可以把x当做参数进行传递。但是原理一定要搞清楚,因为这是接下来的重点内容的铺垫。

    六、装饰器

    当我们的软件已经在线上运行的时候,需要拓展新的功能,那么我们 应该怎么办呢?首先我们要提到开放封闭原则。

    1. 开放原则:对拓展功能是开放的。
    2. 封闭原则:对修改源代码则是封闭的。

    也就是说我们既不能更改函数的源代码,也不能更改调用方式,这个时候,我们就要用到装饰器了。

    装饰器:为函数增加额外的功能且不更改调用方式的函数。

    6.1 无参装饰器

    下面我们进行循序渐进的讲解,究竟是怎么用到装饰器的,他又是怎么做到的符合开放封闭原则的。我们以为一个函数增加一个计算运行时间的功能为例来进行推导。

    import time
    
    def foo(x):
        print("其实我这里有很多代码,但是你看不到")
        time.sleep(3)
        return x
        
    # 1、我们直接在函数内部进行更改。
    def foo(x):
        start_time = time.time()
        print("其实我这里有很多代码,但是你看不到")
        time.sleep(3)
        stop_time = time.time()
        process_time = stop_time - start_time
        print(process_time)
        return x
    
    
    

    这时候虽然我们增加了功能,但是我们已经违反了开放封闭原则,所以失败。

    # 2、利用函数的嵌套定义来直接增加新功能
    
    def outer(x):
        start_time = time.time()
        foo(x)
        stop_time = time.time()
        process_time = stop_time - start_time
        print(process_time)
        return x
    

    我们同样实现了功能,调用方式改变,源代码没变。

    # 3、利用闭包函数来修改
    
    def outer(foo):
        def inter(x):
            start_time = time.time()
            res = foo(x)
            stop_time = time.time()
            process_time = stop_time - start_time
            print(process_time)
            return res
        return inter
    
    foo = outer(foo)
    foo(x)
    

    上述已经实现了不更改函数源码,不更改调用方式了,但是函数被写死了,如果用来修饰多个函数呢?这些函数又有不同个数的参数呢?

    # 终极版装饰器的诞生。
    def outer(func):
        def inter(*args,**kwargs):
            start_time = time.time()
            res = func(*args,**kwargs)
            stop_time = time.time()
            process_time = stop_time - start_time
            print(process_time)
            return res
        return inter
    
    foo = outer(foo)
    

    这是利用星号在形参和实参不同的作用来实现的。

    自此,装饰器就写好了,遵守开放封闭原则,让用户在不知情的情况下,完成了函数的更新。要想掌握装饰器,必须弄懂闭包函数这些前置只是,如果还是不太懂,就继续看看闭包函数板块和参数的知识。

    # 装饰器的模板。
    def outer(func):
    	def inter(*args,**kwargs):
    		res = func(*args,**kwargs)
    		return res
    	return inter
    

    添加装饰器的时候,可以根据模板然后在inter函数里面添加相关功能。

    当然,还有一点就是语法糖的使用,我们可以将装饰器的赋值转成在被修饰函数上一行添加@装饰器名。

    # 语法糖:在被修饰函数上一行添加@装饰器名。
    @outer
    def foo(x):
    	print(x)
    

    6.2 有参装饰器

    当用户进行身份验证时,有的函数要从info文件里得到信息,有的则是从数据库等等,那么如何让让一个装饰器实现那么多功能呢?我们都能想到,那就再给装饰器一个状态参数呗,要知道,之前为了伪装成被装饰参数,内部函数的参数已经固定,外部参数虽然也可以进行参数赋值,但是由于语法糖的存在,导致我们如果在外部函数增加参数之后,语法糖就不能使用了。

    综上,我们还可以再次利用给函数传参的两种方式中的第二种,在函数外在套一层,此时的炸U那个时期已经是三个函数定义了。

    # 语法糖的实际效果相当于执行了这一步:与被修饰函数名相同的全局变量 = 装饰器(被装饰函数名)
    @outer  # foo = outer(foo)
    def foo():
    	print("1")
        
       
    # 有参装饰器的模板
    
    def auth(参数):
        def outer(func):
            def wrapper(*args,**kwargs):
                res = func(*args,**kwargs)
                return res
            return wrapper
        return outer
    
    @auth("参数")
    def foo():
        print(123)
    

    当程序运行到语法糖时,由于函数名加括号,开始执行函数返回outer的内存地址,所以实际上语法糖依然是@outer。不过是多执行了一步函数将参数传到内部函数的名称空间而已。

    6.3多个装饰器的运行原理

    def outer1(func):
        def wrapper1(*args,**kwargs):
            res = func(*args,**kwargs)
            return res
        return wrapper
    
    def outer2(func):
        def wrapper2(*args,**kwargs):
            res = func(*args,**kwargs)
            return res
        return wrapper2
    
    def outer3(func):
        def wrapper3(*args,**kwargs):
            res = func(*args,**kwargs)
            return res
        return wrapper3
    
    
    @outer1  # foo = outer1(foo)= wrapper1的内存地址
    @outer2  # foo = outer2(foo)= wrapper2的内存地址
    @outer3  # foo = outer3(foo) = wrapper3的内存地址
    def foo():
        print("123")
    

    加载顺序:outer3>outer2>outer1

    执行顺序:outer1==>outer2>outer3

    七、递归函数

    递归函数的本质就是循环,在运行函数体期间一直在不停地调用自己。在Python解释器中,递归有一个最大限制,超过这个限制就会报错。

    # 递归的直接调用
    def foo():
    	print("一直循环我会报错")
    	foo()
    	
    # 递归的间接调用
    def foo1():
    	print("我是正常的函数,调用了foo我就不正常了")
    	foo()
    

    递归函数在调用自己的时候又两个过程

    1. 回溯:在函数中不停地调用自己的过程
    2. 递推:在函数满足条件退出函数之后,不停地将已打开的函数结束的过程。
    # while循环
    count = 0
    
    while count <5:
        print(count)
        count += 1
    
    # 改成递归函数之后
    
    def cou(count):
        if count > 4:
            return
        print(count)
        count += 1
        cou(count)
    
    cou(0)
    

    据我观察,递归函数应该是主要应用在需要参数且多次循环的场景之上。

    八、函数的类型提示

    这是一个可用可不用的一个提示。

    def foo(x:str,y:"这里面应该写整数",z:list=[1,2,3]):
    	pass
    	
    

    形参后面加个冒号然后可以写一些提示性信息,声明一下数据类型等,但是如果你不按照规定传参,函数也不会报错,因为这个规定本来就是自己定的 ,但是你打破了这个自己提示的类型,那你声明干嘛呢,,就像调皮的用户,哈哈哈

  • 相关阅读:
    Pwn2Own 内核 TencentOS
    锤子思维的局限性
    内心宁静 Life
    ANTLRWorks: The ANTLR GUI Development Environment
    汇编 while vs for
    CatBoost is a high-performance open source library for gradient boosting on decision trees
    What is ERP
    专利 案件管理系统
    质量:零缺陷 & 零Bug
    SaaS协会 腾讯千帆
  • 原文地址:https://www.cnblogs.com/liqianxin/p/12511228.html
Copyright © 2011-2022 走看看