Python 函数

函数

定义

函数是对一个代码块的封装,等同于代码块的别名,若要多次利用这一段代码块,只需要调用该函数即可

函数可以将程序分成许多不同的部分,每部分都负责完成一项任务

编写函数时一个重要的思想:每个函数应只负责一项具体的工作,在函数里是可以调用其他函数的

一个函数可以有参数也可以无参,并且可以有返回值,也可以无返回值


函数的组成部分:关键词 def、函数名、圆括号、参数、冒号、函数体

函数名应是描述性名称,主要使用小写字母和下划线来命名函数名

函数使用 关键词def 来定义,def 后面要写明 函数名,并且在函数名后面的 圆括号 中,可以决定该函数是否需要 参数,需要注意的是,不管有无参数,圆括号都是必不可少的。在圆括号后面不要遗忘 冒号

在冒号后面换行后,所有的缩进内容构成了 函数体

def hello():
    print("Hello World!")

在上面的示例中,hello是函数名,没有参数,无返回值,print语句是函数体

文档字符串注释

文档字符串注释一般出现在函数定义之后,函数体的上方,用于描述这个函数的作用、功能等,此注释形式支持多行注释,并且在生成文档中,该多行注释的内容就是生成文档中对这个函数解释的内容

形式:三引号(""")组成

def hello():
	"""
	...
	"""
	print("Hello")

每个函数都应该使用文档字符串注释的形式去简要地阐述其功能与调用方式,调用者只需要通过看文档来了解函数的名称、需要的实参以及返回值的类型,就可以很快理解这个函数的作用以及如何使用该函数

函数调用

函数调用就是让Python执行函数的代码,一个函数可以被调用任意次

调用函数的方式:函数名(参数)

指定 函数名,并在函数名后的圆括号内指定 参数(若函数定义时有参数定义,则需要提供参数;若未定义参数,则不需要在圆括号内指定参数,但圆括号必须有

>>> def hello():
...     print("HELLO")
... 
>>> hello()
HELLO
>>> def hello1(a):
...     print(a)
... 
>>> hello1('hello')
hello

若函数定义时,未要求提供实参,而调用时,提供了实参时,则会报 实参不匹配错误

>>> def hello():
...     print("HELLO")
... 
>>> hello('hello')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: hello() takes 0 positional arguments but 1 was given

报错提示该函数需要 0 个参数,却提供了 1 个参数,所以报错了

>>> def hello(name):
...     print("123")
... 
>>> hello()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: hello() missing 1 required positional argument: 'name'

报错提示调用该函数需要一个实参,并且指明了对应形参的名字

实参与形参

简而言之,在定义函数时圆括号内的变量称为 形参,而在实际调用函数时,在被调函数的函数名后 圆括号 内传入的变量或常量称为 实参

实参 是调用函数时传递给函数的信息

形参 是定义函数时所指定的需要函数接收的参数

>>> def hello(a):
...     print(a)
... 
>>> hello('HELLO')
HELLO

以上的代码,a就是 形参,'HELLO' 就是 实参

定义函数时,形参的个数是没有限制的,无论多少个形参,在调用该函数时实参个数要和形参个数匹配

函数调用时,会将每个实参都关联到函数定义中与之相对应的一个形参

实参的值会传递给形参,形参存储实参传过来的值,并且带入函数中运行

给形参赋默认值

在函数定义时,还可以给形参赋默认值。但函数调用时如果实参有确切的值(给该形参显式地提供了实参),则会覆盖这个默认值

所以若在函数定义时给形参指定了默认值,而调用时无实参要传入给该形参的话,则可以省略相应的实参,这样可以简化函数调用

>>> def hello(name, age='23'):
...     print(name)
...     print(age)
... 
>>> hello('Sean')
Sean
23

在给形参指定默认值时,最好把有默认值的形参在定义函数时放在无默认值形参的后面,这样的话,如果是按位置传参时,则比较好区分有默认值与没有默认值的实参,所以最好在函数解释注释中说清楚函数的推荐调用方式

若在函数定义时,把有默认值的形参放在了无默认值形参的前面,会报语法错误,SyntaxError

>>> def hello(first, middle='', last):
...     if middle:
...             print(first + middle + last)
...     else:
...             print(first + last)

  File "<stdin>", line 1
SyntaxError: non-default argument follows default argument

应把有默认值的形参在定义时放在无默认值形参的后面

>>> def hello(first, last, middle=''):
...     if middle:
...             print(first + middle + last)
...     else:
...             print(first + last)
... 
>>> hello('Sean', 'Zhang')
SeanZhang

注意,此处的空字符串用作条件表达式时,代表着 False,Python将非空字符串解读为 True


给形参设置默认值后,对应的实参就成为可选的了,需要时可提供实参,不需要时就不用提供实参,这样的形参称为 可选形参

若不知道这个默认值设置成什么,而这个形参又是可选的话,不妨将可选形参设置为空字符串,在函数体中用if语句判断该实参即可,再次强调,这个有默认值的形参要放到无默认值形参的后面

>>> def person(first_name, last_name, age=''):
...     if age:
...             message = first_name + " " + last_name + " is " + age + " years old"
...     else:
...             message = first_name + " " + last_name
...     return message
... 
>>> mess = person('Sean', 'Zhang', '24')
>>> print(mess)
Sean Zhang is 24 years old

实参传递

向函数传递实参的方式:

  • 按位置传参
  • 指定形参名传参
  • 向函数传递复杂数据结构
  • 传递任意数量的实参

按位置传参

位置实参就是在调用函数时,实参的顺序应参照形参的顺序,要与形参的顺序一致、个数一致,并且一一关联。函数调用时,会将每个实参关联到每个形参

>>> def hello(name, age, num):
...     print("Your name is " + name)
...     print("Your age is " + age)
...     print("Your num is " + num)
... 
>>> hello('Sean', '24', '16')
Your name is Sean
Your age is 24
Your num is 16

形参的个数可以是任意数量的,不管多少个形参,实参个数只要与其对应后,都将按顺序将函数调用中的实参关联到函数定义中相应的形参

若形参是有默认值的值,若不需要修改这个默认值,则实参传递过程中不用显式地写出来传给实参的值

在一一对应时,如果实参顺序不对,比如需要一个姓名字符串,而却传了一个数字字符串,这时候会出现逻辑错误,输出结果会与预期不符

为了避免有时候失误导致的顺序出错,指定形参名传参 可以避免这个问题

指定形参名传参

指定形参名传参在进行函数调用并输入实参时,就将形参变量关联起来,所以传递实参时不会发生混淆的问题,因为值都与相关的形参一一对应了

此时无需考虑顺序问题,只要将每个形参变量都附上具体的值即可,这样不会产生位置实参中出现的顺序错误导致的逻辑问题,因为已经明确地指出了各个形参所要接收的实参

指定形参名传参的实参顺序并不重要,因为知道各个值应存入哪个形参中。应注意的是,形参名在函数调用时不要写错

>>> def hello(name, age, num):
...     print("Your name is " + name)
...     print("Your age is " + age)
...     print("Your num is " + num)
... 
>>> hello(name='Sean', age='14', num='222')
Your name is Sean
Your age is 14
Your num is 222

向函数传递复杂数据结构

复杂的数据结构可以是列表、字典,此处以列表为例

传参的时候,实参为一个列表,在函数中可以访问这个传入的列表,可以对其元素进行访问、遍历等操作

列表作为条件表达式时,若列表是空,则返回 False,若列表至少包含一个元素,则返回 True

对传入函数的列表进行遍历

>>> def book(books):
...     for book in books:
...             print(book)
... 
>>> books1 = ['A', 'B', 'C']
>>> book(books1)
A
B
C

修改传入函数的列表,对列表的修改是永久性的,与其他语言的传参不同,Python将列表作为实参传入时,并不是一个副本,而是真的对列表本身进行操作

>>> def func_lists(lists):
...     for i in range(3):
...             lists.append(i)
... 
>>> numbers = []
>>> func_lists(numbers)
>>> print(numbers)
[0, 1, 2]

由上面可以看出来,lists是真的被操作修改了的

若不希望函数修改传入的列表,则可以使用切片制作列表的副本,并将这个列表的副本传入函数,在函数内对列表的操作只会影响到副本,而对原列表没有任何影响

使用切片表示法创建列表的副本

方式:列表名[:]

>>> def func_lists(origins, news):
...     while origins:
...             orig = origins.pop()
...             news.append(orig)
... 
>>> orig = [1, 2, 3]
>>> news = []
>>> func_lists(orig[:], news)
>>> print(orig)
[1, 2, 3]
>>> print(news)
[3, 2, 1]

对列表的副本操作了,并未影响到原列表

若不使用切片:

>>> orig = [1, 2, 3]
>>> news = []
>>> func_lists(orig, news)
>>> print(orig)
[]
>>> print(news)
[3, 2, 1]

可以发现函数是对列表本身进行操作,影响到了原列表

注意:

  • 复制列表的方式:列表名[:]
  • 复制字典的方式:字典名.copy()
  • 复制元组的方式:新元组 = 要复制的元组

传递任意数量的实参

主要分为两类:

  • 传入的实参不是键值对,则使用 单星号 创造空元组接收所有没有归宿的实参
  • 传入的实参是键值对,则使用 双星号 创造空字典接收所有没有归宿的键值对(实参键值对的形式与传统字典使用方式不太一样)

在定义函数时,若不知道需要定义多少形参,Python允许函数从调用语句中获取任意数量的实参。

只需要将函数的形参设置为: *形参名

Python会创建一个以 形参名 命名的空元组

不管调用语句提供了多少个实参,这个空元组都会接收这些实参,这些实参会存入这个元组中

若以这种方式来接受任意数量的实参,哪怕实参只有一个,也会以元组的形式存储

>>> def hello(*names):
...     print(names)
... 
>>> hello('Jack')
('Jack',)
>>> hello('Jack', 'David', 'Bob', 'Mary')
('Jack', 'David', 'Bob', 'Mary')

这种接收任意数量实参的方式,也可以和按位置传参指定形参名传参一起使用

Python会先匹配按位置传参或指定形参名传参的实参,再将余下的没有归宿的实参存入元组中

将接受任意数量实参的形参要放在普通形参的后面

>>> def hello(name, *colors):
...     print(name)
...     for i in colors:
...             print(i)
... 
>>> test = ['red', 'blue', 'green']
>>> hello('Hello', test)
Hello
['red', 'blue', 'green']
>>> hello('Hello', test, '123')
Hello
['red', 'blue', 'green']
123

hello() 会将 'Hello' 存入 name 中,而将剩下的实参存入 colors 元组中


若要传入的任意数量的实参是键值对的形式,则需要将这个接收键值对形式实参的形参定义为:**形参名

两个星号会让Python创建一个名为 形参名 的 空字典,将接收到的键值对都存入这个字典中

在函数中,可以对这个字典进行所有字典可以进行的操作

>>> def build_dicts(first, last, **infos):
...     dicts = {}
...     dicts['first_name'] = first
...     dicts['last_name'] = last
...     for k, v in infos.items():
...             dicts[k] = v
...     return dicts
... 
>>> dict1 = build_dicts('Jack', 'Will', age='23', color='red')
>>> print(dict1)
{'first_name': 'Jack', 'last_name': 'Will', 'age': '23', 'color': 'red'}

此处需要注意的是,与单纯的字典定义不同,此处实参的键值对的键是一个变量,而不是字符串,表示形式:变量名=值

虽然是传入的键是一个变量,但是存入这个空字典后,这个键是以字符串表示的,所以在函数内对这个字典的操作与传统的字典操作是一样的

在 定义字典 或 给字典添加键值对 时,键值对的键是字符串表示的

dicts = {
    'name' : 'Jack',
	}
dicts['age'] = 24

若此时键名使用的不是字符串,而是变量,则会报错:

>>> dict1[pro] = 123
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'pro' is not defined
>>> dicts = {
...     pro : '123',
...     }
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
NameError: name 'pro' is not defined

但是当键值对作为实参传参时,键一定要是变量,如果是字符串,会报错

>>> dict2 = build_dicts('Jack', 'Will', 'age' = '23', 'color' = 'red')
  File "<stdin>", line 1
SyntaxError: keyword can't be an expression

返回值

函数返回的值称为返回值,返回值可以是一个值或者一组值,其类型也是千变万化的,甚至可以是复杂的数据结构,例如:列表、字典等。

使用 return 语句 将返回值返回到函数调用处

若调用的函数有返回值,则要用一个变量去接收这个返回值,也可以将这个返回值用作其他函数的实参

>>> def people(name, age):
...     message = "He name is " + name
...     message += "\nHis age is " + age
...     return message
... 
>>> message = people('Sean', '24')
>>> print(message)
His name is Sean
His age is 24

返回值可以是字典或者列表,任何类型都可以

>>> def person(name, age):
...     people = {'name' : name, 'age' : '24'}
...     return people
... 
>>> people = person('Sean', '24')
>>> print(people)
{'name': 'Sean', 'age': '24'}

若返回值是字典类型,函数有可选形参时,例如下面例子中的age形参就是可选形参

>>> def person(first_name, last_name, age=''):
...     message = {'first_name' : first_name, 'last_name' : last_name}
...     if age:
...             message['age'] = age
...     return message
... 
>>> mess = person('Jack', 'Zhang', '25')
>>> mess1 = person('Andy', 'Liu')
>>> print(mess)
{'first_name': 'Jack', 'last_name': 'Zhang', 'age': '25'}
>>> print(mess1)
{'first_name': 'Andy', 'last_name': 'Liu'}

函数与模块

若将函数存储在一个单独文件中,这个单独文件可以称为 模块

在别的程序中,若想使用某个模块中的函数,可以使用 import语句 将这个模块导入到该程序中,这样就可以在程序中使用模块中的函数了

合理的使用模块,可以让编写程序的过程专注在逻辑上,而不是函数的具体实现上,并且能较好地重用函数

将函数存储在模块中,别人若想使用你的代码时,只需要导入你的模块即可,你也可以导入别人的模块,从而使用别人编写的代码

模块

若想让函数是可导入的,则要将其放在 模块 中,模块文件的扩展名是 .py

给模块或函数命名时,最好都使用描述性名称进行命名,并且使用 小写字母 与 下划线

导入的方式

  • 导入整个模块
  • 导入特定的函数
  • 导入模块中所有的函数

导入整个模块

使用 import语句 并指定 模块名,就可以在程序中使用模块中的所有函数

导入模块的方式:import 模块名

若要调用模块中的函数,就要指定模块以及对应的函数,使用 句点(.) 分割它们

调用模块的函数的方式:模块名.函数名(实参)

无论是模块还是函数,应先导入,再调用

Python执行import语句时,会将模块中所有的函数复制到当前程序中,但是这些复制的程序是看不见的,在执行程序的过程中,Python会自己处理好复制的事情

导入模块后,该模块的所有函数都可以任意调用

module1.py:

def hello():
	print("Hello Python")

module_test.py:

import module1

module1.hello()

输出:

Hello Python

导入特定的函数

若只想引入某个特定的函数或者任意数量的函数,方式与引入整个模块不太一样

引入整个模块是:import 模块名

若只想引入某个特定的函数:

from 模块名 import 函数名

若要引入多个函数,则要使用逗号分割

from 模块名 import 函数1, 函数2, ...

以这种方式导入的函数可以直接使用函数名去调用,而不需要指明模块名

module1.py:

def hello():
	print("Hello Python")

module_test.py:

from module1 import hello

hello()

输出:

Hello Python

若用这种方式导入特定函数后,依旧在调用时指明了模块名,则会报错

因为你只导入了特定的函数,其实模块对于Python来说是未知的,它无法获取整个模块的代码,所以就会认定这个模块是未定义的

module_test.py:

from module1 import hello

module1.hello()

报错:

Traceback (most recent call last):
  File "module_test.py", line 3, in <module>
    module1.hello()
NameError: name 'module1' is not defined

若导入了整个模块,则可以在调用函数时显式地指明模块名,若只导入了某个特定的函数,则无需显式地指明模块,直接使用函数名去调用即可

导入模块中的所有函数

与直接导入模块的效果一样,但是调用时有区别

如果直接导入模块,在调用模块中的函数时,需要指明模块名与函数名并且用 句点(.) 分隔

如果导入模块中的所有函数,则无需指明模块名,直接使用函数名去调用函数即可。若调用时指明模块名会报错

使用 星号 可以导入模块中所有的函数

方式:**from 模块名 import ***

from module1 import *
hello()

若依旧指明了该模块名:

from module1 import *
module1.hello()

报错:

Traceback (most recent call last):
  File "module_test.py", line 2, in <module>
    module1.hello()
NameError: name 'module1' is not defined

给函数、模块指定别名

在导入函数或者模块时,有时候导入的函数或模块的名字会与当前程序中的函数的名字或者变量的名字上出现冲突,甚至因为函数或模块的名字过长,若在程序中调用时,显得代码不够简洁。

Python在导入模块时,遇到名称相同的函数或变量时,会覆盖当前程序同名的函数与变量,而不是分别导入

因此可以给函数或模块起别名,使用 **关键字as **对函数或者模块进行重命名,需要注意的是应在导入函数或者模块时就要指定别名

给导入的函数起别名方式:

from 模块名 import 函数名 as 别名

from module1 import hello as a

a()

给导入的模块起别名方式:

import 模块名 as 别名

import module1 as mod

mod.hello()

一些函数相关的约定

  1. 给定义函数时,若要给形参指定默认值,则等号两边不要有空格
def function_name(p1, p2='123')
  1. 在调用函数时,若要使用 指定形参名传参,则实参等号两边也不要有空格
function_name(value, p2='Hello')
  1. 若形参或实参数量较多、名字较长,超出了建议代码行的长度(79个字符)

    可在函数定义或函数调用时,输入左括号后按回车键,并在下一行按两次 Tab 键,从而将形参列表和只缩进一层的函数体区分开来,也会让实参列表显得清晰易读

def function_name(
		p1, p2, p3,
		p4, p5, p6)
	print(p1)
	print(p2)
	print(p3)
    
function_name(
		value1, value2, value3,
		value4, value5, value6)
  1. 若源文件或模块中有大量的函数,则函数之间最好空两行,这样可以便于区分函数

  2. import语句应放在整个源代码的最开始的位置

    若在源文件的开头使用了注释来描述整个程序的话,则应将import语句 写在注释之后

posted @ 2021-02-19 20:14  SeanSiyang  阅读(95)  评论(0编辑  收藏  举报