定义函数
在Python中可以使用def关键字来定义函数,和变量一样每个函数也有一个响亮的名字,而且命名规则跟变量的命名规则是一致的。在函数名后面的圆括号中可以放置传递给函数的参数,这一点和数学上的函数非常相似,程序中函数的参数就相当于是数学上说的函数的自变量,而函数执行完成后我们可以通过return关键字来返回一个值,这相当于数学上说的函数的因变量。
例如:
def factorial(num): """求阶乘""" result = 1 for n in range(1, num + 1):result *= n return result m = int(input('m = ')) n = int(input('n = ')) print(factorial(m) // factorial(n) // factorial(m - n))
函数的参数
函数是绝大多数编程语言中都支持的一个代码的"构建块",但是Python中的函数与其他语言中的函数还是有很多不太相同的地方,其中一个显著的区别就是Python对函数参数的处理。在Python中,函数的参数可以有默认值,也支持使用可变参数,所以Python并不需要像其他语言一样支持函数的重载,因为我们在定义一个函数的时候可以让它有多种不同的使用方式。
from random import randint def roll_dice(n=2): """摇色子""" total = 0 for _ in range(n): total += randint(1, 6) return total def add(a=0, b=0, c=0): """三个数相加""" return a + b + c # 如果没有指定参数那么使用默认值摇两颗色子 print(roll_dice()) # 摇三颗色子 print(roll_dice(3)) print(add()) print(add(1)) print(add(1, 2)) print(add(1, 2, 3)) # 传递参数时可以不按照设定的顺序进行传递 print(add(c=50, a=100, b=200))
其实上面的add函数还有更好的实现方案,因为我们可能会对0个或多个参数进行加法运算,而具体有多少个参数是由调用者来决定,我们作为函数的设计者对这一点是一无所知的,因此在不确定参数个数的时候,我们可以使用可变参数,代码如下所示。
# 在参数名前面的*表示args是一个可变参数 def add(*args): total = 0 for val in args: total += val return total # 在调用add函数时可以传入0个或多个参数 print(add())print(add(1)) print(add(1, 2)) print(add(1, 2, 3)) print(add(1, 3, 5, 7, 9))
用模块管理函数
对于任何一种编程语言来说,给变量、函数这样的标识符起名字都是一个让人头疼的问题,因为我们会遇到命名冲突这种尴尬的情况。最简单的场景就是在同一个.py文件中定义了两个同名函数,由于Python没有函数重载的概念,那么后面的定义会覆盖之前的定义,也就意味着两个函数同名函数实际上只有一个是存在的。
Python中每个文件就代表了一个模块(module),我们在不同的模块中可以有同名的函数,在使用函数的时候我们通过import关键字导入指定的模块就可以区分到底要使用的是哪个模块中的foo函数,代码如下所示。
module1.py
def foo():print('hello, world!')
module2.py
def foo():print('goodbye, world!')
test.py
from module1 import foo# 输出hello, world!foo()from module2 import foo# 输出goodbye, world!foo()
也可以按照如下所示的方式来区分到底要使用哪一个foo函数。
test.py
import module1 as m1import module2 as m2m1.foo()m2.foo()
需要说明的是,如果我们导入的模块除了定义函数之外还中有可以执行代码,那么Python解释器在导入这个模块时就会执行这些代码,事实上我们可能并不希望如此,因此如果我们在模块中编写了执行代码,最好是将这些执行代码放入如下所示的条件中,这样的话除非直接运行该模块,if条件下的这些代码是不会执行的,因为只有直接执行的模块的名字才是"__main__"。
module3.py
def foo(): passdef bar(): pass# __name__是Python中一个隐含的变量它代表了模块的名字# 只有被Python解释器直接执行的模块的名字才是__main__if __name__ == '__main__': print('call foo()') foo() print('call bar()') bar()
test.py
test.pyimport module3# 导入module3时 不会执行模块中if条件成立时的代码 因为模块的名字是module3而不是__main__
变量作用域
Python中有关变量作用域的问题
def foo(): b = 'hello' # Python中可以在函数内部再定义函数 def bar(): c = True print(a) print(b) print(c) bar() # print(c) # NameError: name 'c' is not definedif __name__ == '__main__': a = 100 # print(b) # NameError: name 'b' is not defined foo()
上面的代码能够顺利的执行并且打印出100、hello和True,但我们注意到了,在bar函数的内部并没有定义a和b两个变量,那么a和b是从哪里来的。我们在上面代码的if分支中定义了一个变量a,这是一个全局变量(global variable),属于全局作用域,因为它没有定义在任何一个函数中。在上面的foo函数中我们定义了变量b,这是一个定义在函数中的局部变量(local variable),属于局部作用域,在foo函数的外部并不能访问到它;但对于foo函数内部的bar函数来说,变量b属于嵌套作用域,在bar函数中我们是可以访问到它的。bar函数中的变量c属于局部作用域,在bar函数之外是无法访问的。事实上,Python查找一个变量时会按照“局部作用域”、“嵌套作用域”、“全局作用域”和“内置作用域”的顺序进行搜索,前三者我们在上面的代码中已经看到了,所谓的“内置作用域”就是Python内置的那些标识符,我们之前用过的input、print、int等都属于内置作用域。
看看下面这段代码,我们希望通过函数调用修改全局变量a的值,但实际上下面的代码是做不到的。
def foo():a = 200print(a) # 200if __name__ == '__main__': a = 100foo()print(a) # 100
在调用foo函数后,我们发现a的值仍然是100,这是因为当我们在函数foo中写a = 200的时候,是重新定义了一个名字为a的局部变量,它跟全局作用域的a并不是同一个变量,因为局部作用域中有了自己的变量a,因此foo函数不再搜索全局作用域中的a。如果我们希望在foo函数中修改全局作用域中的a,代码如下所示。
def foo():global aa = 200print(a) # 200if __name__ == '__main__':a = 100foo()print(a) # 200
我们可以使用global关键字来指示foo函数中的变量a来自于全局作用域,如果全局作用域中没有a,那么下面一行的代码就会定义变量a并将其置于全局作用域。同理,如果我们希望函数内部的函数能够修改嵌套作用域中的变量,可以使用nonlocal关键字来指示变量来自于嵌套作用域。
在实际开发中,我们应该尽量减少对全局变量的使用,因为全局变量的作用域和影响过于广泛,可能会发生意料之外的修改和使用,除此之外全局变量比局部变量拥有更长的生命周期,可能导致对象占用的内存长时间无法被回收。事实上,减少对全局变量的使用,也是降低代码之间耦合度的一个重要举措。减少全局变量的使用就意味着我们应该尽量让变量的作用域在函数的内部,但是如果我们希望将一个局部变量的生命周期延长,使其在定义它的函数调用结束后依然可以使用它的值,这时候就需要使用闭包。
总结
函数定义的代码可按照以下固定格式写在独立模块,在进行调用。注:pass语句不执行,在代码中表示留作脚注,可能日后会加代码,先占位
def main():# Todo: Add your code here passif __name__ == '__main__':main()
参考链接:https://github.com/jackfrued/Python-100-Days/blob/master/Day01-15/06.函数和模块的使用.md