【Python核心】不可或缺的自定义函数
一个规范的值得借鉴的Python程序,除非代码量很少(比如 10 行、20 行以下),基本都应该由多个函数组成,这样的代码才更加模块化、规范化
函数是Python程序中不可或缺的一部分
事实上,在前面的学习中已经用到了很多Python的内置函数,比如sorted()
表示对一个集合序列排序,len()
表示返回一个集合序列的长度大小等等
接下来学习一下Python的自定义函数
一、函数基础
那么,到底什么是函数?如何在Python程序中定义函数呢?
1.1 函数的定义
说白了,函数就是为了实现某一功能的代码段,只要写好以后就可以重复利用。先来看下面一个简单的例子
def my_func(message):print('Got a message: {}'.format(message))# 调用函数 my_func()
my_func('Hello World')
# 输出
Got a message: Hello World
其中:
def
是函数的声明my_func
是函数的名称- 括号里面的
message
则是函数的参数 - 而print那行则是函数的主体部分,可以执行相应的语句
- 在函数最后可以返回调用结果(
return
或yield
),也可以不返回
总结一下,大概是下面的这种形式:
def name(param1, param2, ..., paramN):statementsreturn/yield value # optional
和其他需要编译的语言(比如C语言)不一样的是,def
是可执行语句,这意味着函数直到被调用前,都是不存在的。当程序调用函数时,def
语句才会创建一个新的函数对象,并赋予其名字
一起来看几个例子,加深对函数的印象:
def my_sum(a, b):return a + bresult = my_sum(3, 5)
print(result)# 输出
8
上面代码处理的步骤:
- 定义了
my_sum()
这个函数,它有两个参数a和b,作用是相加 - 调用
my_sum()
函数,分别把3和5赋于a和b - 返回其相加的值赋于变量result,并输出得到8
1.2 函数的执行
再来看一个例子:
def find_largest_element(l):if not isinstance(l, list):print('input is not type of list')returnif len(l) == 0:print('empty input')returnlargest_element = l[0]for item in l:if item > largest_element:largest_element = itemprint('largest element is: {}'.format(largest_element)) find_largest_element([8, 1,-3, 2, 0])# 输出
largest element is: 8
这个例子中,定义了函数find_largest_element
,作用是遍历输入的列表,找出最大的值并打印。因此,当调用它并传递列表 [8, 1, -3, 2, 0] 作为参数时,程序就会输出largest element is: 8
需要注意,主程序调用函数时必须保证这个函数此前已经定义过,不然就会报错,比如:
my_func('hello world')
def my_func(message):print('Got a message: {}'.format(message))# 输出
NameError: name 'my_func' is not defined
但是,如果在函数内部调用其他函数,函数间哪个声明在前、哪个在后就无所谓,因为def是可执行语句,函数在调用之前都不存在,只需保证调用时所需的函数都已经声明定义:
def my_func(message):my_sub_func(message) # 调用my_sub_func()在其声明之前不影响程序执行def my_sub_func(message):print('Got a message: {}'.format(message))my_func('hello world')# 输出
Got a message: hello world
1.3 参数默认值
Python 函数的参数可以设定默认值,比如下面这样的写法:
def func(param = 0):...
这样,在调用函数func()时,如果参数param
没有传入,则参数默认为0,而如果传入了参数 param,其就会覆盖默认值
1.3 参数类型
Python和其他语言相比的一大特点是,Python是dynamically typed的,可以接受任何数据类型(整型,浮点,字符串等等)
对函数参数来说这一点同样适用,比如还是刚刚的my_sum函数,可以把列表作为参数来传递,表示将两个列表相连接:
print(my_sum([1, 2], [3, 4]))# 输出
[1, 2, 3, 4]
同样,也可以把字符串作为参数传递,表示字符串的合并拼接:
print(my_sum('hello ', 'world'))# 输出
hello world
当然,如果两个参数的数据类型不同,比如一个是列表、一个是字符串,两者无法相加,那就会报错:
print(my_sum([1, 2], 'hello'))
TypeError: can only concatenate list (not "str") to list
可以看到,Python不用考虑输入的数据类型,而是将其交给具体的代码去判断执行,同样的一个函数(比如相加函数my_sum()
),可以同时应用在整型、列表、字符串等等的操作中
1.4 多态
在编程语言中,把这种行为称为多态
这也是Python和其他语言,比如Java、C等很大的一个不同点。当然,Python这种方便的特性,在实际使用中也会带来诸多问题。因此,必要时需要在开头加上数据的类型检查
1.5 函数嵌套
Python函数的另一大特性,是Python支持函数的嵌套。所谓的函数嵌套,就是指函数里面又有函数,比如:
def f1():print('hello')def f2():print('world')f2()
f1()# 输出
hello
world
这里函数f1()
的内部,又定义了函数f2()
,执行逻辑是在调用函数f1()
时先打印字符串hello
,然后f1()
内部再调用f2()
打印字符串world
。那为什么需要函数嵌套?这样做有什么好处呢?
其实,函数的嵌套,主要有下面两个方面的作用
- 第一,函数的嵌套能够保证内部函数的隐私
内部函数只能被外部函数所调用和访问,不会暴露在全局作用域。因此,如果函数内部有一些隐私数据(比如数据库的用户、密码等)不想暴露在外,那可以使用函数的的嵌套将其封装在内部函数中,只通过外部函数来访问。比如:
def connect_DB():def get_DB_configuration():...return host, username, passwordconn = connector.connect(get_DB_configuration())return conn
这里的函数get_DB_configuration
便是内部函数,它无法在connect_DB()函数以外被单独调用。也就是说,下面这样的外部直接调用是错误的:
get_DB_configuration()# 输出
NameError: name 'get_DB_configuration' is not defined
只能通过调用外部函数connect_DB()
来访问它,这样一来,程序的安全性便有了很大的提高
- 第二,合理的使用函数嵌套,能够提高程序的运行效率
来看下面这个例子:
def factorial(input):# validation checkif not isinstance(input, int):raise Exception('input must be an integer.')if input < 0:raise Exception('input must be greater or equal to 0' )...def inner_factorial(input):if input <= 1:return 1return input * inner_factorial(input-1)return inner_factorial(input)print(factorial(5))
这里,使用递归的方式计算一个数的阶乘。因为在计算之前,需要检查输入是否合法,所以写成了函数嵌套的形式,这样一来输入是否合法就只用检查一次
而如果我们不使用函数嵌套,那么每调用一次递归便会检查一次,这是没有必要的,也会降低程序的运行效率
实际工作中,如果遇到相似的情况,输入检查不是很快且还会耗费一定的资源,那么运用函数的嵌套就十分必要
二、函数变量作用域
Python函数中变量的作用域和其他语言类似
2.1 局部变量
如果变量是在函数内部定义的,就称为局部变量,只在函数内部有效。一旦函数执行完毕,局部变量就会被回收,无法访问,比如下面的例子:
def read_text_from_file(file_path):with open(file_path) as file:...
在函数内部定义了file
这个变量,这个变量只在read_text_from_file
这个函数里有效,在函数外部则无法访问
2.2 全局变量
相对应的,全局变量则是定义在整个文件层次上的,比如下面这段代码:
MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):if value < MIN_VALUE or value > MAX_VALUE:raise Exception('validation check fails')
如果运行这段代码,程序便会报错:
UnboundLocalError: local variable 'MIN_VALUE' referenced before assignment
2.3 函数内部使用全局变量
这是因为,Python的解释器会默认函数内部的变量为局部变量,但是又发现局部变量MIN_VALUE
并没有声明,因此就无法执行相关操作。所以,如果一定要在函数内部改变全局变量的值,就必须加上global
这个声明:
MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):global MIN_VALUE...MIN_VALUE += 1...
validation_check(5)
这里的global
关键字,并不表示重新创建了一个全局变量MIN_VALUE
,而是告诉Python解释器,函数内部的变量MIN_VALUE
就是之前定义的全局变量,并不是新的全局变量,也不是局部变量。这样,程序就可以在函数内部访问全局变量并修改它的值
2.4 局部变量和全局变量同名
另外,如果遇到函数内部局部变量和全局变量同名的情况,那么在函数内部,局部变量会覆盖全局变量,比如下面这种:
MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):MIN_VALUE = 3...
在函数validation_check()内部,定义了和全局变量同名的局部变量MIN_VALUE,那么,MIN_VALUE 在函数内部的值就应该是3而不是1
2.5 嵌套函数内部函数使用外部函数变量
类似的,对于嵌套函数来说,内部函数可以访问外部函数定义的变量,但是无法修改,若要修改,必须加上nonlocal这个关键字:
def outer():x = "local"def inner():nonlocal x # nonlocal关键字表示这里的x就是外部函数outer定义的变量xx = 'nonlocal'print("inner:", x)inner()print("outer:", x)
outer()
# 输出
inner: nonlocal
outer: nonlocal
如果不加上nonlocal这个关键字,而内部函数的变量又和外部函数变量同名,那么同样的,内部函数变量会覆盖外部函数的变量
def outer():x = "local"def inner():x = 'nonlocal' # 这里的x是inner这个函数的局部变量print("inner:", x)inner()print("outer:", x)
outer()
# 输出
inner: nonlocal
outer: local
三、闭包
第三个重点,介绍一下闭包(closure)
闭包其实和刚刚讲的嵌套函数类似,不同的是,这里外部函数返回的是一个函数而不是一个具体的值。返回的函数通常赋于一个变量,这个变量可以在后面被继续执行调用
3.1 闭包的定义
举个例子更容易理解一些。比如,想计算一个数的n次幂,用闭包可以写成下面的代码:
def nth_power(exponent):def exponent_of(base):return base ** exponentreturn exponent_of # 返回值是exponent_of函数square = nth_power(2) # 计算一个数的平方
cube = nth_power(3) # 计算一个数的立方
square
# 输出
<function __main__.nth_power.<locals>.exponent(base)>cube
# 输出
<function __main__.nth_power.<locals>.exponent(base)>print(square(2)) # 计算2的平方
print(cube(2)) # 计算2的立方
# 输出
4 # 2^2
8 # 2^3
这里外部函数nth_power()
返回值,是函数exponent_of()
,而不是一个具体的数值
需要注意的是,在执行完square = nth_power(2)
和cube = nth_power(3)
后,外部函数nth_power()
的参数exponent
,仍然会被内部函数exponent_of()
记住。这样,之后调用square(2)或者cube(2)时,程序就能顺利地输出结果,而不会报错说参数exponent没有定义
3.2 闭包的使用场景
看到这里,也许会疑问为什么要闭包呢?上面的程序,也可以写成下面的形式
def nth_power_rewrite(base, exponent):return base ** exponent
确实可以,不过要知道,使用闭包的一个原因是让程序变得更简洁易读。设想一下,比如需要计算很多个数的平方,那么写成下面哪一种形式更好呢?
# 不适用闭包
res1 = nth_power_rewrite(base1, 2)
res2 = nth_power_rewrite(base2, 2)
res3 = nth_power_rewrite(base3, 2)
...# 使用闭包
square = nth_power(2)
res1 = square(base1)
res2 = square(base2)
res3 = square(base3)
...
显然是第二种,是不是?首先直观来看,第二种形式每次调用函数都可以少输入一个参数,表达更为简洁
其次,和上面讲到的嵌套函数优点类似,函数开头需要做一些额外工作,而又需要多次调用这个函数时,将那些额外工作的代码放在外部函数,就可以减少多次调用导致的不必要的开销,提高程序的运行效率
另外还有一点,闭包常常和装饰器(decorator)一起使用
四、总结
学习了Python函数的概念及其应用,有几点需要注意:
- Python中函数的参数可以接受任意的数据类型,使用起来需要注意,必要时请在函数开头加入数据类型的检查
- 和其他语言不同,Python中函数的参数可以设定默认值
- 嵌套函数的使用,能保证数据的隐私性,提高程序运行效率
- 合理地使用闭包,则可以简化程序的复杂度,提高可读性
【Python核心】不可或缺的自定义函数相关推荐
- python学习笔记之自定义函数
live long and prosper 自定义函数 def greet_user():"""现实简单的问候语"""print(" ...
- Python爬虫笔记——def()自定义函数的几种参数
Python自定义函数是以def开头,空一格之后是这个自定义函数的名称,名称后面是一对括号,括号里放置形参列表,结束括号后面一定要有冒号":",函数的执行体程序代码也要有适当的缩排 ...
- 洗礼灵魂,修炼python(21)--自定义函数(2)—函数文档,doctest模块,形参,实参,默认参数,关键字参数,收集参数,位置参数...
函数文档 1.什么是函数文档: 就是放在函数体之前的一段说明,其本身是一段字符串,一个完整的函数需要带有函数文档,这样利于他人阅读,方便理解此函数的作用,能做什么运算 2.怎么查看函数文档: func ...
- 54.Python的def语句自定义函数
54.def语句自定义函数 文章目录 54.def语句自定义函数 1.课题导入-数学中的函数 1.1 计算圆的面积 1.2 计算不规则图形的面积 2. 什么是函数 3. 函数分类 3.1 内置函数 3 ...
- python中平均值函数_python自定义函数ma(x,y)求简单平均值输出结果到列表
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 def ma(x,y): ''' # 自定义函数"ma(x,y)"指南 函数格式:ma(x,y) 函数功能:求序列数据x的y周期的简单 ...
- python基础教程: 自定义函数
多态 我们可以看到,Python 不用考虑输入的数据类型,而是将其交给具体的代码去判断执行,同样的一个函数(比如这边的相加函数 my_sum()),可以同时应用在整型.列表.字符串等等的操作中. 在编 ...
- python核心,内建函数,高阶函数
晨测 global和nonlocal区别 写一个递归的阶乘 回顾 1.global和nonlocal 关键字 2.函数的递归 1.查找规律 2.设置退出条件 3.性能 3.闭包 外函数中定义一个内函数 ...
- 09 | 不可或缺的自定义函数
1.函数基础 函数是程序中不可或缺的一部分,之前也已经学了很多python的内置函数. def my_func(message):print('Got a message: {}'.format(me ...
- python核心教程:max函数怎么使用
max函数可以用于返回多个数字中最大的那个值,如果没有传递参数,则结果为"-Infinity",如果至少有一个参数无法转换为数字,则结果为NaN.下面我们就来看看max函数的具体使 ...
最新文章
- json数据在前端(javascript)和后端(php)转换
- 禁用当前的账户win7_拯救你的win7系统,电脑优化到位,打游戏才会流畅
- 谁都可能是凶手:《八面埋伏》观看手记
- NO.1_python_scrapy组成爬取多页数据连接数据库配置文件书写
- paip.c3p0 nullpointexcept 配置文件根路径读取bug 解决
- 如何让Ubuntu联网
- 电脑安装有道后打开word文档很慢
- Qt无边框窗体实现方案
- 考研复习--高等数学
- Matlab数学建模(八):评价型模型
- 论大数据时代下的海量数据存储技术
- nginx+keepalive实现高可用负载均衡
- 将SkeyeVSS综合安防监控视频流媒体云平台监控画面嵌入微信公众号进行直播
- O2O、C2C、B2B、B2C、F2C的区别在哪里?
- MySQL 数据(字段)类型
- 谈谈 SAP iRPA Studio 创建的本地项目的云端部署问题
- 2022年校招互联网大厂薪酬状况如何?“白菜”总包接近40W是真是假?
- 基于stc15f2k60s2芯片单片机编程(闹铃)
- Libtorch的介绍与使用方法
- atom linux64下载,ATOM下载