【Python基础】13. 函数 Function

这篇笔记记录的知识点:

  • 函数的基本概念
  • 自定义函数
  • 函数的几种参数

编程的三种方式:
1.OOP 面向对象编程,万物皆对象,以class为主,抽象化
2.POP 面向过程编程,万事皆过程,def定义过程
3.函数式编程,将某种功能封装起来,用的时候直接调用函数名,def定义函数,也叫function/方法/过程/子程序


函数的基础概念:

  • 函数是一种设计工具,它能让程序员将复杂的系统分解成可管理的部件, 用于将相关功能打包并参数化
  • 一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需要调用其函数名即可
  • 可以理解为是一个带有名字的,具有一定功能的,用于完成具体工作的代码块.
  • 当程序需要多次执行同一个任务时,只需要调用具有该功能的函数即可.
  • 函数特性:
    • 代码重用
    • 保持一致性
    • 可扩展性
  • Python的函数可以分为两类:

    1. 内置函数:
      python内置了一系列的常用函数,以便于使用. 我们可以根据不同的功能需求直接调用使用.

      【Python基础】13. 函数 Function_第1张图片
      内置函数.png

    2. 自定义函数:
      我们自己根据功能需求,以固定的语句格式定义的函数.

    3. 空函数

    pass语句什么都不做.
    实际上pass是用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。

    def func1():
       pass
    
  • 常见的几种函数:
    1. 全局函数 (定义在模块中,仅限单个文件)
    2. 局部函数(嵌套于其他函数中)
    3. lambda函数(匿名函数)
    4. 内置函数


自定义函数

定义函数的格式:

python定义函数使用def关键字,一般格式如下:

def 函数名(参数):
    '''文档字串符(docstring),注释,描述该函数是做什么的'''
    函数体(代码行)         #这个函数要实现的功能代码

函数名的命名规则:

  • 函数名必须以下划线或字母开头,可以包含任意字母、数字或下划线的组合。不能使用任何的标点符号;
  • 函数名是区分大小写的。
  • 函数名不能是保留字(关键字)

函数的调用

定义了函数之后,就相当于有了一个具有某些功能的代码
但是,函数是不会自动执行的,想要让这些代码能够执行,需要调用它
调用函数很简单的,通过 函数名()即可完成调用:

def test1():                    # 定义函数名
    print("hello!")             # 定义函数的功能
    
test1()                         # 调用函数

函数的文档说明
文档字串符(docstring)描述该函数是做什么的,尽量给自定义函数备注,方便日后查看使用.

def test(a,b):
 '''用来完成对2个数求和'''
     print("%d" % (a+b))

test(11,22)

如果执行,以下代码:

help(test)

能够看到test函数的相关说明:

Help on function test in module __main__:

test(a, b)
    用来完成对2个数求和
(END)

Tips: 使用三引号来构成文档字符串,用来解释该函数的主要功能,这是一个很好的编码习惯.


函数的参数

实参和形参

实参是一个实实在在存在的参数,是实际占用内存地址的
形参只是意义上的一种参数,在定义的时候是不占内存地址的.

  • 定义时小括号中的参数,用来接收参数用的,称为 “形参”
  • 调用时小括号中的参数,用来传递给函数用的,称为 “实参”

形参:形式参数,不是实际存在,是虚拟变量,在定义函数和函数体的时候使用形参,目的就是在函数调用的时候接收实参(实参个数,类型应与实参一一对应)

实参:实际参数,调用函数时候传给函数的变量,可以是常量,变量,表达式,函数,传给形参

区别:形参是虚拟的,不占用内存空间,形参变量只有在调用时才分配内存单元,实参是一个变量,占用空间,数据传送单向,实参传给形参,不能形参传给实参。

def sum_num(a,b):        #a,b 是形参
    sum_num = a+b
    print(sum_num)
    
sum_num(10,33)           #10,33 是实参

定义时小括号中的参数,用来接收参数用的,称为 “形参”
调用时小括号中的参数,用来传递给函数用的,称为 “实参”
没特别指明,形参和实参必须一一对应,多一个少一个都报错


常用的几种参数类型:

在Python中可以使用必选参数,默认参数,可变参数,关键字参数,这四类可以一起使用,或者只使用其中某一些.
但是有多种参数,在定义函数和传参的时候,必须要注意参数的置放顺序:
参数的顺序必须是:

必备参数,可变(不定长)参数*args, 默认(缺省)参数,可变(不定长)参数**kargs,关键字参数

1. 必备参数(位置参数)

  • 调用函数时根据函数定义的参数位置来传递参数。
  • 所有参数的顺序必须一一对应,且少一个参数都不可以
def func_1(name,sex):
    sex_dict = {1: u'先生', 2: u'女士'}
    print ('hello %s %s, welcome to python world!' %(name, sex_dict.get(sex, u'先生')))
func_1("刘",2)

运行结果:hello 刘 女士, welcome to python world!

2. 默认参数(缺省参数)

  • 在形参中默认有值的参数
  • python为了简化函数的调用,提供了默认参数机制
  • 用于定义函数,为参数提供默认值,调用函数时可传可不传该默认参数的值(注意:所有位置参数必须出现在默认参数前,包括函数定义和调用)
  • 在定义有默认参数的函数时,需要注意以下:
    • 必选参数必须在前面,默认参数在后;
    • 设置何种参数为默认参数?一般来说,将参数值变化小的设置为默认参数。
def func_2(name,age=18):
    print("我是位置参数:%s"%name,"我是默认参数:%s"%age)

func_2("如花")   #只给name传参,会调用默认参数的值
func_2("如花",28)  #给age传参,会覆盖之前的默认参数

运行结果如下:

我是位置参数:如花 我是默认参数:18
我是位置参数:如花 我是默认参数:28

3. 可变参数(不定长参数)

  • 也叫做不定长参数,动态参数
  • 定义函数时,有时候我们不确定调用的时候会传递多少个参数(不传参也可以)。此时,可用包裹(packing)位置参数*args,或者包裹关键字参数**kargs,来进行参数传递,会显得非常方便。
  • 有时可能需要一个函数能处理比当初声明时更多的参数, 这些可变参数也被叫做不定长参数,声明时不会命名。
    • 加了星号(*)的变量args会存放所有未命名的变量参数,args为元组
    • 而加**的变量kwargs会存放命名参数,即形如key=value的参数, kwargs为字典

(1) 包裹位置传递*args
我们传进的所有参数都会被args变量收集,它会根据传进参数的位置合并为一个元组(tuple),args是元组类型,这就是包裹位置传递。

def func(*args):
    ....
    
# func()
# func(a)
# func(a, b, c)

(2)包裹关键字传递**kargs
kargs是一个字典(dict),收集所有关键字参数

def func(**kargs):
    ....

# func(a=1)
# func(a=1, b=2, c=3)

(3) 对于*args**kwargs在函数中我们可以称之为参数组,但是这两个还是有区别的

1:args的功能:------接收N个位置参数,转换成元组tuple形式
2:
kwargs的功能------接收N个关键字参数,转换成字典dict形式
3:位置参数一定要在关键字参数之前,也就是(
args,**kwargs)

(4) *args**kwargs一起用, 功能会更强大
可以两个一起使用:功能更加的强大:会把无参数名臣的封装成一个tuple 把有参数名称的封装成一个字典;并且,arg必须在**kwargs前面

# def show(*args ,**kwargs):
#     print(args,kwargs)
# show(11,22,33,nn=22,kk=33)

值得注意的是虽然*args**kwargs会把我们给他的参数封装成列表和字典,但是如果我们在给定实际参数的时候给的是一个列表或者是字典,只是表示只有一个实参,而不是列表和字典里面的值;如果想要实现这个功能,那么我们就需要在列表或者字典前面加上*或者**就能实现了;

4. 关键字参数

用于函数调用,通过“键-值”形式加以指定。可以让函数更加清晰、容易使用,同时也清除了参数的顺序需求。

#以下是用关键字参数正确调用函数的实例 
func_01('tanggu', sex=1)
func_01(1, name='tanggu')
func_01(name='tanggu', sex=1)
func_01(sex=1, name='tanggu')

# 以下是错误的调用方式
func_01(name='tanggu', 1)
func_01(sex=1, 'tanggu')

tips:虽然函数参数类型很多,但不要同时使用太多的组合,否则函数接口的可理解性很差.


函数的返回值

函数的返回值是函数重要的组成部分。函数的根本在于实现程序的部分功能,所以很多时候我们需要将函数执行后的结果返回给程序再由程序作出进一步的操作。可以说是函数的返回值令函数与函数之间,函数与主程序之间更加紧密的联系起来。

函数的返回值:

  • 在Python的函数中都有一个返回值,默认为None。也可以使用return value语句来定义一个且只能定义一个可为任意类型的返回值。但是我们能够返回一个序列类型的对象,来实现返回多个值的效果。
  • 函数外部的代码要想获取函数的执行结果,就可以在函数里面用return语句,把结果返回.
  • return 代表一个函数的终止,函数在执行过程中只要遇到return语句,就会停止执行,并返回结果.如果return 后面带一个print 或者return ,则后面的不执行
  • 如果未在函数中指定return,那么这个函数的返回值就是None
def func_2(name,age=18):
    print("我是位置参数:%s"%name,"我是默认参数:%s"%age)


func_2("如花")   #只给name传参
func_2("如花",28)  #给age传参,会覆盖之前的默认参数


def without_return(a,b):  #定义一个没有return的函数
    print(a+b)
    print(a-b)


def with_return(a,b):   #定义一个有return的函数
    return a+b
    return a-b

result1 = without_return(5,10)  #调用上边定义的没有return的函数
result2 = with_return(5,10)     #调运上边定义的有return的函数

print("没有返回值的函数:",result1)
print("有返回值的函数:",result2)

上段代码运行后,结果如下:

15
-5
没有返回值的函数: None
有返回值的函数: 15

[注意:]

  • 定义的函数没有返回值,调用时接用变量收到的是None
  • 函数体里的代码执行完后会回到函数调用的地
  • 函数体里有多个return语法不会报错,但是不能做到返回多个值的操作
  • 函数执行到return语句表示函数执行结束,后边有再多也不会执行了

接收多个rerun值

理论上,return只能返回一个值; 如果想要返回多个值,可以考虑把多个值放在列表,元祖,字典里边.

def return_value():
    print('返回多个值')
    return [1,3,5]
    #return (1,3,5)
    #return {'a':1,'b':2,'c':3}

result = return_value()
print(result)

上述代码运行结果:

返回多个值
[1, 3, 5]
  • return后面可以是元组,列表、字典等,只要是能够存储多个数据的类型,就可以一次性返回多个数据
  • 如果return后面有多个数据,那么默认是元组

函数的嵌套调用

  • 函数的嵌套,在函数里边再定义一个函数
  • 调用可以理解为,是在一个函数里调用了其他函数
  • 嵌套函数不能在外部使用,只能在函数内部使用
def show_msg():
    print("show_msg")# 在函数里面定义的函数就是嵌套函数
    
    def show():   
        print("我是函数体里面的内部函数->>子函数")
    
    show()       # 在函数体里面调用嵌套函数
 
show_msg()      # 调用函数
                # 注意点: 嵌套函数不能再函数外使用,只能在函数内部使用

上述代码运行结果:

show_msg
我是函数体里面的内部函数->>子函数

函数嵌套的小应用: 使用冰箱:

  • 在实际开发中,使用函数的嵌套可以大大的提高代码的可读性
def use_ice_box(eat):
    # 嵌套函数的作用,把函数拆分成不同的功能函数

    # 打开冰箱
    def open_ice_box():
        print("打开冰箱门")

    # 向冰箱放入食物
    def put_eat(eat):
        print("把%s放入冰箱" % eat)

    # 关闭冰箱
    def close_ice_box():
        print("关闭冰箱门")


    open_ice_box()
    put_eat(eat)
    close_ice_box()


use_ice_box("苹果")

局部变量和全局变量,函数的作用域

变量作用域

LEGB原则

  • Python中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。
  • 变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。
  • Python的作用域一共有4种,如下:
    • L ( Local):函数内的区域,包括局部变量和形参。
    • E(Enclosing):外面嵌套函数区域,常见的是闭包函数外的函数。
    • G( Global):全局作用域。
    • B( Built-in): 内建作用域。
  • Python中变量是采用L -> E->G一>B的规则查找
    • 当Python检索变量的时候,先是在局部中查找,如果找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内中找。

局部变量

  • 局部变量,就是在函数内部定义的变量
  • 作用范围:只能在函数体里面使用
  • 局部变量的作用: 临时存储函数体里面的所需要的数据的
    • 局部变量等函数执行完成以后会释放
    • 如果函数里面return了一个返回值,那么等外界使用完成以后该返回值以后才会释放

全局变量和关键字global

  • 如果一个变量,既能在一个函数中使用,也能在其他的函数中使用,这样的变量就是全局变量
  • 也可以理解成在函数外定义的变量就是全局变量
num1 = 1     #<--全局变量

def funcA():
    num1 = 3      #<--- 局部变量.可以理解为定义了一个局部变量,只不过局部变量的名字和全局变量的名字相同而已,不是同一个变量
    print("funcA里的变量:",num1)

def funcB():
    print("funcB里的变量:",num1) 

funcA()
funcB()

上述代码运行结果:

funcA里的变量: 3
funcB里的变量: 1

使用关键字global修改全局变量

当内部作用域想修改外部作用域的变量时,要用到global和nonlocal关键字了。
global关键字用来在函数或其他局部作用域中使用全局变量。但是如果不修改全局变量也可以不使用global关键字。

  • 如果在函数中出现global 全局变量名字, 那么这个函数中即使出现和全局变量名相同的变量名 = 数据 也理解为对全局变量进行修改,而不是定义局部变量
  • global 表示声明要修改全局变量
  • 如果在一个函数中需要对多个全局变量进行修改,那么可以使global a, b,...n
  • 也可以多次用global声明,global a,global b
# 修改全局变
num1 = 1

def funcA():
    global num1    # <-- 表示声明要修改全局变量
    num1 = 3
    print("funcA里的变量:",num1)

def funcB():
    print("funcB里的变量:",num1)

funcA()
funcB()

运行结果:

funcA里的变量: 3
funcB里的变量: 3

在Python中,以上边代码为例,如果在函数内部对全局变量num1进行修改, Python会把变量num1当做是局部变量,为了使全局变量生效,我们可以在函数内使用global关键字进行声明.
如果没有对变量num1进行全局变量申明,运行程序后会报错:UnboundLocalError: local variable 'a' referenced before assignment

nonlocal关键字

nonlocal是在Python3.0中新增的关键字,python2.x不提供支持。
使用nonlocal关键字可以在一个嵌套的函数中修改嵌套作用域中的变量。
示例一:

def FinalResult():
    result = 0
    print("外部函数的变量result的值为:"+ str(result))

    def sumResult():
        result = 12
        print("内部函数的变量result的值为"+str(result))
    sumResult()


FinalResult()

上述示例一代码运行结果如下:

外部函数的变量result的值为:0
内部函数的变量result的值为12

示例二:

def FinalResult():
    result = 0
    #print("外部函数的变量result的值为:"+ str(result))

    def sumResult():
        result = 12
        print("内部函数的变量result的值为"+str(result))

    sumResult()
    print("外部函数的变量result的值为:"+ str(result))


FinalResult()

上述示例二代码运行结果如下:

内部函数的变量result的值为12
外部函数的变量result的值为:0

注意 通过上述示例可以看出, 嵌套在函数FinalResult内部的内嵌函数sumResult中, 我们对变量result的赋值,其实本质是重新创建了一个新的变量,而不是外部函数FinalResultresult=0这条语句的重新赋值.
(2) 通过调换外部函数的print语句调用内部函数sumResult两条语句的先后位置, 可以对比发现, 内部函数的变量赋值,无论什么时候被调用都没有对外部的同名变量造成影响.

示例三:

  • 使用nonlocal关键字
  • 在内部嵌套的函数sumResult中使用了nonlocal关键字,就会告诉Python在外部的函数FinalResult中使用嵌套作用域中的变量result
  • 这样,当我们在内嵌函数中对变量result进行修改时,会直接影响到整个嵌套作用域中的result变量
def FinalResult():
    result = 0

    def sumResult():
        nonlocal result
        result = 12
        print("内部函数的变量result的值为"+str(result))

    sumResult()
    print("外部函数的变量result的值为:"+ str(result))

FinalResult()

上述示例三代码运行结果如下:

内部函数的变量result的值为12
外部函数的变量result的值为:12

注意

  • 使用global关键字修饰的变量之前可以不存在
  • 使用nonlocal关键字修饰的变量之前不存在,运行会报错:
    • SyntaxError: no binding for nonlocal 'result' found

[小总结:]
一个变量在函数体外,一个变量在函数体内, 改变量用global.
一个变量在内嵌函数里,一个变量在包裹内嵌函数的外层函数里,改变量nonlocal

多层嵌套函数和函数外同用一个变量,用global申明.

(这段代码为实验,不确定全适用,也有可能方法有点蠢,因为后来发现,貌似可以用return( ̄. ̄))

  • 申明变量的原则是,哪里用就在哪里申明.

假设我们有一个变量result,这个变量会在函数体外使用,也会在多层嵌套的函数的某几个层里使用.
我们希望达到的效果是:在任何一个地方修改变量result,所有位置的变量result的值都改变,即在之后无论在函数外还是函数内再调用这个变量result,得到的是最新的result的值.具体实现如下:

示例四:

result = 10     # <== 函数外的变量result
print("函数外的result变量:"+str(result))     #<== 第一条print执行语句

def FinalResult():
    global result    <== 申明result为全局变量, 这里如果不申明, 知result程序默认不是同一个变量,只是同名.
    result = 11

    print("外部函数的变量result的值为:"+ str(result))    # <== 第2条print执行语句
    def sumResult():
       #nonlocal resul     # <== 这里如果用nonlocal,会报错
        global result   #用global申明result为全局变量
        result = 12
        print("内部函数的变量result的值为"+str(result))     # <== 第3条print执行语句


    sumResult()
    print("调用完最里层函数后,再次打印外部函数的变量result的值为:"+ str(result))     #<== 第四条print执行语句


FinalResult()
print("调用完最里层函数后,再次打印函数外的result变量:",result)    #<== 第5条print执行语句

[小总结:]

  • 我的目的是在最里层的函数sumResult里修改变量result为12,之后再调用它的外层函数中的变量result或再次调用整个函数体外的变量result时, 这个result的值都是12
  • 需要两次global申明, 遵循'申明变量的原则是,哪里用就在哪里申明'
    • 如果只在内层函数中global申明,但外层函数中的result不申明, 再次调用,函数体外边的这个result变为12了,外层函数中的result的值依旧是11.


函数的拆包 & 交换变量值

  • 拆包:把容器中的每一个数据拆分到不同变量进行保存数据
  • 容器:列表,字典, 元组, 字符串
  • 拆包时要注意,需要拆的数据的个数要与变量的个数相同,一一对应,不能多,否则程序会异常

拆包

list1 = [2,3,4]
a,b,c =list1   # 拆包的时候需要一一对应,不能多
print(a, b)

tuple1 = ("张三", "李四")
name1, name2 = tuple1
print(name1, name2)

dict1 = {"name": "小新", "age": 5}.values()
value1, value2 = dict1
print(value1, value2)

str1 = "apple"
a, b, c, d, e, = str1
print(a, b, c,d,e)

上述代码运行结果:

2 3
张三 李四
小新 5
a p p l e

[注意:]变量名的数量一定要和容器内元素的数量一模一样,否则会报错.
例: alueError: not enough values to unpack (expected 6, got 5),ValueError: too many values to unpack (expected 4)

交换两个变量的值

  • 一般用法:
    • 通用的交换变量的方式:定义临时变量
num1 = 1
num2 = 2

num3 = num1
num1 = num2
num2 = num3
print(num1, num2)
  • Python特有的用法
num1 = 1
num2 = 2

num1, num2 = num2, num1
print(num1, num2)

多函数程序的基本使用流程

一般在实际开发过程中,一个程序往往由多个函数)组成,并且多个函数共享某些数据,这种场景是经常出现的,因此下面来总结下,多个函数中共享数据的几种方式:

  1. 使用全局变量
  2. 使用函数的返回值、参数
  3. 函数嵌套调用
  1. 使用全局变量
  • 多个函数可以使用同一个全局变量
  • 全局变量可以在函数里共享数据
score = 100
# 修改全局变量数据的函数
def modify():
    # 声明修改全局变量
    global score
    # 修改全局变量
    score = 90
    print("modify:", score)

# 获取全局变量修改后的数据
def show():
    # 获取全局变量的数据
    print("获取全局变量修最新的数据:", score)

modify()
show()

上述代码运行后的结果:

modify: 90
获取全局变量修最新的数据: 90
  1. 使用函数的返回值、参数
  • 函数的返回值可以作为其他函数的参数
def return_value():
    return "哈哈"
    
def show_info(msg):
    print(msg)
    
# 1.获取函数的返回值
value = return_value()
# 2.把函数的返回值作为其他函数的参数使用
show_info(value)

上述代码的运行结果:

哈哈
  1. 函数嵌套调用
def test1():
    # 通过return将一个数据结果返回
    return 20

def test2():
    # 1. 先调用test1并且把结果返回来
    result = test1()
    # 2. 对result进行处理
    print(result)

# 调用test2时,完成所有的处理
test2()

上述代码运行结果:

20

总结:函数使用注意事项

1. 自定义函数
  • 函数名不能重复

    • 如果在同一个程序中出现了多个相同函数名的函数,那么在调用函数时就会出现问题,所以要避免名字相同
    • 还有一点 不仅要避免函数名之间不能相同,还要避免 变量名和函数名相同的,否则都会出现问题
  • 一个函数到底有没有返回值,就看有没有return,因为只有return才可以返回数据

  • 在开发中往往根据需求来设计函数需不需要返回值

  • 函数中,可以有多个return语句,但是只要执行到一个return语句,那么就意味着这个函数的调用完成

  • 在调用函数时,如果需要把一些数据一起传递过去,被调用函数就需要用参数来接收

  • 参数列表中变量的个数根据实际传递的数据的多少来确定

无参数、无返回值
def 函数名():
    语句
无参数、有返回值
def 函数名():
    语句
    return 需要返回的数值
有参数、无返回值
def 函数名(形参列表):
    语句
有参数、有返回值
def 函数名(形参列表):
    语句
    return 需要返回的数值
2.调用函数

调用的方式为:函数名([实参列表])

  • 如果调用的函数 在定义时有形参,那么在调用的时候就应该传递参数
  • 调用时,实参的个数和先后顺序应该和定义函数中要求的一致
  • 如果调用的函数有返回值,那么就可以用一个变量来进行保存这个值
3.作用域
  • 在一个函数中定义的变量,只能在本函数中用(局部变量)
  • 在函数外定义的变量,可以在所有的函数中使用(全局变量)

参考资料:

  • 传智博客讲义
  • 廖雪峰老师的教程
  • cnblog-python的位置参数、默认参数、关键字参数、可变参数区别
  • cnblog-python 函数参数类型
  • 战争热诚的博客

扩展阅读:
cnblog-Python进阶-函数默认参数

你可能感兴趣的:(【Python基础】13. 函数 Function)