实际工作生活中,我曾见到不少初学者编写的 Python 程序,他们长达几百行的代码中,却没有一个函数,通通按顺序堆到一块儿,不仅让人读起来费时费力,往往也是错误连连。
如果你对本文的知识点有理解的地方,可以点击阅读原文,在文章下面留言,我会回你解答!
一个规范的值得借鉴的 Python 程序,除非代码量很少(比如 10 行、20 行以下),基本都应该由多个函数组成,这样的代码才更加模块化、规范化。
函数是 Python 程序中不可或缺的一部分。事实上,在前面的学习中,我们已经用到了很多 Python 的内置函数,比如 sorted() 表示对一个集合序列排序,len() 表示返回一个集合序列的长度大小等等。这节课,我们主要来学习 Python 的自定义函数。
1. 函数基础
那么,到底什么是函数,如何在 Python 程序中定义函数呢?
说白了,函数就是为了实现某一功能的代码段,只要写好以后,就可以重复利用。我们先来看下面一个简单的例子:
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): statements return/yield value # optional
和其他需要编译的语言(比如 C 语言)不一样的是,def 是可执行语句,这意味着函数直到被调用前,都是不存在的。当程序调用函数时,def 语句才会创建一个新的函数对象,并赋予其名字。
我们一起来看几个例子,加深你对函数的印象:
def my_sum(a, b): return a + b result = my_sum(3, 5) print(result) # 输出 8
这里,我们定义了 my_sum() 这个函数,它有两个参数 a 和 b,作用是相加;随后,调用 my_sum() 函数,分别把 3 和 5 赋于 a 和 b;最后,返回其相加的值,赋于变量 result,并输出得到 8。
再来看一个例子:
def find_largest_element(l): if not isinstance(l, list): print('input is not type of list') return if len(l) == 0: print('empty input') return largest_element = l[0] for item in l: if item > largest_element: largest_element = item print('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
另外,Python 函数的参数可以设定默认值,比如下面这样的写法:
def func(param = 0): ...
这样,在调用函数 func()时,如果参数 param没有传入,则参数默认为 0;而如果传入了参数 param,其就会覆盖默认值。
前面说过,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()),可以同时应用在整型、列表、字符串等等的操作中。
在编程语言中,我们把这种行为称为多态。这也是 Python 和其他语言,比如 Java、C 等很大的一个不同点。当然,Python 这种方便的特性,在实际使用中也会带来诸多问题。因此,必要时请你在开头加上数据的类型检查。
Python 函数的另一大特性,是 Python 支持函数的嵌套。所谓的函数嵌套,就是指函数里面又有函数,比如:
def f1(): print('hello') def f2(): print('world') f2() f1() # 输出 hello world
其实,函数的嵌套,主要有下面两个方面的作用。
第一,函数的嵌套能够保证内部函数的隐私。内部函数只能被外部函数所调用和访问,不会暴露在全局作用域,因此,如果你的函数内部有一些隐私数据(比如数据库的用户、密码等),不想暴露在外,那你就可以使用函数的的嵌套,将其封装在内部函数中,只通过外部函数来访问。比如:
def connect_DB(): def get_DB_configuration(): ... return host, username, password conn = 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 check if 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 1 return input * inner_factorial(input-1) return inner_factorial(input) print(factorial(5))
这里,我们使用递归的方式计算一个数的阶乘。因为在计算之前,需要检查输入是否合法,所以我写成了函数嵌套的形式,这样一来,输入是否合法就只用检查一次。而如果我们不使用函数嵌套,那么每调用一次递归便会检查一次,这是没有必要的,也会降低程序的运行效率。
实际工作中,如果你遇到相似的情况,输入检查不是很快,还会耗费一定的资源,那么运用函数的嵌套就十分必要了。