函数
为什么要写函数?为了省内存空间,同时使得主函数逻辑清楚,便于读者阅读(不用拘泥于如何实现,只需知道这个函数的功能)
定义函数
def greet_user():
"""显示简单的问候语"""
print("Hello")
greet_user()
输出效果为:
Hello
关键字def表明要定义一个函数
首行 def 函数名():
跟在def greet_user():后的所有缩进行构成了函数体
第二行的文本被称为文档字符串的注释,描述了函数是干嘛的。文档字符串用三个双引号括起来,实质上是用来生成有关程序中函数的文档
调用函数
只需写函数名+()即可
给函数起别名
函数名其实就是指向一个函数对象的引用,所以完全可以把函数名赋给另外一个变量a,就可以通过a来调用,相当于给函数起了别名,当函数名比较长而又经常调用时,给函数起简洁的别名可以简化一定的书写过程和阅读过程。
一个样例:
>>>a = max
>>>a(3,2,-1,5)
5
向函数传参
def greet_user(username):
"""显示简单的问候语"""
print("Hello, "+username.title()+"!")
greet_user('Horace')
输出效果为:Hello, Horace!
定义时向()填入参数即可,同时调用时也要传与定义的形参列表相符的参数个数
函数定义中的username称为形式参数,调用函数时的’Horace’称为实参
实参的分类
位置实参
调用函数中的每个实参都关联到函数定义中的一个形参,最简单的关联方式是基于实参的顺序。这种关联方式叫做位置实参
def describe_pet(animal_type,pet_name):
print("\nI have a "+animal_type+".")
print("My "+animal_type+"'s name is "+pet_name.title()+".")
describe_pet('hamster','harry')
输出效果为:
I have a hamster.
My hamster's name is Harry.
一一对应,学过C的都知道,不展开讲了
关键字实参
关键字实参是传递给函数的名称-值对。直接在实参中将名称和值关联起来,因此向函数传递参数不会混淆
def describe_pet(animal_type,pet_name):
print("\nI have a "+animal_type+".")
print("My "+animal_type+"'s name is "+pet_name.title()+".")
describe_pet(pet_name='harry',animal_type='hamster')
输出效果为:
I have a hamster.
My hamster's name is Harry.
函数参数默认值
只需在函数定义处给形参赋初始值
def describe_pet(animal_type = 'dog',pet_name):
如果调用函数时没有给齐参数则会使用函数的默认值,给齐则会优先使用实参
但要注意,函数定义默认参数时,这个默认参数必须指向不变的对象。讲完返回值后再讲为什么
返回值
可返回一个简单值
语法上用return
def get_formatted_name(first_name,last_name):
"""返回整洁的姓名"""
full_name = first_name +" "+ last_name
return full_name.title()
musician = get_formatted_name('jimi','hendrix')
print(musician)
输出效果为:
Jimi Hendrix
甚至可以返回多个值
def example(x,y):
return x+1,y+1
j,k = example(3,5)
# 将example返回的两个值对应赋给j,k
print(j)
print(k)
输出为:
4
6
这跟c语言里函数只能返回一个值显然不同,python那么特殊吗?
但实际上它仍然是返回一个值。为什么这么所=说呢?
如果你试着打印example(3,5)
的结果,将会看到输出结果为(3,5)
。这个函数返回了一个元组,只不过包含了两个值。所以函数本质上仍是返回一个值
也可返回一个字典
def build_person(first_name,last_name):
"""返回一个字典,其中包含一个人的信息"""
person = {'first':first_name,'last':last_name}
return person
musician = build_person('jimi','hendrix')
print(musician)
输出效果为:
{'first': 'jimi', 'last': 'hendrix'}
回顾前面的问题,为什么要强调函数定义默认实参时,默认参数需指向不变的对象呢?
我们来看看一个例子:
def add_end(L=[]):
L.append('END')
return L
这个函数在没有传入参数时会加个END,以标识此次没有参数传入,正常运行是没有问题的。
>>> add_end([1, 2, 3])
[1, 2, 3, 'END']
>>> add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']
然而当多次不提供参数时…
>>> add_end()
['END']
第一次结果仍是对的,然而第二次调用时
>>> add_end()
['END', 'END']
我们显然不想看到两个END。为什么出现了两个呢?
是因为第一次调用时创建了L这个变量,它指向列表list,而列表是可变的,之后append(‘END’)产生两个不是很自然的事吗?
所以要指向一个不变的对象,如元组
小技巧:让实参变成可选的
通过给字符串空的初始值,再配合if else即可实现,请看示例代码
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
print(full_name.title())
musician = get_formatted_name('jimi','hendrix')
这样传参的时候填不填middle_name都没什么所谓,但要对应好位置。
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',str(28))
print(musician)
输出效果为:
{'first': 'jimi', 'last': 'hendrix', 'age': '28'}
如果传参中有年龄(非空字符串),则将age-28的键值对加入字典中**(妙蛙种子)**
传递列表字典等数据结构
def greet(users):
"""问候列表中的每个用户"""
for user in users:
print("Hello, "+user.title()+".")
user_names = ['Horace','Licko','Jiang']
greet(user_names)
输出效果为:
Hello, Horace.
Hello, Licko.
Hello, Jiang.
字典大同小异,就不在此展开了,接下来均以列表这种数据结构为例讲解
接下来场景是一个3D打印的场景,编写了两个函数,一个用于移动元素,一个用于展示
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):
"""显示所有打印好的模型"""
for completed_model in completed_models:
print(completed_model)
unprinted_designs = ['iphone','xiaomi','huawei']
completed_models = []
print(unprinted_designs,completed_models)
show(completed_models)
效果为:
Printing model: huawei
Printing model: xiaomi
Printing mode1: iphone
huawei
xiaomi
iphone
那么此时的unprinted列表是什么情况?我们多加一个print(unprinted_designs)看一看
结果发现是个空列表
函数内对列表的操作是可以保留的(跟C不同)
那么如果我不想改变列表呢?该怎么做?
答案很简单:传参时传列表的副本
通式:function(list_name[:])
但缺点是花时间花内存来创建副本,除非有那方面的需求,否则不建议这样做
传任意数量的参数
def make_pizza(*toppings):
"""打印顾客点的所有配料"""
print(toppings)
make_pizza('a')
make_pizza('a','b','c','d','e')
输出效果:
('a',)
('a', 'b', 'c', 'd', 'e')
哟吼!好家伙,不管提供多少个参数,他都能接收,输出还是带圆括号()的,好神奇~
此处形参* toppings中的星号*让python创建一个名为topping的空元组,并将所有收到的值传入空元组中
('a',)
('a', 'b', 'c', 'd', 'e')
可结合使用位置实参和任意数量实参
def make_pizza(size,*toppings):
"""打印顾客点的所有配料"""
print("Make a inch "+str(size)+" pizza with following toppings:")
for topping in toppings:
print("-"+topping)
make_pizza(18,'a')
make_pizza(20,'a','b','c','d','e')
输出效果为:
Make a 18 inch pizza with following toppings:
-a
Make a 20 inch pizza with following toppings:
-a
-b
-c
-d
-e
关键字实参+任意数量实参
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_file = build_profile('Albert','Einstein',
location = 'princeton',field = 'phycis')
print(user_file)
输出效果为:
{'first_name': 'Albert', 'last_name': 'Einstein',
'location': 'princeton', 'field': 'phycis'}
程序解析:
build_profile函数形参中**让python建立一个user_info的空字典,并将所有的键值对存入该字典中
items()函数大家应该还没忘吧?返回一个字典的键和值
最后函数返回一个字典
调用build_profile函数得到一个字典赋值给user_file,打印。
ps:profile[key] = value
的key不要加单引号’'噢
将函数存储在模块之中
函数的优点是将代码与主程序分离。
我们可将函数存储在称为模块的独立文件中,再将模块导入到文件中(用import)
import语句允许在当前运行的程序文件中使用模块中的代码
通过将函数存储在独立的文件中,可隐藏程序代码的细节,将重点放在程序的高层逻辑上,还能让我这个函数被多个程序调用。
学C的知道头文件,模块跟头文件其实差不多dddd
导入整个模块
要让函数是可导入的,得先创建模块。模块是扩展名为.py的文件(ipynb可不可以呢?存疑,等我试试)
以下是vs code的运行环境
创建一个包含函数make_pizza的模块
def make_pizza(size,*toppings):
"""打印顾客点的所有配料"""
print("Make a "+str(size)+" pizza with following toppings:")
for topping in toppings:
print("-"+topping)
该代码存入pizza.py
然后我们在pizza.py所在的目录中创建另一个making_pizzas.py的文件
在making_pizzas.py文件中导入pizza模块,调用函数两次
运行成功。
程序是怎么运行的呢?代码行import pizza让python打开pizza.py,并将其中的所有函数都复制到这个程序中。你是看不到复制的代码的,因为这个程序运行时,Python在幕后复制。
要调用被导入模块中的函数,导入模块名称.函数名(),跟C++调用类里函数是一样的。
总的来说,模块名module_name.py,函数记为function,语法:(以英文字面意思理解)
导入一个模块 import module_name.py
调用模块中的函数 module_name.function_name()
导入特定的函数
语法:from module_name import function_name
from pizza import make_pizza
make_pizza(16,'pepperoni')
make_pizza(12,'mushroom','green peppers','extra cheese')
运行结果如下(在终端界面),成功运行
可以用逗号分隔函数名,从而导入多个的函数
如果使用这种语法,调用函数时不需要使用 .
这是因为我们在import语句中显式导入了函数,所以调用时只需指定其名称
使用as给函数指定别名
如果要导入的函数的名称可能与程序中现有的名称冲突,或者函数的名称太长,可指定简短而独一无二的别名。如何做呢?
导入时做。请看下面示例
from pizza import make_pizza as mp
mp(16,'pepperoni')
mp(12,'mushroom','green peppers','extra cheese')
运行结果如下,成功运行
因为这里直接导入了特定的函数,所以直接用函数名可调用。然后因为as给make_pizza起了mp的别名(有C函数指针那味了),所以可以通过mp来调用
**语法:from module_name import function_name as
既然可以给函数指定别名,那么模块能有别名吗?能!
使用as给模块指定别名
如果模块名很长就可以简便工作
语法:import module_name as mn
import pizza as p
p.make_pizza(16,'pepperoni')
p.make_pizza(12,'mushroom','green peppers','extra cheese')
运行结果如下,成功运行
导入模块中的所有函数
from pizza import *
make_pizza(16,'pepperoni')
make_pizza(12,'mushroom','green peppers','extra cheese')
运行结果如下,成功运行
import语句中的*让python将模块pizza中的每个函数都复制到这个程序文件中。
由于导入了每个函数,所以可以通过名称来调用函数
但书上警告说:使用并非自己编写的大型模块时,最好不要采用这种导入方法,如果导入函数名称中与你的项目中使用的名称相同,可能会覆盖掉函数。最好的做法是:只导入你需要使用的函数,要么导入整个模块并使用句点.表示法
函数编写指南
(这些都是约定俗成的东西,大家都遵守这样的规范,代码就方便阅读)
-
编写函数时应给函数指定描述性名称,且只在其中使用小写字母和下划线。(因为后面类要以大写字母开头以示区分)
-
每个函数都应包含简要阐述其功能的注释,采用文档字符串格式(如果不熟悉,建议回到前面复习一下)
-
给形参指定默认值时,等号两边不要有空格,如
def function(p0,p1='')
,调用时的关键字形参也是如此 -
如果程序或模块包含多个函数,可使用两个空行将相邻的函数隔开,更容易知道前一个函数的末尾和后一个函数的开头
-
所有的import语句都应该放在文件开头(除非你在开头写注释hhh)