本节内容
1、函数的定义
2、如何使用函数
3、函数的返回值
4、使用注释改进文档
5、传递任意数量的实参
6、函数与模块
1、函数的定义
函数的一般定义(中学/数学):函数的近代定义是给定一个数集A,假设其中的元素为x,对A中的元素x施加对应法则f,记作f(x),得到另一数集B,假设B中的元素为y,则y与x之间的等量关系可以用y=f(x)表示,函数概念含有三个要素:定义域A、值域C和对应法则f。
编程语言中函数的定义(计算机):函数是逻辑结构化和过程化的一种编程方法。
函数的定义方法(案例):
>>> def fib(n): # write Fibonacci series up to n ... """Print a Fibonacci series up to n.""" ... a, b = 0, 1 ... while a < n: ... print(a, end=' ') ... a, b = b, a+b ... print() """ def:定义函数的关键字 flb:函数名 (n):定义函数的参数 """""":文档描述 代码块: a, b = 0, 1 while a < n: print(a, end=' ') a, b = b, a+b print() """
具体学习:
下面打印一个问候的简单函数:
1 def greet_user(): #定义一个函数,关键词为"def",函数名为"greet_user",最后以“:”结尾 2 """显示简单的问候语""" #描述函数的具体功能 3 print("Hello!") #函数体的代码块,用于实现函数功能 4 5 greet_user() #调用函数
注意:(1)在第一行定义函数中,不需要任何信息就能完成工作,因此括号是空的(即便如此,括号必不可少!)
(2)要调用函数,可以依次指定函数名以及括号括起的必要信息。在第五行代码中,因为此函数greet_uesr()括号中不需要任何信息,只需要输入greet_uesr()即可。和预期效果一样,打印Hello!:
练习:大脑P149
1 def search4vowels(): 2 vowels = set('aeiou') 3 word = input("Provide a word to search for vowels:") 4 found = vowels.intersection(set(word)) 5 for vowel in found: 6 print(vowel) 7 8 search4vowels() 9 search4vowels() #可以重复多次调用
2、如何使用函数(传递实参)
函数的使用方法(先看案例)
>>> def fib2(n): # return Fibonacci series up to n ... """Return a list containing the Fibonacci series up to n.""" ... result = [] ... a, b = 0, 1 ... while a < n: ... result.append(a) # see below ... a, b = b, a+b ... return result ... >>> f100 = fib2(100) # call it >>> f100 # write the result [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
2.1 向函数传递信息
针对greet_user()只要稍作修改,就可不仅实现Hello,还可以将用户名字作为抬头:
1 def greet_user(username): 2 """显示简单的问候语""" 3 print("hello,"+username.title()+"!") 4 5 greet_user("zhichao")
代码greet_user("zhichao")调用函数greet_user()时,向它传递执行print()语句所需要的信息username。
2.2 实参和形参
greet_user(username) #username 为形参;函数完成其工作时所需要的信息。
greet_user("zhichao") #"zhichao"为实参;实参是调用函数时传递给函数的信息。
形参:形式参数,不是实际存在的,是虚拟变量。在定义函数和函数体的时候使用形参,目的是在函数调用时接收实参
实参:实际参数,调用函数时传递给函数的参数,可以是常量、变量,表达式,函数,传给形参
区别:形参是虚拟的,不占用空间,形参变量只有在被调用时才分配内存单元,实参是一个变量,占用内存空间,数据传递单向,实参传给形参,不能倒过来。
传递实参:
1、位置实参(基于实参的顺序):
1 #zhichao 2 3 def describe_pet(animal_type,pet_name): 4 """显示宠物信息""" 5 print(" I have a" + animal_type + ".") 6 print("My" + animal_type + "'s name is "+pet_name.title() + ".") 7 8 describe_pet('hamster','harry') 9 describe_pet('cat','python') #函数调用多次是一种效率极高的工作方式
位置实参的顺序很重要,请确认函数调用中实参的顺序与函数定义形参的顺序一致
2 关键字参数
1 def describe_pet(pet_name,animal_type="dog",): 2 """显示宠物信息""" 3 print(" I have a " + animal_type + ".") 4 print("My" + animal_type + "'s name is "+pet_name.title() + ".") 5 6 describe_pet(pet_name='harry',animal_type='hamster') #关键字实参 7 describe_pet(pet_name='python',animal_type='cat') #关键字实参
思考1:关键字参数是否需要与形参顺序一致?
思考2:关键字参数和位置参数能否同时存在?
例:
#eg1 def func_test(x,y): print(x) print(y) func_test(x=1,2) #? #eg2 def func_test(x,y): print(x) print(y) func_test(1,y=2) #? #eg3 def func_test(x,y,z): print(x) print(y) func_test(1,y=2,3) #? #结论?
****关键参数是不可以写在位置参数前面的
2.3 默认值
编写函数时,可以给形参指定默认值。
1 def describe_pet(pet_name,animal_type="dog",): 2 """显示宠物信息""" 3 print(" I have a " + animal_type + ".") 4 print("My" + animal_type + "'s name is "+pet_name.title() + ".") 5 6 describe_pet('harry','hamster') 7 describe_pet('python','cat')
2.4 像函数中传递列表:
eg:
1 #Zhichao 2 3 def greet_users(names): 4 """向列表中的每位用户都发出简单的问候""" 5 for name in names: 6 msg = "Hello, "+name.title() +"!" 7 print(msg) 8 9 usernames = ['hannah','ty','margot'] 10 greet_users(usernames)
3、函数的返回值
函数返回的值被称为函数的返回值;
在函数中,可用return语句将值返回到调用函数的代码行;
返回值能将你程序的大部分繁重工作移到函数中去完成,从而简化主程序。
3.1 返回简单值
eg(formatted_name1):
1 #zhichao 2 3 def get_formatted_name(first_name,last_name): 4 """返回整洁的姓名""" 5 full_name = first_name + ' '+ last_name 6 return full_name.title() 7 8 musician = get_formatted_name('jimi','hendrix') #调用返回值的函数时,需要提供一个变量,用于存储返回的值 9 print(musician)
3.2 让实参变成可选的
有时候,需要让实参变成可选的,这样使用函数的人就只需要在必要时候才提供额外信息。
我们对:formatted_name1 进行扩展(函数的可扩展性)
eg(formatted_name2):
1 def get_formatted_name(first_name,middle_name,last_name): 2 """返回整洁的姓名""" 3 full_name = first_name + ' '+middle_name+' '+ last_name 4 return full_name.title() 5 6 musician = get_formatted_name('jimi','lee','hendrix') #有些人有中间名 7 print(musician)
有些人不一定有中间名,那么对函数进行优化:
eg(formatted_name2):
#zhichao 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() musician = get_formatted_name('jimi','lee','hendrix') musician1 = get_formatted_name('jimi','hendrix') print(musician) print(musician1)
3.3 返回多个值
函数可以返回任意类型的值,包括列表、字典和集合等较为复杂的数据结构。
大脑P159:
返回一个值:
返回一个集合:
1 # Zhichao 2 3 def search4vowels(word): 4 """Return a boolean based on any vowels found""" 5 vowels = set('aeiou') 6 found = vowels.intersection(set(word)) 7 return found 8 word = search4vowels('hello Zhichao') 9 print(word)
返回数据:
1 #Zhichao 2 3 def search4vowels(word): 4 """Return a boolean based on any vowels found""" 5 vowels = set('aeiou') 6 return vowels.intersection(set(word)) 7 word = search4vowels('hello Zhichao') 8 print(word)
返回字典:
1 #Zhichao 2 3 def build_person(first_name,last_name): 4 """返回一个字典,其中包含有关一个人的信息""" 5 person = {'first':first_name,'last':last_name} 6 return person 7 8 musician = build_person('jimi','hendrix') 9 print(musician)
4.使用注释改进文档
复习python的四种数据类型。
当我们调用函数时,才知道我们需要输入的参数和返回值的类型“type”
对此,我们的一种解决办法是把这个信息增加到docstring
python3 注解的记法
1.函数注解是可选的。
2.函数注解可以提供信息。
eg:
1 #zhichao 2 3 def search4vowels(word:str) ->set: 4 """Return a boolean based on any vowels found""" 5 vowels = set('aeiou') 6 return vowels.intersection(set(word)) 7 8 help(search4vowels)
5.传递任意数量的实参
1. *args,传入多个参数,转化成元组。
假如一个函数定义一个披萨的配料,但并不知道有多少配料需要加入,在参数不确定的情况下,我们引入任意数量的实参。
eg:
#Zhichao def make_pizza(*toppings): """打印顾客点的所有配料""" print(toopings) make_pizza("peperoni") make_pizza("mushroom","green peppers","extra cheese")
注意:*toppings的值是一个封装好的空元组,并将所有接收到的值都封装在这个元组里。
2.**kwargs,把关键字参数,转化成字典。
eg:
1 # -*- coding:utf-8 -*- 2 # Author:Zhichao 3 4 def infos(**kwargs): 5 """打印个人信息""" 6 print(kwargs) 7 8 infos(name="Zhichao",age="24",job="IT")
设计一个对顾客点披萨进行描述:
1 #Zhichao 2 3 def make_pizza(*toopings): 4 """概述要制作的披萨""" 5 print(" Making a pizza with the following toppings") 6 for topping in toopings: 7 print("--"+topping) 8 9 make_pizza("peperoni") 10 make_pizza("mushroom","green peppers","extra cheese")
结合位置实参和任意数量实参:
#Zhichao def make_pizza(size,*toopings): #回顾位置实参应放在前面 """概述要制作的披萨""" print(" Making a "+str(size)+"-inch pizza with the following toppings") for topping in toopings: print("--"+topping) make_pizza(14,"peperoni") make_pizza(12,"mushroom","green peppers","extra cheese")
6.函数与模块
函数的优点之一是将代码与主程序分离;
我们可以更进一步,将函数存储在被称为“模块”的独立文件中,再将模块导入主程序;
import语句允许我们在当前运行的程序文件中使用模块中的代码。
优势:
1.通过将函数存储在独立的文件中,可隐藏程序代码的细节,将重点放在程序的高层逻辑上;
2.可以让不同的程序中重用函数;
3.可与其他程序员共享这些文件而不是整个程序;
4.知道如何导入函数能让你使用其他程序员编写的函数库。
6.1 导入整个模块
模块是扩展名为.py的文件(如一些内置的模块:C:ProgramDataAnaconda3Lib)
下面我们来创建一个模包含函数make_pizza()的模块。
pizza.py
1 #Zhichao 2 3 def make_pizza(size,*toopings): 4 """概述要制作的披萨""" 5 print(" Making a "+str(size)+"-inch pizza with the following toppings") 6 for topping in toopings: 7 print("--"+topping)
接下来,我们在pizza.py所在目录下创建一个另外的名为making_pizzas.py文件,这个文件导入刚创建的模块,在调用make_pizza()两次:
making_pizzas.py
1 import pizza #调用模块 2 3 pizza.make_pizza(16,'pepperoni') #调用模块的函数 4 pizza.make_pizza(12,"mushroom","green peppers","extra cheese")
6.2 导入特定的函数
可以导入模块中的特定函数,方法如下:
1 from module_name import function_name
通过用逗号分开函数名可以同时导入多个函数:
1 from module_name import function_0,function_1,function_2
对于前面的pizza案例,我们可以导入模块中的特定函数:
1 from pizza import make_pizza 2 make_pizza(16,'pepperoni') #调用模块的函数 3 make_pizza(12,"mushroom","green peppers","extra cheese")
若使用此种方法,调用函数时不需要用句点。
6.3 使用as 给函数指定别名
思考:如果你导入的函数名与程序中现有的名称冲突怎么办?或者你调用的函数名称太长怎么办?
我们可以指定函数的另一个名称,类似于外号。
下面我们根据make_pizza指定名称
1 from pizza import make_pizza as mp 2 mp(16,'pepperoni') #调用模块的函数 3 mp(12,"mushroom","green peppers","extra cheese")
通用语法格式:
1 from moudle_name import function_name as fn
6.4 使用as给模块指定别名
eg:
1 import pizza as p 2 3 p.make_pizza(16,'pepperoni') 4 p.make_pizza(12,"mushroom","green peppers","extra cheese")
通用语法格式:
import moudle_name as mn
6.5 导入模块中的所有函数
使用星号(*)运算符可以让Python导入模块中的所有函数:
1 #Zhichao 2 from pizza import* 3 4 make_pizza(16,'pepperoni') 5 make_pizza(12,"mushroom","green peppers","extra cheese")
通用语法格式:
from moudle_name import *
Head First Python
大脑P162
使用注解改进文档:
关于注解更多详细内容参见PEP3107
(https://www.python.org/dev/peps/pep-3107)
大脑 P164
函数:我们已经知道些什么
* 函数是命名的代码块。
* def关键字用来命名函数,函数代码在def关键字下(相对于def关键字)缩进
* Python的三重引号字符串可以用来函数增加多行注释。如果采用这种方式,他们称之为docstring
* 函数可以接受任意多个命名参数
* return语句允许函数返回任意值(也可以不反回任何值)
*函数注解可以用来描述函数参数的类型
建立一个通用的函数:
1 #Zhichao 2 3 def search4vowels(phrase: str) -> set: 4 """return any vowels found in a supplied phrase""" 5 vowels = set('aeiou') 6 return vowels.intersection(set(phrase)) 7 8 def search4letters(phrase:str,letters:str)->set: 9 """return a set of the 'letters' found in 'phrase'. """ 10 return set(letters).intersection(set(phrase)) 11 12 help(search4letters) 13 print(search4letters('hitch-hiker','aeiou')) 14 print(search4letters('galaxy','xyz'))
函数补充:
函数可以调用函数吗?
函数引用
eg:
1 # Author:Zhixhao 2 3 def search4letters(phrase:str,letters:str)->set: 4 """return a set of the 'letters' found in 'phrase'. """ 5 logger("search4letters") 6 return set(letters).intersection(set(phrase)) 7 8 def logger(source): 9 print("from %s"%source) 10 help(search4letters) 11 12 print(search4letters('hitch-hiker','aeiou')) 13 print(search4letters('galaxy','xyz'))
局部变量与全局变量以及其作用域:
1 #Zhichao 2 3 school = "中山大学" 4 5 def change_name(name:str): 6 """修改名字""" 7 # global school 8 school = "中山大学南方学院" 9 print("before change",name,school) 10 name = name.title() #局部变量,可以理解这个函数就是这个变量的作用域 11 # age = 18 12 print("after change",name) 13 14 # print("age",age) 15 name = "zhichao" 16 change_name(name) 17 print(name,school)
不建议使用 global 在函数内部修改全局变量,容易导致逻辑混乱不清
再来看一个例子:
1 # Author:Zhichao 2 3 names = ["Marry","Jack","Lin"] 4 def change_name(): 5 names.append("Zhichao") 6 print("inside func",names) 7 8 change_name() 9 print(names)
请阅读大脑P185-p187
并尝试理解:
总结:
可变:列表、字典和集合
不可变:字符串、整数和元组
(回顾字典的key命名)
递归:
1 def calc(n): 2 print(n) 3 if int(n/2) == 0: 4 return n 5 return calc(int(n/2)) 6 7 calc(10)
递归特性:
1.必须有一个明确的结束条件
2.每次进入更深一层递归时,问题规模相比上次递归应有所减少。
3.递归效率不高,递归次数过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会多加一层栈帧,每当函数返回,栈就会减少一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。)