Python基础:什么是函数!

1、函数介绍


函数(function),它首先是作为一个数学的术语先出现的,没记错的话在我国应该是初二数学的教学内容。函数在教科书中的描述如下:


给定一个数集A,假设其中的元素为x,对A中的元素x施加对应法则f,记作f(x),得到另一数集B,假设B中的元素为y,则y与x之间的等量关系可以用y = f(x)来表示,如下:


在数学中,函数是一种关系,它表达了一个输入值与一个输出值之间的对应关系,例如简单的一次函数、二次函数和三角函数,还有复杂点的指数函数、对数函数以及幂函数等。

对于计算机编程,函数的概念最初也源自于数学的函数,它也可以看作是一种数学函数的实现,因为它的目标也可以是根据输入参数执行相应的处理,然后输出期望的返回结果,也可以简单地用y = f(x)来表示。不一样的是,在编程中,函数的概念更加广泛一点,它更多的表示一组可重用的代码块,用于执行特定的任务。但它并没有规定一定要有输入和输出,它的目的除了包含实现类似于数学函数一样的 输入->激励->输出 功能外,更重要的是为了提高代码的复用性和可维护性。

例如,数学求圆面积的公式为:s = πr²,对于数学函数而言,它等价于s = f®,假如数集A中有2、3、4三个半径值,那么代入函数公式就得出数集B的值为π*2²、π*3²、π*4²。对于编程而言,对应代码如下:

r1 = 2
r2 = 3
r3 = 4

s1 = 3.14 * r1 * r1
s2 = 3.14 * r2 * r2
s3 = 3.14 * r3 * r3

如上,每求一个圆的面积都必须列一次完整的圆面积公式。虽然只有一行代码,读者可能感受不到它的麻烦,但如果是复杂点的功能,需要写多行代码才能实现的时候,你会发现同样的功能只是因为一个参数不一样就要拷贝多次来实现,这显然不是我们追求的效果。于是编程中使用函数来对这部分重复的功能进行封装。


对于求圆面积的函数而言,半径就是函数的输入参数,面积就是函数的输出参数或者返回值,例如:

def area(r):
    return 3.14 * r * r

我们先不用管函数是怎么定义的,先看效果,定义函数后,代码变成了:

s1 = area(r1)
s2 = area(r2)
s3 = area(r3)

也就是说,area这个函数把求圆面积的实现细则给封装起来了,函数调用者不需要知道求圆面积的公式是什么,也不需要知道π值是啥,对于调用者来说,它相当于一个黑盒子,只需要知道代入半径,便可得出圆的面积值即可。求不同半径的圆面积,调用的都是同一个函数,只是传入的半径值不一样。函数降低了代码的复杂度,提高了代码的易读性或者可维护性。

在这里插入图片描述

晓晓在刚学C语言编程的时候,记得教科书上有这么一句话:“函数是C程序的基本单位”,Python说:

一个Python程序可以分解为多个功能独立的函数来实现,并且在Python程序设计中,我们通常将一些常用的功能模块封装成函数,放在公共函数库中供其他程序调用。

善于利用函数可以减少编写重复代码的工作量。同时,在多人协作开发的项目中,函数又可以作为各个模块之间交互的接口,使得项目开发能够并行进行。因此,学会定义和使用函数,是Python编程的必修课。

2、函数定义


  在Python中,支持使用def关键字来定义一个函数,语法如下:
def 函数名(参数列表):

       函数体

       return 返回

函数名有如下规定:

  • 函数名字必须以字母或下划线开头,不能是数字或者其他符号开头;

  • 函数名字不能是Python保留的关键字,如if、else、for等;

  • 函数名字中可以包含数字,但不能包含空格;

  • 函数名字应具有描述性,避免使用单个字母或者非常用的英文缩写;

  • 建议函数名尽量避免与标准库或者常用的第三方库重名,避免混淆。

函数定义需要注意

  • 函数定义后面有一个英文的冒号;

  • 参数列表可以为空,但即使参数为空,括号也不能省掉;

  • 函数体必须与def之间有一定的缩进,建议缩进4个空格;

  • 函数可以没有return语句,没有return语句时函数默认返回None;

  • 函数只要执行到return语句就会马上返回,后续代码直接跳过;

例如,前面的求圆面积的函数定义:

def area(r):
    return 3.14 * r * r

area是函数名,r是参数,3.14*r*r是返回值,可见,返回值也可以是一个表达式。当然,你也可以这样写:

def area(r):
    s = 3.14 * r * r
    return s

这样写就多了一个变量赋值的过程,但是对于初学者来说可能更好理解一点。


Python支持定义一个空函数,用于函数打桩:

def nop():
    pass

注意:定义空函数时,pass是必须要带的,它只是一个占位符,表示什么都不做,或者可以理解为我现在还没有想好怎么做,先pass,让代码能运转起来。此外,pass也可以放在其他语句里,如if else的执行语句、for while的循环体内等等,例如:

if a == b:
    pass

像这种暂时没有任何处理的条件语句,它的执行语句也不能为空,如果不用pass临时填补,运行是会报语法错误的。


对于稍微复杂一点的函数定义,我们建议写上函数文档字符串。关于文档字符串的描述,我们在前面课程已经讲过,就是用一对三个双引号括起来的注释。在函数定义中,文档字符串可以用于描述函数功能、参数、返回值及异常信息等,目的是写给调用者或后续维护人员看的,帮助他们快速的了解并掌握函数的功能及使用方法。Python是没有规定函数文档字符串的具体格式的,允许编码者自行定义,一般格式如下:

"""
函数功能描述

Args:
    参数:参数说明
    
Returns:
    返回值:返回值说明
    
Raises:
    异常:异常说明
"""

当然,你可以根据你所遵循的编程规,或者实际情况来编写合适的文档字符串。如果函数功能很简单,或者函数名字可以自解释的话,不写函数文档字符串也是完全没有问题的。

3、函数参数


和其他编程语言一样,我们定义Python函数的时候,一般会先把函数参数的个数、名字及位置确定下来,这种确定的参数在Python中又称为必选参数或者位置参数。Python的函数定义虽然简单,但是函数参数的灵活度却非常大,除了定义必选参数外,还支持使用默认参数、可变参数和关键字参数、命名关键字参数等。

定义函数时,函数括号内默认罗列的以逗号分隔的各个名称就是函数的位置参数,参数的命名和函数命名约束一样,格式如下:


def 函数名(参数1, 参数2, ……):


例如,上面的求圆面积函数:

def area(r):
    return 3.14 * r * r

上面的参数r就是一个位置参数,当我们调用erea函数时,必须且只能传入一个参数,如:

area(2)
area(3)

如果没有传入参数,或者传入多于1个参数,都会导致运行异常,例如:



如果一个函数定义时有2个或者2个以上的位置参数,则调用时传入参数的顺序必须与函数定义的顺序一一对应,例如:

def circu(length, width):
    return 2 * (length + width)

调用示例如下:

circu(3, 2)

其中3传递给对应位置的参数length,2传递给对应的位置参数width。

Python函数中的默认参数是一个可选的参数,它们在函数定义时提供一个默认值,如果在调用函数时没有提供该参数的值,则使用默认值,格式如下:


def 函数名(参数名=默认值):


例如:

def student(name, age, sex="male"):
    pass

上面的name和age是位置参数,sex就是一个默认参数。因此,当你调用student函数时,如果没有传入sex参数,它就默认给sex参数赋值为“male”,例如:

# 默认参数sex没有赋值,赋默认值"male"
student("lilei", 18)

# 默认参数sex赋值为"female",把它传递给函数
student("hanmeimei", 17, "female")

默认参数的目的是为了简化函数的调用,即调用函数时,如果传递的参数与默认参数一致,则不需要显式地给默认参数赋值。

关于默认参数需要注意:

  • 如果函数参数列表中同时存在位置参数和默认参数,那么位置参数必须在默认参数的前面(从左往右算),否则解释器会报错;

  • 当函数中有多个默认参数时,可以按位置顺序提供默认参数,也可以不按顺序提供默认参数,当不按顺序提供默认参数时,必须带上参数名字。

例如有以下函数:

def student(name, age, sex="male", grade=5):
    pass

可以按顺序传入默认参数:

student("hanmeimei", 17, "female", 6)

这时,"female"按位置关系是赋值给sex的,6则是赋值给grade的。

如果sex使用默认参数,grade不使用默认参数,怎么办呢?那就必须带上参数名,显式地给默认参数赋值了,如下:

student("hanmeimei", 17, grade=6)

此外,当带上参数名赋值时,可以不按默认参数的定义的顺序赋值,如:

student("hanmeimei", 17, grade=6, sex="female")

假如有这样一个需求:

定义一个函数,用它来计算不确定个数的多个数之和,也就是说:可以传三个数给函数,也可以传5个数给函数,函数需要根据实际传递的数来计算他们的总和。

当我们尝试定义这个函数时,或许读者会马上想到用列表或者元组来作为函数参数传入,然后在函数体内遍历列表或元组,计算所有元素的和,例如:

def mysum(nums):
    sum = 0
    for data in nums:
        sum += data

    return sum

这种方法固然也是可以实现的,但是必须在调用函数前,先组装出一个列表或元组来,如:

mysum([1, 2, 3])
mysum((1, 2, 3, 4, 5))

这样做也无可厚非,但是我们有更好的办法,那就是使用可变参数。

Python函数中的可变参数是指在函数调用时,可以接受任意数量的参数。可变参数的格式是在参数前加一个*号,格式如下:


def 函数名(*args):


例如:

def mysum (*args):
    pass

这样就可以根据需要,传入不确定个数的参数了。当然,也可以是0个参数:

mysum()
mysum(1, 2, 3)
mysum(1, 2, 3, 4, 5)

函数可变参数接收到的是一个元组,因此上面的实现代码可以完全不变:

def mysum(*nums):
    sum = 0
    for data in nums:
        sum += data

    return sum

关于可变参数,再给读者介绍一个小知识,那就是当我们在列表或者元组变量前加一个*号时,表示获取列表或元组里的所有元素的值(有点C语言里取指针值的味道),一般用于作为函数参数时使用,相当于把元素展开传递给函数,一般传递给可变参数, 如:

nums = (1, 2, 3)
mysum(*nums)    # 其效果相当于mysum(1, 2, 3)

但是,它并不是只能用于可变参数,你可以把*list或者*tuple理解为去掉列表或元组外壳的一组数据,他们可以放到任何展开后合法的使用场景中,例如:

nums = (1, 2, 3)
data = [2, 4, 6, *nums]

上述代码最终data的值为[2, 4, 6, 1, 2, 3],相信读者已经大概猜到它的原理了,可以简单理解为宏展开,其他使用场景请读者自行探索。

这是Python灵活的又一种表现了,不过灵活虽然好,但完全掌握是前提,把握不住的灵活不但不能给你带来好处,反而会遭其反噬哦。


  Python中的关键字参数其实就是带参数名的可变参数,格式为参数前加两个\*号,关键参数的主要作用是用于扩展函数参数,格式如下:

def 函数名(**kwargs):


例如:

def student (**kwargs):
    pass

关键字参数在函数内自动组装为一个字典,如:

def student(**kwargs):
    print(kwargs)

student(name="lilei", age=18, sex="male")

上面代码把kwargs打印出来是一个字典:

可见,关键字参数的传参方式和默认参数一样,也是“变量名=变量值”,只是它可以传递任意个参数而已,当然,你也可以传递0个关键字参数,这样的话kwargs就是个空字典{}。


同理,我们也可以通过**符号来引用字典变量,并且把它作为关键字参数的入参,如:

data = {"name": "lilei", "age": 18, "sex": "male"}

def student(**kwargs):
    print(kwargs)

student(**data)

这个输出和上一段代码输出是完全一样的。当然,**dict也一样可以用于任意展开后合法的使用场景,如:

base = {"name": "lilei", "age": 18, "sex": "male"}
data = {**base, "grade": 5, "class": 1}
print(data)

输出为:


命名关键字参数格式如下:


def 函数名(*, 参数1, 参数2, ….):


注意,命名关键字参数前有一个*号分隔符,*号后面的都视为命名关键字参数,例如:

def student(*, name, sex):
    pass

上面的name和sex都是命名关键字参数,在调用函数的时候,必须带上参数名,如:

student(name="lilei", sex="male")

且传参必须与定义的命称及个参数个数一致,但顺序不要求一致,例如,你可以这样传参:

student(sex="male", name="lilei")

关于命名关键字参数,刚开始学时因为它的名字原因,尝试从关键字参数的角度去理解它,发现并不好理解,因为它并不是一个可变参数,而关键字参数本质上是一个可变参数,因此:


命名关键字参数 ≠ 命名 + 关键字参数


反而,你可以简单的把它理解成命名的位置参数。为什么呢?因为位置参数是强制按顺序传参,而命名关键字参数是强制用命名来传参。此外,命名关键字参数也有点默认参数的味道,因为也可以指定默认值,指定了默认值的命名关键字参数可以不传参,如:

def student(*, name, sex="male"):
    print(name, sex)

student(name="lilei")

上述例子中,因为命名关键字参数sex指定了默认值,函数调用的时候,如果传参与默认值一致,可以不传参。

如果命名关键字参数前面已经有一个可变参数,则不能再带*号分隔符,默认可变参数之后的都是命名关键字参数,如:

def student(*args, name, sex):
    print(args, name, sex) 

但下面代码是错误的:

def student(*args, *, name, sex):
    print(args, name, sex)

该代码运行会抛出异常。

上述是Python函数中支持的5种参数类型,Python函数定义时允许组合使用它们,称为混合参数。使用混合参数时,需要注意,参数定义的顺序必须是:


(位置, 默认, 可变, 命名关键字, 关键字)


举一个支持全部类型参数混合的例子:

def student(name, age, sex="male", *hobby, father, mother, **score):
    pass

这里name和age是位置参数,sex是默认参数,hobby是可变参数,father和mother是命名关键字参数,score是关键字参数。一个合法的函数调用如下:

student("lilei", 18, "male", "dance", "sing", father="lisi", mother="zhangsan", math=100, English=99, chinese=70)

需要注意的是:当参数列表中同时存在默认参数和可变参数时,调用函数时默认参数必须赋值,且必须按位置参数方式赋值,即上面的第三个参数赋值不能以sex="male"这种方式赋值。当然,如果没有可变参数,则无此限制,即默认参数可以不赋值也可以“名=值”方式赋值,请读者自行尝试。

4、函数返回值


在上面 9.2 函数定义 中已经介绍,Python函数可以通过return语句结束函数,并返回一个值。函数的return语句可以返回任何类型的数据,包括数字、字符串、列表、元组、字典等,如上面的求圆面积的例子:

def area(r):
    return 3.14 * r * r

它返回的就是一个浮点类型的数值。

其他返回值类型这里就不举例了,请读者自行尝试。如果函数结束时没有调用return语句,则默认返回None。

python函数的返回值可以直接赋值给变量,当然,也可以直接把函数A作为函数B的参数使用,相当于会先运行函数A,把函数A运行的返回值赋值给B对应位置的参数,例如:

# 求圆的面积
def area(r):
    return 3.14 * r * r

# 返回两个数中较大的一个
def mymax(s1, s2):
    return s1 if s1 >= s2 else s2

# 求两个圆面积较大的一个
s = mymax(area(2), area(3))

5、函数调用


上面已经举了大量函数定义和调用的例子,相信读者已经大概知道函数调用的方法和注意事项了,这里主要是想补充说明两点:一个是函数调用时传参的数据类型问题,另一个是关于值传递还是引用传递的问题。

  • 参数数据类型

Python的函数可以接受任意类型的参数,包括基本数据类型(整型、浮点型、字符串等)和引用数据类型(列表、元组、字典等)。还记得我们前面 第5课 Python数据类型 中对数据类型分类的相关描述吗?

对于像C语言一样的静态类型语言,函数定义时必须指定参数的数据类型,函数调用时传递的参数数据也必须与函数参数的数据类型一致,否则编译过程就会报错!但是对于Python这样的动态类型语言,通过上面的学习读者应该也知道了,它们的函数定义时并不需要指定数据类型,这样的话就会存在函数调用时传参数据类型不匹配的问题,例如:

# 求两个数的最大值
def mymax(num1, num2):
    return num1 if num1 >= num2 else num2

该函数定义时并不能规定num1和num2的数据类型,当调用该函数时,如果正常传入两个数字类型的数值,则运行是正常的,如:

a = mymax(2.3, 3.4)

输出是3.4。

但是,如果函数调用者故意传入非数字类型的数据,将会导致该函数运行时出现TypeError异常, 例如:

a = mymax("2", 3)

运行异常如下:

很明显,出错的原因是因为字符串和整型之间不能用>=关系运算符。

为了避免出现这种情况,函数设计的时候可以加上参数类型的判断或者处理。例如,可以在函数入口尝试把字符串类型转换为数字类型,如果转换不了就打印提示或者返回错误码。当然这只是一种建议,实际上此类型的错误我们还是希望通过raise异常的方式来呈现比较好。

关于异常,我们将在后续的 进阶篇 相关课程中详细讲解。

  • 值传递与引用传递

如果读者有学过C语言,应该知道有一个东西叫做指针,也知道C语言的函数参数有传值和传指针这两种方式。没学过C语言也没关系,我们现在来好好盘一盘。首先,Python没有指针的概念,读者也不用管什么是指针,我们先来看两个Python函数的例子,第一个:

def func(var):
    var = 10
    print("var :", var)

data = 5
func(data)
print("data:", data)

代码运行的结果是:

此例子中,func函数尝试对参数var的值进行修改,函数内var值修改成功了,但是并没有影响它的输入参数,即整型变量data的值。

第二个例子:

def func(var):
    var[0] = 3
    print("var :", var)

data = [1, 2]
func(data)
print("data:", data)

此例子中,func函数尝试对参数var的值进行修改,函数内var值修改成功了,并且也影响了它的输入参数,即列表变量data的值。

对比这两个例子,先思考一下区别点在哪里。

为了弄清楚这个问题,我们需要先复习一下 第5课 Python数据类型 中关于Python变量定义与赋值的相关知识。对于函数参数的赋值,其原理与普通变量赋值是一样的,对于上面第一个例子,代码过程图解如下:

在这里插入图片描述

由于这种参数传递方式效果等同于只把值复制给了函数参数,而不是把变量的地址传递过去,所以一般我们称之为值传递方式。

而第二个例子的传入参数是一个列表,和我们之前学过的引用类或容器类变量的赋值原理是一样的,代码过程图解如下:

在这里插入图片描述

由于这种参数传递方式效果等同于把变量的地址传递给了函数参数,而不是把值复制过去,所以称之为引用传递或地址传递方式。

已经很明白了,对于不可变数据类型的参数,都是值传递,对于引用类型或者容器型的参数,都是引用传递。但请读者注意,这里并不是说引用传递就一定能修改变量的值哦,那也得变量本身是可写的数据类型才行,比如元组,虽然它也是引用数据类型,但是它是只读的呀!

6、函数式编程


对于复杂软件或系统的开发,我们一般会把软件功能分解为一个个简单的任务,用函数方式来实现这些任务,然后再把这些函数统筹管理起来就能实现整体软件的功能了,这是软件项目中的一个很常见的做法,称为函数式编程。函数式编程是一个很重要的概念,理解起来简单,但是实践起来也不容易。

笔者征战码场十多年,见过许多工作了十年八年都还没领悟的人,他们要么完全没有函数式编程的思维,一个几千行代码的软件,他们能把所有功能放一起从头写到尾;要么就有一点函数式编程的想法,但是又不会准确的划分函数功能范围,认为只要把一堆代码捆在一起就是函数,他们自己也说不清楚这个函数是实现什么功能,而且思维大多已经定型,甚至还自我感觉挺良好,我称这类人为码林霸(BUG)主,读他们代码的人都是码林盟主(蒙住),他们简直是项目的杀手、测试团队的恩人!

在这里插入图片描述

为了让读者能更好的理解函数式编程,笔者决定糊弄一个通俗点的例子。

大家应该都去下过馆子吧,当然,这里是指规模较大一点的餐馆,他们一般设有以下工种:采购、厨房、服务、财务、清洁。餐馆老板一般会根据工种来设置岗位,并明确各个岗位的工作内容。笔者把每个工种比喻成一个功能函数,并明确函数的功能以及输入输出,如下:

  • 采购 :负责食材采购,它的输入是从财务那里拿钱,输出是采购到的食材,交给厨房。
  • 厨房 : 负责烹饪,它的输入是采购回来的食材,输出是烹煮好的饭菜,交给服务。
  • 服务 : 负责服务食客,它的输入是厨房烹煮好的饭菜,端给食客,输出是食客吃完后的脏餐盘,交给清洁。
  • 财务 : 负责收钱结算,它的输入是食客买单,输出是给采购资金以及把营收交给老板。
  • 清洁 : 负责清洗盘子,它的输入是服务端过来的脏盘子,输出是清洁好的盘子,交给厨房再次使用。

各个“函数”各司其职,每个“函数”只需要做好自己的本职工作就行了,不要去考虑别的事情。例如,搞采购的你只需要从财务那里拿钱去按菜单买回来食材就行了,不要去考虑厨房是怎么烹煮的,那是厨房的职责;厨房也只需要负责把饭菜做美味就行了,不要去想煮好的饭菜会不会端错地方,那是服务要做的事情。

这些专职的“函数”之间还需要一个统筹的管理者,那就是总经理或者老板,他是“函数调用者”,负责策划并指挥好什么岗位什么时候做什么事情,如果没有他,各个“函数”之间可能无法协调工作,例如,需要先安排采购把菜买回来,才能让厨房去烹煮;另外,清洁得把盘子清洗干净交给厨房,厨房才能把饭菜盛进去,然后才能让服务端出去给客人,总不能直接整锅端出去给人家吃吧?

当然,不管是餐馆的运营还是软件系统的运行,远没有这么简单,例如,还有反馈机制、“函数”之间的互相沟通协调机制、突发或者异常处理机制等等,这里就先不发散讲解了。

最后,关于Python函数,还有一些高级用法,比如高阶函数、返回函数、匿名函数、函数装饰器、偏函数等,这些我们把它们放到后续 进阶篇 相关课程中讲解,本文的重点是先快速的教会大家如何定义和使用函数。

至此,任务已完成。

pCeTo9O47L9XV5Ew),如有侵权,请联系删除。

感兴趣的小伙伴,赠送全套Python学习资料,包含面试题、简历资料等具体看下方。

一、Python所有方向的学习路线

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。

img
img

二、Python必备开发工具

工具都帮大家整理好了,安装就可直接上手!img

三、最新Python学习笔记

当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。

img

四、Python视频合集

观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

img

五、实战案例

纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

img

六、面试宝典

在这里插入图片描述

在这里插入图片描述

简历模板在这里插入图片描述

若有侵权,请联系删除

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值