当前位置: 首页 > 后端技术 > Python

Python从入门到精通100天-Day05-Usingfunctionsandmodules

时间:2023-03-26 02:01:35 Python

Usingfunctionsandmodules在讲解本章内容之前,我们先来研究一道数学题。请说出下列方程有多少个正整数解。$$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='))fm=1fornuminrange(1,m+1):fm*=numfn=1fornuminrange(1,n+1):fn*=numfmn=1fornuminrange(1,m-n+1):fmn*=numprint(fm//fn//fmn)函数的作用上面代码中,阶乘计算了3次。这段代码实际上是一个重复的代码。编程大师MartinFowler先生曾说过:“代码有很多难闻的气味,而重复是最糟糕的一种!”。对于上面的代码,可以将计算阶乘的函数封装成一个函数模块,叫做“function”。在我们需要计算阶乘的地方,只需要“调用”这个“函数”即可。定义函数在Python中可以使用def关键字来定义函数,命名规则与变量的命名规则一致。传递给函数的参数可以放在函数名后面的括号里,这很像数学中的函数。程序中函数的参数相当于数学中函数的参数,函数执行后我们可以通过return关键字返回一个值,相当于数学中函数的因变量。在理解了如何定义函数之后,我们就可以重构上面的代码了。所谓重构,就是在不影响代码执行结果的情况下,调整代码的结构。重构后的代码如下。deffactorial(num):"""计算阶乘:paramnum:非负整数:return:num"""result=1forninrange(1,num+1):result*=nreturnresultm=int(input('m='))n=int(input('n='))print(factorial(m)//factorial(n)//factorial(m-n))说明:Python的math模块其实有已经是一个阶乘函数。其实要计算阶乘,不用自己定义,直接用这个现成的函数就可以了。以下示例中的一些函数实际上是Python内置的。我们在这里再次实现它们来解释函数的定义和使用。实际开发中不建议做这种低级的重复性工作。函数的参数函数是大多数编程语言所支持的代码的“积木”,但是Python中的函数与其他语言中的函数还是有很多区别的。显着差异之一是Python支持处理函数参数。在Python中,函数的参数可以有默认值,也支持使用可变参数,所以Python不需要像其他语言一样支持函数重载,因为我们在定义一个函数的时候,可以让它有很多不同的Howto使用,下面是两个小例子。fromrandomimportrandintdefroll_dice(n=2):"""Rolldice:paramn:骰子数:return:n个骰子点的总和"""total=0for_inrange(n):total+=randint(1,6)returntotaldefadd(a=0,b=0,c=0):returna+b+c#如果不指定参数,使用默认值掷两个骰子print(roll_dice())#掷三个diceprint(roll_dice(3))print(add())print(add(1))print(add(1,2))print(add(1,2,3))#passparameters你可以通过print(add(c=50,a=100,b=200))的顺序不在设定的顺序中。上面两个函数的参数都设置了默认值,也就是说如果没有传入对应参数的值,就会使用参数的默认值,所以可以通过多种方式调用add函数在上面的代码中,这与很多其他语言中函数重载的效果是一致的。上面的add函数还有一个更好的实现,因为可能会添加零个或多个参数,具体的参数个数由调用者决定。作为功??能设计师,我们对此有一定的了解。未知,所以当参数个数不确定时,我们可以使用可变参数,代码如下。#参数名前面的*表示args是可变参数#即调用add函数时可以传入零个或多个参数defadd(*args):total=0forvalinargs:total+=valreturntotalprint(add())print(add(1))print(add(1,2))print(add(1,2,3))print(add(1,3,5,7,9))模块管理函数对于任何编程语言来说,变量、函数等标识符的命名都是一件令人头疼的事情,因为我们会遇到命名冲突的尴尬情况。最简单的场景是在同一个.py文件中定义两个同名的函数。由于Python没有函数重载的概念,后面的定义会覆盖前面的定义,也就是说两个同名函数实际上只有一个存在。deffoo():print('hello,world!')deffoo():print('goodbye,world!')#下面的代码会输出什么?foo()#输出再见,世界!如何解决命名冲突?Python中的每个文件代表一个模块。我们可以在不同的模块中使用相同名称的函数。在使用函数的时候,我们通过import关键字导入指定的模块,可以区分要使用的是哪个模块的foo函数。代码如下。module1.pydeffoo():print('你好,世界!')module2.pydeffoo():print('再见,世界!')test.pyfrommodule1importfoo#outputhello,world!foo()frommodule2importfoo#outputgoodbye,world!foo()也可以通过以下方式区分使用哪个foo函数。test.pyimportmodule1asm1importmodule2asm2m1.foo()m2.foo()但是如果代码是这样写的,那么程序中调用的是最后导入的foo,因为后面导入的foo覆盖了前面导入的foo。test.pyfrommodule1importfoofrommodule2importfoo#输出goodbye,world!foo()test.pyfrommodule2importfoofrommodule1importfoo#输出hello,world!foo()除了定义函数外,要导入的模块还有可执行文件code,然后Python解释器将在导入此模块时执行这些代码。其实这未必是意料之中的,所以如果在模块中写执行代码,最好将这些执行代码放到如下所示的条件中,这样的话,除非直接运行模块,if下的代码condition不会被执行,因为只有直接执行的模块的名字是“__main__”。module3.pydeffoo():passdefbar():pass#__name__是Python中的一个隐式变量,代表模块的名称#只有Python解释器直接执行的模块名称是__main__if__name__=='__main__':print('callfoo()')foo()print('callbar()')bar()test.pyimportmodule3#导入module3时,模块中if条件为真时的代码不会执行,因为模块名称__main__改为module3练习练习一:实现计算最大公约数和最小公倍数的功能。defgcd(x,y):(x,y)=(y,x)ifx>yelse(x,y)forfactorinrange(x,0,-1):ifx%factor==0andy%factor==0:returnfactordeflcm(x,y):returnx*y//gcd(x,y)练习二:实现判断一个数是否回文的功能。defis_palindrome(num):temp=numtotal=0whiletemp>0:total=total*10+temp%10temp//=10returntotal==num练习3:实现判断一个数是否为质数的功能不是。defis_prime(num):forfactorinrange(2,num):ifnum%factor==0:returnFalsereturnTrueifnum!=1elseFalse练习四:编写程序判断输入的正整数是否为a回文素数。if__name__=='__main__':num=int(input('请输入一个正整数:'))ifis_palindrome(num)andis_prime(num):print('%d是一个回文素数'%num)in将代码中重复出现的、相对独立的功能提取成函数后,这些功能可以组合使用,解决更复杂的问题,这也是为什么要定义和使用函数的一个很重要的原因。最后,让我们讨论一下Python中变量的作用域。deffoo():b='hello'defbar():#Python可以在函数内部重新定义函数c=Trueprint(a)print(b)print(c)bar()#print(c)#NameError:name'c'isnotdefinedif__name__=='__main__':a=100#print(b)#NameError:name'b'isnotdefinedfoo()上面的代码可以顺利执行并打印出100和“hello”,但是bar函数内部并没有定义a和b这两个变量,所以a和b从哪里来。在上面代码的if分支中定义了一个变量a,它是一个全局变量(globalvariable),属于全局作用域,因为它没有定义在任何函数中。上面foo函数中定义了变量b,它是函数中定义的局部变量(localvariable),属于局部作用域,在foo函数外无法访问;但是对于foo函数内部的bar函数,比如变量b属于嵌套作用域,我们可以在bar函数中访问到。bar函数中的变量c属于局部作用域,在bar函数外无法访问。实际上,Python在查找变量时,会按照“局部作用域”、“嵌套作用域”、“全局作用域”、“内置作用域”的顺序进行查找。前三个在上面的代码中都见过,所谓“内置作用域”指的是Python内置的隐式标识符,如min、len等,都属于内置作用域)。再看下面的代码,希望通过函数调用来修改全局变量a的值,但实际上下面的代码是做不到的。deffoo():a=200print(a)#200if__name__=='__main__':a=100foo()print(a)#100调用foo函数后,我们发现a的值还是100,这是因为当我们在函数foo中写a=200时,我们重新定义了一个名为a的局部变量,它和全局作用域中的a不是同一个变量,因为局部作用域有自己的变量a,所以foo函数没有longer在全局范围内搜索a。如果要在foo函数中修改全局范围内的a,代码如下。deffoo():globalaa=200print(a)#200if__name__=='__main__':a=100foo()print(a)#200global关键字可以用来表示foo函数中的变量a来自全局作用域,如果全局作用域中没有a,那么下面这行代码会定义变量a,并将其放在全局作用域中。同样,如果想让函数内部的函数修改嵌??套作用域内的变量,可以使用nonlocal关键字来表示变量来自嵌套作用域。在实际开发中,应该尽量减少全局变量的使用,因为全局变量的范围和影响太广,可能会出现意想不到的修改和使用。此外,全局变量的生命周期比局部变量长。循环,可能导致对象占用的内存长时间无法被垃圾回收)。其实,减少全局变量的使用也是降低代码间耦合度的重要措施,也是Dimiter定律的一种实践。减少全局变量的使用意味着我们应该尽量将变量的作用域保持在函数内部,但是如果我们想延长一个局部变量的生命周期,使其在函数调用结束后仍然可以被访问,那么我们需要用到闭包),我们将在后面的内容中进行说明。解释:很多人经常把“闭包”和“匿名函数”混淆,其实它们是不同的概念。如果想提前了解这个概念,建议阅读维基百科或知乎上关于这个概念的讨论。说了这么多,结论其实很简单。从现在开始,Python代码可以写成下面的格式。这个小小的改进实际上是在理解功能和作用域的基础上迈出的一大步。defmain():#Todo:Addyourcodeherepassif__name__=='__main__':main()本文基于github开源项目(作者:罗浩)https://github.com/jackfrued/Python-100天。Git使用这个项目开始使用Python。本系列文章是为了记录学习过程,分享给有兴趣的朋友。我基于这个项目创建了一个新项目https://github.com/skygiter/Python-100Days.git