函数模块的使用
我们是站在巨人的肩膀上的,很多事情前任已经做过,我们就可以理解吸收
拿来直接用就行。或者我们自己编程时,有时候会用到某一个功能很多次,
这时候我们就可以将它封装在一个模块中,随时取用,减少代码的重复。
代码有很多种坏味道,重复是最坏的一种
引例
x1 + x2 + x3 + x4 = 8这道方程有多少组正整数解?
可以将它想象为8个苹果放进四个箱子中,每个箱子中至少有一个的放法
那么可以用排列组合公式来展示
那么程序代码可以写成下面这样:
m = int(input("m = "))
n = int(input("n = "))
fm = 1
for num in range(1, m+1):
fm *= num
fn = 1
for num in range(1, n+1):
fn *= num
fmn = 1
for num in range(1, m-n+1):
fmn *= num
print(fm // fn // fmn)
在上面的程序中,我们看到,对m,n,m-n做的事情是求阶乘,他们三个的功能
是一样的,但是写了三次代码,很明显代码重复率太高了,像这样实现一个功能的
代码可以单独拿出来写在一个模块中,用到的时候调用就可以了。
定义函数
Python中使用def关键字来定义函数,函数的名字和变量的命名一样,函数后面的括号可以放置参数也可以没有,函数最后可以用return返回函数值,也可以不返回。
了解基本规则后,我们对上面的引例进行模块化
def factorial(num):
'''
求阶乘
'''
result = 1
for i in range(1,num+1):
result *= i
m = int(input("m = "))
n = int(input("n = "))
print(factorial(m) // factorial(n) // factorial(m-n))
这样代码量就减少了,而且其他地方如果用到求阶乘的模块也一样可以引用,而求
阶乘的代码实际上不用咱们自己写,python中内置了factorial函数,直接拿来用。
函数的参数
函数是大多数编程语言中支持的一个“构建快”,Python中的函数的参数可以有默认值,也支持可变参数,所以说Python的函数不需要重载。例如
from random import randint
def roll_dice(n=2):
'''
摇骰子
'''
total = 0
for i in range(n):
total += randint(1,6)
return total
如果不传递参数,那么n的默认值就是2,摇两颗骰子。
可变参数中用*args表示一个可变参数,**kwargs表示后面的集合
def add(*args):
total = 0
for i in args:
total += i
return total
print(add())
print(add(1))
print(add(1,2))
....
用模块管理函数
给函数起名字时,如果自己一个人开发,可以比较容易避免重名,而实际上多个人一起工作开发的时候,就难免会出现这种状况,最简单的解决办法就是每个人将不同的函数封装进一个模块中,然后调用,避免重名的情况,例如
model1.py
def foo():
print("hello world")
model2.py
def foo():
print("goodbye")
test.py
from moudel1 import foo
foo() # 输出hello world
from moudel2 import foo
foo() # 输出goodbye
但切记不可以这样
from moudel1 import foo
from moudel2 import foo
foo() # 输出goodbye
这样moudel2会覆盖moudel2,输出goodbye,python中就是同名的引用会覆盖前面的那一个
实际工作中,我们写一个模块,当导入它的时候,我们可能不希望它执行,而是希望我们调用它再执行,而且模块中可能会封装几个函数,我们希望调用哪个哪个再执行,这就需要在模块中写上name这个条件,例如
moudel3.py
def foo():
pass
def bar():
pass
if __name__ == "__main__":
print("call foo")
foo()
print("call bar")
bar()
test.py
import moudel3
这里导入moudel3时,程序不会执行if条件中内容,因为模块的名字时moudel3,而不是__main__
练习
求最小公倍数和最大公约数的函数
def gcd(x,y): # 最大公约数
(x,y) = (y,x) if x > y else (x,y) # 注意加括号
for factor in range(x,0,-1):
if x % factor == 0 and y % factor == 0:
return factor
def lcm(x,y): # 最小公倍数
return x*y//gcd(x,y)
判断一个数是不是回文数
def is_palindrome(num):
temp = num
total = 0
while temp>0:
total = total * 10 + temp % 10
temp //= 10
return total == num
判断一个数是不是素数
def is_prime(num):
for factor in range(2,num):
if num % factor == 0:
return False
return True if num != 1 else False
判断输入的正整数是不是回文素数
if __name__ == "__main__":
num = int(input("请输入一个正整数:"))
if is_prime(num) and is_palindrome(num):
print("%d是回文素数" % num)
变量作用域
Python查找一个变量会按照 局部作用域–>嵌套作用域–>全局作用域–>内置作用域(隐含标识符)进行查找
def foo():
b = 'hello'
def bar():
c = True
print(a) # 正常输出100,if分支中全局变量有a
print(b) # 正常输出hello,嵌套作用域有b
print(c) # 正常输出True,局部作用域有c
bar()
print(c)
#NameError,c属于bar的局部作用域中,而不属于外部foo函数
if __name__ == "__main__":
a = 100
print(b) # NameError,b属于foo的作用域,无法在外部输出
foo()
如果想要在函数内部修改全局变量,必须加上global关键字
def foo():
a = 200 # 这个是局部,不能改变外部的值,内存地址不一样
print(a) # 输出200
if __name__ == "__main__":
a = 100
foo() # 无法修改a的值为200,这个作用域中的a是全局中
print(a) # 输出100
def foo():
global a
a = 200 # 这个不是局部,改变外部的值,内存地址一样
print(a) # 输出200
if __name__ == "__main__":
a = 100
foo() # 修改a的值为200,这个作用域中的a是全局的
print(a) # 输出200
同理,如果希望内部函数修改嵌套函数中的变量,加上nonlocal就可以
在实际开发中,我们应该尽量减少对全局变量的使用,因为全局变量的作用域和影响过于广泛,可能会发生意料之外的修改和使用,除此之外全局变量比局部变量拥有更长的生命周期,可能导致对象占用的内存长时间无法被垃圾回收。事实上,减少对全局变量的使用,也是降低代码之间耦合度的一个重要举措,同时也是对迪米特法则的践行。减少全局变量的使用就意味着我们应该尽量让变量的作用域在函数的内部,但是如果我们希望将一个局部变量的生命周期延长,使其在函数调用结束后依然可以访问,这时候就需要使用闭包
以后写程序按照以下格式写,变量作用域
这样变量就不是全局变量,有助于垃圾回收
def main():
pass # 代码
if name == “main“:
main()