Day006:函数和模块的使用
参考:pyton3函数
在讲解本章节的内容之前,我们先来研究一道数学题,请说出下面的方程有多少组正整数解。
$$x_1 + x_2 + x_3 + x_4 = 8$$
事实上,上面的问题等同于将8个苹果分成四组每组至少一个苹果有多少种方案。想到这一点问题的答案就呼之欲出了。
$$C_M^N =\frac{M!}{N!(M-N)!}, \text{(M=7, N=3)} $$
可以用Python的程序来计算出这个值,代码如下所示。
"""
输入M和N计算C(M,N)
"""
m = int(input('m = '))
n = int(input('n = '))
i = 1
j = 1
k = 1
for num in range(1, m+1):
i *= num
for num in range(1, n+1):
j *= num
for num in range(1, n-m+1):
k *= num
print('i= %d\nj= %d\nk= %d\nresult=%d\n' %(i, j , k, j // (k * i)))
m = 5
n = 8
i= 120
j= 40320
k= 6
result=56
1.函数的作用
不知道大家是否注意到,在上面的代码中,我们做了3次求阶乘,这样的代码实际上就是重复代码。编程大师Martin Fowler先生曾经说过:“代码有很多种坏味道,重复是最坏的一种!”,要写出高质量的代码首先要解决的就是重复代码的问题。对于上面的代码来说,我们可以将计算阶乘的功能封装到一个称之为“函数”的功能模块中,在需要计算阶乘的地方,我们只需要“调用”这个“函数”就可以了。
2.定义函数
在Python中可以使用def
关键字来定义函数,和变量一样每个函数也有一个响亮的名字,而且命名规则跟变量的命名规则是一致的。在函数名后面的圆括号中可以放置传递给函数的参数,这一点和数学上的函数非常相似,程序中函数的参数就相当于是数学上说的函数的自变量,而函数执行完成后我们可以通过return
关键字来返回一个值,这相当于数学上说的函数的因变量。
你可以定义一个由自己想要功能的函数,以下是简单的规则:
- 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()。
- 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
- 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
- 函数内容以冒号起始,并且缩进。
- return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。
2.1 语法
Python 定义函数使用 def 关键字,一般格式如下:
def 函数名(参数列表):
函数体
默认情况下,参数值和参数名称是按函数声明中定义的顺序匹配起来的。
2.2 实例
定义函数输出"hello world!":
def hello():
print('hello, world!')
hello()
hello, world!
更复杂点的应用,函数中带上参数变量:
# 计算面积函数:
def area(width, height):
width = int(input('输入长方形的宽: '))
height = int(input('输入长方形的高: '))
return width * height
def print_welcome(name):
print("welcome", name)
print_welcome("yangjie")
w = 4
h = 5
print(area(w, h))
welcome yangjie
输入长方形的宽: 5
输入长方形的高: 6
30
在了解如何定义函数后,我们可以对最开始的代码进行重构,所谓重构就是在不影响代码执行结果的前提下对代码的结构进行调整,重构之后的代码如下所示。
def factorial(num):
"""
求阶乘
:param num: 非负整数
:return: num的阶乘
"""
result = 1
for n in range(1, num+1):
result *= n
return result
m = int(input('m = '))
n = int(input('n = '))
# 当需要计算阶乘的时候不用再写循环求阶乘而是直接调用已经定义好的函数
print(factorial(n),end='\n')
print(factorial(m),end='\n')
print(factorial(n-m),end='\n')
print(factorial(n) / factorial(m) / factorial(n-m))
m = 5
n = 8
40320
120
6
56.0
def factorial(num):
"""
求阶乘
:param num: 非负整数
:return: 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))
m = 8
n = 5
56.0
说明:Python的math模块中其实已经有一个factorial函数了,事实上要计算阶乘可以直接使用这个现成的函数而不用自己定义。下面例子中的某些函数其实Python中也是内置了,我们这里是为了讲解函数的定义和使用才把它们又实现了一遍,实际开发中不建议做这种低级的重复性的工作。
2.3 函数的调用
定义一个函数:给了函数一个名称,指定了函数里包含的参数,和代码块结构。
这个函数的基本结构完成以后,你可以通过另一个函数调用执行,也可以直接从 Python 命令提示符执行。
如下实例调用了 printme() 函数:
# 定义函数
def printme( str ):
# 打印任何传入的字符串
print (str)
return
# 调用函数
printme("我要调用用户自定义函数!")
printme("再次调用同一函数")
我要调用用户自定义函数!
再次调用同一函数
4.函数的参数
以下是调用函数时可使用的正式参数类型:
- 必需参数
- 关键字参数
- 默认参数
- 不定长参数
4.1 必须参数
必需参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样。
调用printme()函数,你必须传入一个参数,不然会出现语法错误:
#可写函数说明
def printme( str ):
"打印任何传入的字符串"
print (str)
return
#调用printme函数
printme("right")
printme()
right
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-13-e78176cd61cb> in <module>()
8 printme("right")
9
---> 10 printme()
TypeError: printme() missing 1 required positional argument: 'str'
4.2 关键字参数
关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。
使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
以下实例在函数 printme() 调用时使用参数名:
# 可写函数声明
def printme( str ):
# 打印任何传入的参数
print( str )
return
# 调用printme函数
printme( str = "关键字参数")
关键字参数
以下实例中演示了函数参数的使用不需要使用指定的参数顺序:
# 可写函数说明
def printinfo(age, name):
print("年龄: ",age)
print("姓名: ",name)
return
#调用printinfo函数
printinfo( name="yangjie", age=50)
年龄: 50
姓名: yangjie
4.3 默认参数
调用函数时,如果没有传递参数,则会使用默认参数。以下实例中如果没有传入 age 参数,则使用默认值,如以下例子:
4.3.1 打印姓名年#可写函数说明
#可写函数说明
def printinfo( name, age = 35 ):
"打印任何传入的字符串"
print ("名字: ", name)
print ("年龄: ", age)
return
#调用printinfo函数
printinfo( age=50, name="runoob" ) # 不按顺序传递关键字参数
print ("------------------------")
printinfo( name="runoob" ) # 省略默认参数age
print ("------------------------")
printinfo() #缺少关键字参数name
名字: runoob
年龄: 50
------------------------
名字: runoob
年龄: 35
------------------------
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-5-8216a804245b> in <module>()
11 printinfo( name="runoob" )
12 print ("------------------------")
---> 13 printinfo()
TypeError: printinfo() missing 1 required positional argument: 'name'
函数是绝大多数编程语言中都支持的一个代码的“构建块”,但是Python中的函数与其他语言中的函数还是有很多不太相同的地方,其中一个显著的区别就是Python对函数参数的处理。在Python中,函数的参数可以有默认值,也支持使用可变参数,所以Python并不需要像其他语言一样支持函数的重载,因为我们在定义一个函数的时候可以让它有多种不同的使用方式,下面是两个小例子。
from random import randint
def roll_dice(n=2):
"""
摇色子
:param n: 色子的个数
:return: n颗色子点数之和
"""
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())
6
print(roll_dice(3))
7
print(add())
0
print(add(1))
1
print(add(1,2))
3
print(add(1, 2, 3))
6
# 传递参数时可以不按照设定的顺序进行传递
print(add(c=50, a=100, b=200))
350
我们给上面两个函数的参数都设定了默认值,这也就意味着如果在调用函数的时候如果没有传入对应参数的值时将使用该参数的默认值,所以在上面的代码中我们可以用各种不同的方式去调用add
函数,这跟其他很多语言中函数重载的效果是一致的。
其实上面的add
函数还有更好的实现方案,因为我们可能会对0个或多个参数进行加法运算,而具体有多少个参数是由调用者来决定,我们作为函数的设计者对这一点是一无所知的,因此在不确定参数个数的时候,我们可以使用可变参数,代码如下所示。
# 在参数名前面的*表示args是一个可变参数
# 即在调用add函数时可以传入0个或多个参数
def add(*args):
total = 0
for val in args:
total += val
return total
print(add())
print(add(1))
print(add(1, 2))
print(add(1, 2, 3))
print(add(1, 3, 5, 7, 9))
0
1
3
6
25
4.4 不定长参数
你可能需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数,和上述 2 种参数不同,声明时不会命名。基本语法如下:
def functionname([formal_args,] *var_args_tuple ):
"函数_文档字符串"
function_suite
return [expression]
加了星号 * 的参数会以元组(tuple)的形式导入,存放所有未命名的变量参数。
# 可写函数说明
def printinfo(arg1, *vartuple):
"打印任何传入的参数"
print("输出: ")
print(arg1)
print(vartuple)
#调用printinfo函数
printinfo(70, 60, 50)
输出:
70
(60, 50)
如果在函数调用时没有指定可变参数,它就是一个空元组。我们也可以不向函数传递未命名的变量。如下实例:
#可写函数说明
def printinfo(arg1, *vartuple ):
"打印任何传入的参数"
print("输出: ")
print(arg1)
for var in vartuple:
print(var)
return
# 调用printinfo函数
printinfo(10)
printinfo(70, 60, 50)
输出:
10
输出:
70
60
50
还有一种就是参数带两个星号 基本语法如下:**
def functionname([formal_args,] **var_args_dict ):
"函数_文档字符串"
function_suite
return [expression]
加了两个星号 ** 的参数会以字典的形式导入。
#可写函数说明
def printinfo (arg1, **vardict):
"打印任何传入的参数"
print("输出: ")
print(arg1 )
print(vardict)
return
# 调用printinfo函数
printinfo(1, a = 2,b = 3)
输出:
1
{'b': 3, 'a': 2}
声明函数时,参数中星号 * 可以单独出现,例如:
def f(a,b,*,c):
return a + b = c
如果单独出现*后的参数必须用关键字传入。
def f(a, b, *, c):
print(a+b+c)
return a + b + c
f(1,2,c=3)
f(1,2,3)
6
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-8-c1344a49904d> in <module>()
6 f(1,2,c=3)
7
----> 8 f(1,2,3)
TypeError: f() takes 2 positional arguments but 3 were given
5.参数传递
在python中,类型属于对象,变量是没有类型的
a = [1, 2, 3]
a = "yangjie"
以上代码中,[1,2,3] 是 List 类型,"yangjie" 是 String 类型,而变量 a 是没有类型,她仅仅是一个对象的引用(一个指针),可以是指向 List 类型对象,也可以是指向 String 类型对象。
5.1 可更改(muable)和不可更改(inmuable)对象
在python中,strings,tuples和numbers是不可更改的对象,而list,dict等则是可更改的对象。
- 不可变类型:变量赋值a = 5后再赋值a = 10,这里实际是新生成一个int值对象10,再让a指向它,而5被丢弃,不是改变a的值,相当于新生成了a。
- 可变类型:变量赋值la = [1,2,3,4]后在赋值la[2] = 5则是将list la的第三个元素值再更改,本身la没有动,只是其内部的一部分值被修改了。
python函数的参数传递:
- 不可变类型:类似c++的值传递,如整数、字符串、元组。如fun(a),传递的只是a的值,没有影响a对象本身,比如在fun(a)内部修改a的值,只是修改另一个复制的对象,不会影响a本身。
- 可变类型:类似c++的引用传递,如列表,字典。如fun(la),则是将la真正的传过去,修改后fun外部的la也会受影响。
python中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和可变对象。
### 5.2 传不可变对象实例
def changeint(a):
a = 10
return a
b = 2
changeint(b)
print(b)
2
实例中有 int 对象 2,指向它的变量是 b,在传递给 ChangeInt 函数时,按传值的方式复制了变量 b,a 和 b 都指向了同一个 Int 对象,在 a=10 时,则新生成一个 int 值对象 10,并让 a 指向它。
5.3 传可变对象实例
可变对象在函数里修改了参数,那么在调用这个函数的函数里,原始的参数也被改变了。例如:
# 可写函数说明
def changeme(mylist):
"修改传入的列表"
mylist.append([1,2,3,4])
print("函数内取值: ", mylist)
return
# 调用changeme函数
mylist = [10,20,30]
changeme(mylist)
print("函数内取值:", mylist)
函数内取值: [10, 20, 30, [1, 2, 3, 4]]
函数内取值: [10, 20, 30, [1, 2, 3, 4]]
6.匿名函数
python 使用 lambda 来创建匿名函数。
所谓匿名,意即不再使用 def 语句这样标准的形式定义一个函数。
- lambda 只是一个表达式,函数体比 def 简单很多。
- lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
- lambda 函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
- 虽然lambda函数看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。
6.1 语法
lambda函数的语法只包含一个语句,如下:
lamba [arg1 [,arg2,……argn]]:expression
如下实例:
# 可写函数说明
sum = lambda arg1, arg2: arg1 + arg2
# 调用sum函数
print("相加后的值为:", sum(10, 20))
print("相加后的值为:", sum(20, 20))
相加后的值为: 30
相加后的值为: 40
7.return语句
return[表达式] 语句用于退出函数,选择性地向调用方返回一个表达式。不带参数值的return语句返回None。之前的例子都没有示范如何返回数值,以下实例演示了 return 语句的用法:
# 可写函数说明
def sum( arg1, arg2 ):
# 返回2个参数的和。
total = arg1 + arg2
print("函数内: ", total)
return total
#调用sum函数
total = sum(10, 20)
print("函数外: ", total)
函数内: 30
函数外: 30
8.变量作用域
Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。
变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。Python的作用域一共有4种,分别是:
- L (Local) 局部作用域
- E (Enclosing) 闭包函数外的函数中
- G (Global) 全局作用域
- B (Built-in) 内置作用域(内置函数所在模块的范围)
以 L –> E –> G –>B 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内置中找。
g_count = 0 # 全局作用域
def outer():
o_count = 1 # 闭包函数外的函数中
def inner():
i_count = 2 # 局部作用域
内置作用域是通过一个名为 builtin 的标准模块来实现的,但是这个变量名自身并没有放入内置作用域内,所以必须导入这个文件才能够使用它。在Python3.0中,可以使用以下的代码来查看到底预定义了哪些变量:
import builtins
dir(builtins)
Python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这些语句内定义的变量,外部也可以访问,如下代码:
if True:
... msg = 'I am from Runoob'
...
msg
'I am from Runoob'
实例中 msg 变量定义在 if 语句块中,但外部还是可以访问的。
如果将 msg 定义在函数中,则它就是局部变量,外部不能访问:
def test():
... msg_inner = 'I am from Runoob'
...
msg_inner
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'msg_inner' is not defined
从报错的信息上看,说明了 msg_inner 未定义,无法使用,因为它是局部变量,只有在函数内可以使用。
8.1 全局变量和局部变量
定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。
局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。如下实例:
total = 0 # 这是一个全局变量
# 可写函数说明
def sum(arg1, arg2):
# 返回两个参数的和
total = arg1 + arg2
print("函数内部是局部变量:", total, end="\t\n")
return total
# 调用sum函数
sum(10, 20)
print("函数外是全局变量:", total, end="\t")
函数内部是局部变量: 30
函数外是全局变量: 0
8.2 global 和 nonlocal关键字
当内部作用域想修改外部作用域的变量时,就要用到global和nonlocal关键字了。
以下实例修改全局变量 num:
num = 1
def fun1():
global num # 需要使用global关键字申明
print(num)
num = 123
print(num)
return
fun1()
print(num)
1
123
123
如果要修改嵌套作用域(enclosing作用域,外层非全局作用域)中的变量则需要nolocal关键字了,如下实例:
def outer():
num = 10
def inner():
nonlocal num # nonlocal关键字申明
num = 100
print(num)
inner()
print(num)
return
outer()
100
100
另外有一种特殊情况,假设下面这段代码被运行:
a = 10
def test():
a = a + 1
print(a)
return
test()
---------------------------------------------------------------------------
UnboundLocalError Traceback (most recent call last)
<ipython-input-5-38efa8ff1b2f> in <module>()
5 return
6
----> 7 test()
<ipython-input-5-38efa8ff1b2f> in test()
1 a = 10
2 def test():
----> 3 a = a + 1
4 print(a)
5 return
UnboundLocalError: local variable 'a' referenced before assignment
错误信息为局部作用域引用错误,因为 test 函数中的 a 使用的是局部,未定义,无法修改。
修改 a 为全局变量,通过函数参数传递,可以正常执行输如下所示:
a = 10
def test(a):
a = a + 1
print(a)
return
test(a)
11
9. 用模块管理函数
对于任何一种编程语言来说,给变量、函数这样的标识符起名字都是一个让人头疼的问题,因为我们会遇到命名冲突这种尴尬的情况。最简单的场景就是在同一个.py文件中定义了两个同名函数,由于Python没有函数重载的概念,那么后面的定义会覆盖之前的定义,也就意味着两个函数同名函数实际上只有一个是存在的。
def foo():
print('hello, world!')
def foo():
print('goodbye, world!')
# 下面的代码会输出什么呢?暑促最后一个定义的foo函数返回值
foo()
goodbye, world!
当然上面的这种情况我们很容易就能避免,但是如果项目是由多人协作进行团队开发的时候,团队中可能有多个程序员都定义了名为foo的函数,那么怎么解决这种命名冲突呢?答案其实很简单,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 m1
import module2 as m2
m1.foo()
m2.foo()
但是如果将代码写成了下面的样子,那么程序中调用的是最后导入的那个foo,因为后导入的foo覆盖了之前导入的foo。
test.py
from module1 import foo
from module2 import foo
# 输出goodbye, world!
foo()
需要说明的是,如果我们导入的模块除了定义函数之外还中有可以执行代码,那么Python解释器在导入这个模块时就会执行这些代码,事实上我们可能并不希望如此,因此如果我们在模块中编写了执行代码,最好是将这些执行代码放入如下所示的条件中,这样的话除非直接运行该模块,if条件下的这些代码是不会执行的,因为只有直接执行的模块的名字才是“main”。
module3.py
def foo():
pass
def bar():
pass
# __name__是python中一个隐含的变量它代表了模块的名字
# 只有被python解释器直接执行的模块的名字才是__main__
if __name__ == '__main__':
print('call foo()')
foo()
print('call bar()')
bar()
call foo()
call bar()
10.练习
10.1 实现计算最大公约数和最小公倍数的函数
def gcd(x, y):
if x > y:
(x, y) = (y, x)
for n in range(x, 0, -1):
if x % n == 0 and y % n == 0:
return n
else:
return 0
def lcm(x, y):
return x * y / gcd(x, y)
x = int(input("x = "))
y = int(input("y = "))
if gcd(x, y):
print("%d和%d的最大公约数是%d" % (x, y, gcd(x, y)))
print("%d和%d的最小公倍数是%d" % (x, y, lcm(x, y)))
else:
print("%d和%d没有最大公约数")
x = 12
y = 48
12和48的最大公约数是12
12和48的最小公倍数是48
10.2 实现判断一个数是不是回文数的函数
def is_palindrome(num):
temp = num
total = 0
while temp > 0:
total = total * 10 + temp % 10
temp //= 10
return total == num
x = int(input('x= '))
if is_palindrome(x):
print ("%d是回文数" %x)
x= 123321
123321是回文数
10.3 判断一个数是不是素数的函数
def is_prime(num):
if num == 1:
return False
else:
for n in range(2, num):
if num % n == 0:
return False
else:
return True
x = int(input('请输入一个正整数:'))
if not is_prime(x):
print("%d不是一个素数" %x)
else:
print("%d是一个素数" %x)
请输入一个正整数:23
23是一个素数
10.4 写一个程序判断输入的正整数是不是回文素数
if __name__ == '__main__':
num = int(input('请输入正整数:'))
if is_palindrome(num):
if is_prime(num):
print("%d是一个回文素数" % num)
else:
print("%d是一个回文数非素数" % num)
else:
if is_prime(num):
print("%d是一个素数非回文数" % num)
else:
print("%d是既不是回文数也不是素数" % num)
请输入正整数:264462
264462是一个回文数非素数
2019-5-21 10:07:04