文章目录
第八章.函数
通过使用函数,程序的编写、阅读、测试和修复都将更容易
8.1 定义函数def
使用关键词def告诉Python你要定义一个函数
函数定义:def 函数名(参数):
函数定义代码放在调用代码前面
# 定义函数
def greet_user(usename):
"""显示简单的问候语"""
print("Hello, " + usename.title() + "!")
# 调用函数
greet_user('jesse')
文档字符串(docstring),描述函数是做什么的,文档字符串用三引号括起来,如"""显示简单的问候语"""
在greet_user(‘jesse’)中,实参’jesse’传递给了函数greet_user(),这个值被存储在形参username中
8.3 返回值 直接return
函数可以返回任何类型的值,包括列表和字典等较复杂的数据结构
8.3.1 返回简单值
在函数中,可使用return语句将值返回到调用函数的代码行
def my_abs(x):
if (x >= 0):
return x
else:
return -x
res = my_abs(-3)
print(res) # 输出3
8.3.2 让实参变成可选的
给形参middle_name指定一个默认值——空字符串,并将它放在形参列表的末尾
这样调用时可以传两个参数,也可以传3个参数
传参数有两种方式
get_formatted_name('jimi', 'hendrix')
get_formatted_name(first_name='jimi', last_name='hendrix')
# 实参可选
def get_formatted_name(first_name, last_name, middle_name=''):
if middle_name:
full_name = first_name + ' ' + middle_name + ' ' + last_name
else:
full_name = first_name + ' ' + last_name
return full_name.title()
print(get_formatted_name('jimi', 'hendrix')) # 输出 Jimi Hendrix
print(get_formatted_name('john', 'hooker', 'lee')) # 输出 John Lee Hooker
8.3.3 返回字典
def build_person(first_name, last_name, age=''):
"""返回一个字典,其中包含有关一个人的信息"""
person = {'first': first_name, 'last': last_name}
if age:
person['age'] = age
return person
musician = build_person('jimi', 'hendrix', 27)
# 或 musician = build_person(first_name='jimi', last_name='hendrix', age=27)
# 这是其实参数的顺序可以随意
print(musician) # 输出 {'first': 'jimi', 'last': 'hendrix', 'age': 27}
注意:这里虽然默认age=''
是字符串,但是你可以输入一个int数值,相当于age = 27
8.3.4 返回多个函数值
def createDataSet():
group = np.array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])
labels = ['A','A', 'B', 'B']
return group, labels
x, y = createDataSet() # 用 x,y 去接收变量 group,labels
print(x,y,sep='\n\n') # sep 指定 x 和 y 的分隔符,见help(print)
# res = createDataSet()
# x, y = res
8.3.5 pass空语句 用来占位
pass语句在函数中的作用:当你在编写一个程序时,执行语句部分思路还没有完成,这时你可以用pass语句来占位,保证格式完整,也可以当做是一个标记,标记这是我之后要来完成的函数
def isDigit():
pass # do nothing
定义一个函数isDigit,但函数体部分暂时还没有完成,又不能空着不写内容,因此可以用pass来替代占个位置
Python中的pass
类似C++中;
if(a == 2) {
; // do nothing
} else {
cout << a << endl;
}
8.4 传递列表
8.4.1 在函数中修改列表
需要打印的设计存储在一个列表中,打印后移到另一个列表中
编写两个函数,实现上面的需求
# 定义函数-传递列表
def print_models(unprinted_designs, completed_models):
"""
模拟打印每个设计,直到打印没有未打印的设计为止
打印每个设计后,都将其移到列表completed_models中
"""
while unprinted_designs:
current_design = unprinted_designs.pop()
print("Printing model: " + current_design)
completed_models.append(current_design)
def show_completed_models(completed_models):
"""显示打印好的所有模型"""
print("\nThe following models have been printed:")
for completed_model in completed_models:
print(completed_model)
unprinted_designs = ['iphone', 'robot pendant', 'dodecahedron']
completed_models = []
# 调用上面定义的两个函数
print_models(unprinted_designs, completed_models)
show_completed_models(completed_models)
描述性的函数名让别人阅读这些代码时更清晰
用了函数的好处:如果我们需要对打印代码进行修改,只需修改这些代码一次,就能影响所有调用该函数的地方,效率很高
理念:每个函数都应只负责一项具体工作,这由于使用一个函数来完成两项工作
8.4.2 禁止函数修改原列表
有时需要禁止函数修改原列表,可以像函数传递列表的副本而不是原件,这样函数所做的任何修改都只影响副本,而丝毫不影响原件
切片表示法[:]创建列表副本
如可以把上面例子中的调用修改为
print_models(unprinted_designs[:], completed_models)
这样原列表unprinted_designs不会被修改
但是一般不建议创建副本,除非特别需要。因为让函数使用现成的列表可避免花时间和内存创建副本,从而提高效率,在处理大型列表时尤其如此
8.5 传任意多的实参*toppings元组
有时你预先不知道函数需要接受多少个实参,好在Python允许函数从调动语句中收集任意数量的实参
以制作披萨为例
我们无法预先知道顾客要多少种配料
形参*toppings
中的星号让Python创建一个名为toppings的空元组,并将接收的所有值都封装到这个元组中
def make_pizza(*toppings):
"""打印顾客点的所有配料"""
print(toppings)
make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')
输出
('pepperoni',)
('mushrooms', 'green peppers', 'extra cheese')
8.6 传递任意多的实参**user字典
有时你想收集有关用户的信息,但不确定会是什么样的信息。
在下面的例子中,函数build_profile()接受名first_name和姓last_name,同时还接受任意数量的关键字实参:
形参**user_info中的两个星号让Python创建一个名为user_info的空字典,并将收到的所有键-值对,如field='physics'
,都封装在这个字典里
def build_profile(first, last, **user_info):
"""创建一个字典,其中包含我们知道的有关用户的一切"""
profile = {}
profile['first_name'] = first
profile['last_name'] = last
for key, value in user_info.items():
profile[key] = value
return profile
user_profile = build_profile('albert', 'einstein', location='princeton', field='physics')
print(user_profile)
# 输出 {'first_name': 'albert', 'last_name': 'einstein', 'location': 'princeton', 'field': 'physics'}
8.7 lambda表达式
python 使用 lambda 来创建匿名函数
使用lambda比普通的def定义函数要简单,不需要写def和return
lambda函数一般功能简单:单行expression决定了lambda函数不可能完成复杂的逻辑,只能完成非常简单的功能
lambda 函数可以接收任意多个参数 (包括可选参数) 并且返回单个表达式的值
带参数
f = lambda x, y: 2 * x + y
print(f(4, 3)) # 输出11
不带参数
R = lambda : print("Hi")
R() # 输出 Hi
例:
# 输入样例:
# 4 3
# 0 0 2 1
# 2 0 1 1
R = lambda: map(int, input().split())
# R()读入一行数,R() = map(int, input().split()) # 迭代器
n, m = R() # n = 4, m = 3
list_a, list_b = list(R()), list(R()) # 读入两行数
# list_a = [0, 0, 2, 1]
说明:函数名可以赋给变量,简化有些赋值的函数名,当然这会降低可读性
s = sorted
print(s([2,3,1])) # 输出 [1, 2, 3]
8.8 将函数存储在模块中
导入模块相对于把模块中的代码隐形地复制到当前程序中,所以如果模块开头有一句print(“Hello”),则当运行import语句时就会输出Hello
强力推荐的写法
用多少func.py中函数,就导入多少
from func import function_1 as f1, function_2 as f2, function_3 as f3
将函数存储在被称为模块的独立文件中,再将模块导入主程序中,import语句运行在当前运行的程序文件中使用模块中的代码
将函数存储在独立的文件中后,可与其他程序员共享这些文件而不是整个程序,还可以让你知道如何使用其他程序员编写的函数库
一个标准库文件中的import语句示例:
import _collections_abc
from operator import itemgetter as _itemgetter, eq as _eq
from keyword import iskeyword as _iskeyword
import sys as _sys
import heapq as _heapq
from _weakref import proxy as _proxy
from itertools import repeat as _repeat, chain as _chain, starmap as _starmap
from reprlib import recursive_repr as _recursive_repr
sys.path.append()
当我们需要添加自己的搜索目录时,可以通过列表的append()方法,特别是模块和自己写的程序不在同一个目录的情况
import sys
sys.path.append('/Users/macos/Documents/Wilson79/Python/Program')
8.8.1 导入整个模块import(同一目录)
模块是扩展名为.py的文件
下面例子中,Python读取文件时,代码行 import models
让Python打开同一目录下的models.py。你看不到复制的代码,因为这是幕后复制的,你只需要知道你可以使用models.py中定义的所有函数。更详细的说明请看本博客后续补充 1 的内容
调用方法:模块名.函数名(参数)或模块名.变量名
main.py
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@Author : Wilson79
@Filename : main.py
@Date : 2020/1/22 下午06:45
"""
import models # 让Python打开models.py(必须在同一目录下),并将其中的所有函数复制到当前程序中
unprinted_designs = ['iphone', 'robot pendant', 'dodecahedron']
completed_models = []
# 调用方法:模块名.函数名(参数)
models.print_models(unprinted_designs[:], completed_models) # 创建列表副本(一般不建议)
models.show_completed_models(completed_models)
models模块里有个变量叫做num(不在if __name__ == '__main__':
之内),则可以用models.num访问这个变量
models.py
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@Author : Wilson79
@Filename : models.py
@Date : 2020/1/22 下午06:44
"""
def print_models(unprinted_designs, completed_models):
"""
模拟打印每个设计,直到打印没有未打印的设计为止
打印每个设计后,都将其移到列表completed_models中
"""
while unprinted_designs:
current_design = unprinted_designs.pop()
print("Printing model: " + current_design)
completed_models.append(current_design)
def show_completed_models(completed_models):
"""显示打印好的所有模型"""
print("\nThe following models have been printed:")
for completed_model in completed_models:
print(completed_model)
搜索路径
当你导入一个模块,Python 解析器对模块位置的搜索顺序是:
1、当前目录
2、如果不在当前目录,Python 则搜索在 shell 变量 PYTHONPATH 下的每个目录。
3、如果都找不到,Python会察看默认路径。UNIX下,默认路径一般为/usr/local/lib/python/。
模块搜索路径存储在 system 模块的 sys.path 变量中。变量里包含当前目录,PYTHONPATH和由安装过程决定的默认目录。
8.8.2 导入特定函数from module import fun
有时不需要导入全部的函数,可以只导入特定的函数(当前文件只能用特定的函数)
注意:这种方式还是会隐式地导入所有脚本中的内容,运行时也会先执行脚本的 内容,只不过当前文件只能用特定的函数。更详细的说明请看本博客后续补充 1 的内容
语法:
from module_name import function_1, function2, function3
注意:只需写出函数名,不用加()
调用方法
直接使用函数,不需使用句号,因为import语句显式地导入了要用的函数
示例:
new.py
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@Author : Wilson79
@File : new.py
@Time : 2020/1/19 下午03:45
"""
from func import function_1, function_2
function_1() # import显式导入了函数,故可直接使用
function_2()
# function_3() # error,因为没有导入这个函数
func.py
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@Author : Wilson79
@Filename : func.py
@Date : 2020/1/22 下午07:00
"""
def function_1():
print("星期一")
def function_2():
print("星期二")
def function_3():
print("星期三")
8.8.3 用as给模块或函数指定别名
给模块指定别名
import module_name as p
说明:方便你更轻松地调用模块中的函数 p.function(),让你更专注于描述性的函数名,而不是模块名
给函数指定别名
from module_name import function_name as fn
说明:防止导入函数与当前程序冲突,或函数的名字太长
8.8.4 导入模块中的所有函数from module_name import *
使用*
运算符可以让Python导入模块中的所有函数
from module_name import *
由于导入了所有函数,不需要使用句点表示法,而是直接使用函数
new.py
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@Author : Wilson79
@File : new.py
@Time : 2020/1/19 下午03:45
"""
from func import * # 导入func所有函数,且可以直接使用
function_1() # import显式导入了所有函数,故可直接使用
function_2()
function_3()
特别注意:如果不是自己编写的函数库,不建议使用这种导入方式
因为模块中有函数名称与你的项目中的函数使用相同的名称时,会出现意想不到的结果:Python遇到相同名称的函数会覆盖函数,而不是导入所有的函数
8.8.5 Python会覆盖原先定义的函数
from func import * # 导入func所有函数,且可以直接使用
# import显式导入了所有函数,故可直接使用
function_1() # 输出 星期一
# Python会覆盖原先定义的函数
def function_1():
print("你好") # 输出 你好
function_1()
def function_1():
print("函数")
function_1() # 输出 函数
@补充 1:三种 import 的异同点 隐式导入和显式导入
相同之处:不管是哪种 import 导入方式,都会隐式地导入 human.py(脚本)中所有内容,运行 action.py 时会先运行 human.py
不同之处:当前文件能用 human.py 中的哪些函数或类,则需要看你具体 import 的方法
示例:如何理解 Python 中的 if name == ‘main’ 这行代码的含义
补充 2:Python 程序结构推荐
不同函数 def 的定义顺序可以任意,但对于一个函数,定义必须在调用之前
通过定义一个 main 函数,可以让程序的主体内容出现在最开头,而把其他函数定义在 main 下面,这样阅读的时候我会先看到 main 函数,这是一个良好的程序设计习惯,否则你一开始定义十个函数,你往下要翻阅很久才能看到程序核心内容
def main():
pass
if __name__ == '__main__':
main()
补充 3:Python允许函数嵌套定义
def outer(st,Xing,Ming):
def inner(a,b):
print(a,b,sep="")
if st:
inner(Xing,Ming)
else:
inner(Ming,Xing)
outer(True,"Yao","Ming")