Day06 - 函数和模块的使用_简述在编程中使用函数和模块的好处。-程序员宅基地

技术标签: python_100天从新手到大师_笔记  python  函数  系统化python零基础入门  

函数和模块的使用

在讲解本章节的内容之前,我们先来研究一道数学题,请说出下面的方程有多少组正整数解。

x 1 + x 2 + x 3 + x 4 = 8 x_1 + x_2 + x_3 + x_4 = 8 x1+x2+x3+x4=8

事实上,上面的问题等同于将8个苹果分成四组每组至少一个苹果有多少种方案。想到这一点问题的答案就呼之欲出了。

C M N = M ! N ! ( M − N ) ! , (M=7, N=3) C_M^N =\frac{M!}{N!(M-N)!}, \text{(M=7, N=3)} CMN=N!(MN)!M!,(M=7, N=3)

可以用Python的程序来计算出这个值,代码如下所示。

```Python
"""
输入M和N计算C(M,N)
"""

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)
```

函数的作用

不知道大家是否注意到,在上面的代码中,我们做了3次求阶乘,这样的代码实际上就是重复代码。编程大师Martin Fowler先生曾经说过:“代码有很多种坏味道,重复是最坏的一种!”,要写出高质量的代码首先要解决的就是重复代码的问题。对于上面的代码来说,我们可以将计算阶乘的功能封装到一个称之为“函数”的功能模块中,在需要计算阶乘的地方,我们只需要“调用”这个“函数”就可以了。

定义函数

在Python中可以使用def关键字来定义函数,和变量一样每个函数也有一个响亮的名字,而且命名规则跟变量的命名规则是一致的。在函数名后面的圆括号中可以放置传递给函数的参数,这一点和数学上的函数非常相似,程序中函数的参数就相当于是数学上说的函数的自变量,而函数执行完成后我们可以通过return关键字来返回一个值,这相当于数学上说的函数的因变量。

在了解了如何定义函数后,我们可以对上面的代码进行重构,所谓重构就是在不影响代码执行结果的前提下对代码的结构进行调整,重构之后的代码如下所示。

```Python
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))
```

**说明:**Python的math模块中其实已经有一个factorial函数了,事实上要计算阶乘可以直接使用这个现成的函数而不用自己定义。下面例子中的某些函数其实Python中也是内置了,我们这里是为了讲解函数的定义和使用才把它们又实现了一遍,实际开发中不建议做这种低级的重复性的工作。

函数的参数

函数是绝大多数编程语言中都支持的一个代码的“构建块”,但是Python中的函数与其他语言中的函数还是有很多不太相同的地方,其中一个显著的区别就是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())
# 摇三颗色子
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函数,这跟其他很多语言中函数重载的效果是一致的。

其实上面的add函数还有更好的实现方案,因为我们可能会对0个或多个参数进行加法运算,而具体有多少个参数是由调用者来决定,我们作为函数的设计者对这一点是一无所知的,因此在不确定参数个数的时候,我们可以使用可变参数,代码如下所示。

```Python
# 在参数名前面的*表示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))
```

用模块管理函数

对于任何一种编程语言来说,给变量、函数这样的标识符起名字都是一个让人头疼的问题,因为我们会遇到命名冲突这种尴尬的情况。最简单的场景就是在同一个.py文件中定义了两个同名函数,由于Python没有函数重载的概念,那么后面的定义会覆盖之前的定义,也就意味着两个函数同名函数实际上只有一个是存在的。

```Python
def foo():
	print('hello, world!')


def foo():
	print('goodbye, world!')


# 下面的代码会输出什么呢?
foo()
```

当然上面的这种情况我们很容易就能避免,但是如果项目是由多人协作进行团队开发的时候,团队中可能有多个程序员都定义了名为foo的函数,那么怎么解决这种命名冲突呢?答案其实很简单,Python中每个文件就代表了一个模块(module),我们在不同的模块中可以有同名的函数,在使用函数的时候我们通过import关键字导入指定的模块就可以区分到底要使用的是哪个模块中的foo函数,代码如下所示。

module1.py

```Python
def foo():
    print('hello, world!')
```

module2.py

```Python
def foo():
    print('goodbye, world!')
```

test.py

```Python
from module1 import foo

# 输出hello, world!
foo()

from module2 import foo

# 输出goodbye, world!
foo()
```

也可以按照如下所示的方式来区分到底要使用哪一个foo函数。

test.py

```Python
import module1 as m1
import module2 as m2

m1.foo()
m2.foo()
```

但是如果将代码写成了下面的样子,那么程序中调用的是最后导入的那个foo,因为后导入的foo覆盖了之前导入的foo

test.py

```Python
from module1 import foo
from module2 import foo

# 输出goodbye, world!
foo()
```

test.py

```Python
from module2 import foo
from module1 import foo

# 输出hello, world!
foo()
```

需要说明的是,如果我们导入的模块除了定义函数之外还中有可以执行代码,那么Python解释器在导入这个模块时就会执行这些代码,事实上我们可能并不希望如此,因此如果我们在模块中编写了执行代码,最好是将这些执行代码放入如下所示的条件中,这样的话除非直接运行该模块,if条件下的这些代码是不会执行的,因为只有直接执行的模块的名字才是“__main__”。

module3.py

```Python
def foo():
    pass


def bar():
    pass


# __name__是Python中一个隐含的变量它代表了模块的名字
# 只有被Python解释器直接执行的模块的名字才是__main__
if __name__ == '__main__':
    print('call foo()')
    foo()
    print('call bar()')
    bar()
```

test.py

```Python
import module3

# 导入module3时 不会执行模块中if条件成立时的代码 因为模块的名字是module3而不是__main__
```

练习

练习1:实现计算求最大公约数和最小公倍数的函数。
```Python
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)
```
练习2:实现判断一个数是不是回文数的函数。
```Python
def is_palindrome(num):
	temp = num
	total = 0
	while temp > 0:
		total = total * 10 + temp % 10
		temp //= 10
	return total == num
```
练习3:实现判断一个数是不是素数的函数。
```Python
def is_prime(num):
	for factor in range(2, num):
		if num % factor == 0:
			return False
	return True if num != 1 else False
```
练习4:写一个程序判断输入的正整数是不是回文素数。
```Python
if __name__ == '__main__':
	num = int(input('请输入正整数: '))
	if is_palindrome(num) and is_prime(num):
		print('%d是回文素数' % num)
```

通过上面的程序可以看出,当我们将代码中重复出现的和相对独立的功能抽取成函数后,我们可以组合使用这些函数来解决更为复杂的问题,这也是我们为什么要定义和使用函数的一个非常重要的原因。

最后,我们来讨论一下Python中有关变量作用域的问题。

```Python
def foo():
	b = 'hello'

	def bar():  # Python中可以在函数内部再定义函数
        c = True
        print(a)
        print(b)
        print(c)

	bar()
    # print(c)  # NameError: name 'c' is not defined


if __name__ == '__main__':
	a = 100
    # print(b)  # NameError: name 'b' is not defined
	foo()
```

上面的代码能够顺利的执行并且打印出100和“hello”,但我们注意到了,在bar函数的内部并没有定义ab两个变量,那么ab是从哪里来的。我们在上面代码的if分支中定义了一个变量a,这是一个全局变量(global variable),属于全局作用域,因为它没有定义在任何一个函数中。在上面的foo函数中我们定义了变量b,这是一个定义在函数中的局部变量(local variable),属于局部作用域,在foo函数的外部并不能访问到它;但对于foo函数内部的bar函数来说,变量b属于嵌套作用域,在bar函数中我们是可以访问到它的。bar函数中的变量c属于局部作用域,在bar函数之外是无法访问的。事实上,Python查找一个变量时会按照“局部作用域”、“嵌套作用域”、“全局作用域”和“内置作用域”的顺序进行搜索,前三者我们在上面的代码中已经看到了,所谓的“内置作用域”就是Python内置的那些隐含标识符minlen等都属于内置作用域)。

再看看下面这段代码,我们希望通过函数调用修改全局变量a的值,但实际上下面的代码是做不到的。

```Python
def foo():
	a = 200
	print(a)  # 200


if __name__ == '__main__':
	a = 100
	foo()
	print(a)  # 100
```

在调用foo函数后,我们发现a的值仍然是100,这是因为当我们在函数foo中写a = 200的时候,是重新定义了一个名字为a的局部变量,它跟全局作用域的a并不是同一个变量,因为局部作用域中有了自己的变量a,因此foo函数不再搜索全局作用域中的a。如果我们希望在foo函数中修改全局作用域中的a,代码如下所示。

```Python
def foo():
	global a
	a = 200
	print(a)  # 200


if __name__ == '__main__':
	a = 100
	foo()
	print(a)  # 200
```

我们可以使用global关键字来指示foo函数中的变量a来自于全局作用域,如果全局作用域中没有a,那么下面一行的代码就会定义变量a并将其置于全局作用域。同理,如果我们希望函数内部的函数能够修改嵌套作用域中的变量,可以使用nonlocal关键字来指示变量来自于嵌套作用域,请大家自行试验。

在实际开发中,我们应该尽量减少对全局变量的使用,因为全局变量的作用域和影响过于广泛,可能会发生意料之外的修改和使用,除此之外全局变量比局部变量拥有更长的生命周期,可能导致对象占用的内存长时间无法被垃圾回收。事实上,减少对全局变量的使用,也是降低代码之间耦合度的一个重要举措,同时也是对迪米特法则的践行。减少全局变量的使用就意味着我们应该尽量让变量的作用域在函数的内部,但是如果我们希望将一个局部变量的生命周期延长,使其在函数调用结束后依然可以访问,这时候就需要使用闭包,这个我们在后续的内容中进行讲解。

说明:很多人经常会将“闭包”一词和“匿名函数”混为一谈,但实际上它们是不同的概念,如果想提前了解这个概念,推荐看看维基百科或者知乎上对这个概念的讨论。

说了那么多,其实结论很简单,从现在开始我们可以将Python代码按照下面的格式进行书写,这一点点的改进其实就是在我们理解了函数和作用域的基础上跨出的巨大的一步。

```Python
def main():
    # Todo: Add your code here
    pass


if __name__ == '__main__':
    main()
```
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_44621510/article/details/89609663

智能推荐

Spring Cloud入门教程-使用Spring Cloud Bus 刷新配置-程序员宅基地

文章浏览阅读559次,点赞7次,收藏16次。这份文档从构建一个键值数据库的关键架构入手,不仅带你建立起全局观,还帮你迅速抓住核心主线。除此之外,还会具体讲解数据结构、线程模型、网络框架、持久化、主从同步和切片集群等,帮你搞懂底层原理。相信这对于所有层次的Redis使用者都是一份非常完美的教程了。你的支持,我的动力;祝各位前程似锦,offer不断!!!《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!链图片转存中…(img-QZ9qSD8a-1713394629272)]你的支持,我的动力;

Java基础-构造函数_java构造函数-程序员宅基地

文章浏览阅读1.3w次,点赞19次,收藏87次。java基础-构造函数_java构造函数

【C++】洛谷P3382 【模板】三分法_c++ 整数域三分-程序员宅基地

文章浏览阅读193次。如题,给出一个N次函数,保证在范围[l,r]内存在一点x,使得[l,x]上单调增,[x,r]上单调减。试求出x的值。_c++ 整数域三分

入门指南【ML-Agents 官方文档翻译(ML-Agent 1.9.1,Unity 2018-2020)】_拖动3d球模型位于assets/ml-agents/examples/3dball/tfmodels-程序员宅基地

文章浏览阅读4.8k次。Getting Started Guide本指南将讲解在 Unity 中打开其中一个示例工程 ,训练一个Agent,并将训练过的模型嵌入到 Unity 环境中的完整过程。阅读本教程之后,您应该能够训练任何示例环境。如果你不熟悉的 Unity Engine,查看我们的 Background: Unity 章节。此外,如果你不熟悉机器学习,请查看我们的 Background: Machine Learning 页面,以了解简要概述和有用的建议。我们将使用 3D Balance Ball 环境,它包含_拖动3d球模型位于assets/ml-agents/examples/3dball/tfmodels进入model下的财产be

4D毫米波雷达 ti awr2243 天线通道校准方法-程序员宅基地

文章浏览阅读2.8k次,点赞6次,收藏24次。通道之间校准是因为一个主设备和三个从设备之间的频率、相位、振幅不匹配。然后进行距离维度FFT,根据物体大致距离和距离分辨率,得到所在距离维度尖峰所在频点index及其复数值(复数数据),组成12*16的复数矩阵。每一块板子都要生成一个校准矩阵,将雷达原始数据相乘之后,在进行其它功能的开发。ti awr2243使用的是4片级联方案,4个三发四收雷达板,总共12个发射天线16个接收天线,192个虚拟通道。为补偿用的调频斜率;为参考虚拟通道的尖峰索引的复数值;为别的虚拟通道的尖峰索引的复数值;_awr2243

国家税务总局全国增值税发票查验平台网站js逆向分析及全逆向算法还原_“http://inv—veri.chinatax.gov.cn/”-程序员宅基地

文章浏览阅读2.1w次,点赞2次,收藏22次。本文教程针对的事2021年7月2日时国税查验平台的js分析,其中版本号为V2.0.06_009。主要分析内容为key9和flwq39以及fplx这3个参数的算法,其中key9分为获取验证码阶段和查验阶段,算法有所区别,flwq39同理。教程开始:一、官方网址https://inv-veri.chinatax.gov.cn/index.html二、请求分析国税查验平台请求共分为2个,第一个请求获取验证码,第二个请求为输入验证码后查验数据并返回发票详细信息。第一步:安装证书基础:谷歌_“http://inv—veri.chinatax.gov.cn/”

随便推点

基于python小说推荐系统 协同过滤推荐算法 双推荐算法 Django框架 毕业设计-程序员宅基地

文章浏览阅读100次。基于python小说推荐系统 协同过滤推荐算法 双推荐算法 Django框架 毕业设计大数据分析毕设之基于python+Django的小说推荐系统大数据大屏数据可视化分析+协同过滤推荐系统-大数据分析毕设之基于python+Django的小说推荐系统大数据大屏数据可视化分析+协同过滤推荐系统-大数据分析毕设之基于python+Django的小说推荐系统大数据大屏数据可视化分析+协同过滤推荐系统-大数据分析毕设之基于python+Django的小说推荐系统大数据大屏数据可视化分析+协同过滤推荐系统-

5、循环神经网络(RNN )-程序员宅基地

文章浏览阅读1.4k次。最基本的单层网络,输入是x,经过变换Wx+b和激活函数f得到输出y。_循环神经网络

ubuntu18.04安装opencv3.4.3过程记录【附截图】_[modules\imgcodecs\cmakefiles\opencv_imgcodecs.dir-程序员宅基地

文章浏览阅读719次。安装ubuntu18.04+opencv3.4.3+OpenCV_contribOpenCV3.0以上的版本,把一些不稳定的函数放到了第三方库OpenCV_contrib中,为了应用这些功能,我们需要通过CMake把OpenCV_contrib中的功能重新加入到OpenCV中。https://github.com/opencv/opencv_contrib/blob/master/README.md安装依赖的软件包打开终端,安装以下软件包sudo apt install build-essent_[modules\imgcodecs\cmakefiles\opencv_imgcodecs.dir\build.make:431: bin/libop

chatgpt赋能python:Python快捷键大全_python上一段代码快捷键-程序员宅基地

文章浏览阅读375次。本文由chatgpt生成,文章没有在chatgpt生成的基础上进行任何的修改。以上只是chatgpt能力的冰山一角。作为通用的Aigc大模型,只是展现它原本的实力。对于颠覆工作方式的ChatGPT,应该选择拥抱而不是抗拒,未来属于“会用”AI的人。AI职场汇报智能办公文案写作效率提升教程 专注于AI+职场+办公方向。下图是课程的整体大纲下图是AI职场汇报智能办公文案写作效率提升教程中用到的ai工具。_python上一段代码快捷键

java中jquery怎么写_Jquery就是这么简单-程序员宅基地

文章浏览阅读759次。什么是Jquery?Jquey就是一款跨主流浏览器的JavaScript库,简化JavaScript对HTML操作就是封装了JavaScript,能够简化我们写代码的一个JavaScript库为什么要使用Jquery?我觉得非常重要的理由就是:它能够兼容市面上主流的浏览器,我们学习AJAX就知道了,IE和FireFox获取异步对象的方式是不一样的,而Jquery能够屏蔽掉这些不兼容的东西...(1..._java中实现jquery写法

蚂蚁区块链BaaS平台应用开发指南(四):JavaSDK的接入-程序员宅基地

文章浏览阅读2.5k次。在尝试本节的样例代码前,需要保证目标智能合约已经按照蚂蚁区块链BaaS平台应用开发指南(三):从一个简单合约开始中的做法编译部署成功。基于JavaSDK的接入在上一节里,我们通过Cloud IDE部署了一个最简单的智能合约,并且通过Cloud IDE成功的调用了合约的方法。拿传统应用的开发来类比,这就像在数据库上增加了一个存储过程,然后通过外部应用来触发这个存储过程的执行。那么,对于区块链..._com/alipay/mychain/sdk/api/env/issloption

推荐文章

热门文章

相关标签