【python】06.函数和模块的使用

函数和模块的使用

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

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

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

"""
输入M和N计算C(M,N)Version: 0.1
Author: 骆昊
"""
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
fm_n = 1
for num in range(1, m - n + 1):fm_n *= num
print(fm // fn // fm_n)

函数的作用

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

定义函数

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

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

"""
输入M和N计算C(M,N)Version: 0.1
Author: 骆昊
"""
def fac(num):"""求阶乘"""result = 1for n in range(1, num + 1):result *= nreturn resultm = int(input('m = '))
n = int(input('n = '))
# 当需要计算阶乘的时候不用再写循环求阶乘而是直接调用已经定义好的函数
print(fac(m) // fac(n) // fac(m - n))

说明: Python的math模块中其实已经有一个名为factorial函数实现了阶乘运算,事实上求阶乘并不用自己定义函数。下面的例子中,我们讲的函数在Python标准库已经实现过了,我们这里是为了讲解函数的定义和使用才把它们又实现了一遍,实际开发中并不建议做这种低级的重复劳动

函数的参数

函数是绝大多数编程语言中都支持的一个代码的"构建块",但是Python中的函数与其他语言中的函数还是有很多不太相同的地方,其中一个显著的区别就是Python对函数参数的处理。在Python中,函数的参数可以有默认值,也支持使用可变参数,所以Python并不需要像其他语言一样支持函数的重载,因为我们在定义一个函数的时候可以让它有多种不同的使用方式,下面是两个小例子。

from random import randintdef roll_dice(n=2):"""摇色子"""total = 0for _ in range(n):total += randint(1, 6)return totaldef 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个或多个参数进行加法运算,而具体有多少个参数是由调用者来决定,我们作为函数的设计者对这一点是一无所知的,因此在不确定参数个数的时候,我们可以使用可变参数,代码如下所示。

# 在参数名前面的*表示args是一个可变参数
def add(*args):total = 0for val in args:total += valreturn 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没有函数重载的概念,那么后面的定义会覆盖之前的定义,也就意味着两个函数同名函数实际上只有一个是存在的。

def foo():print('hello, world!')def foo():print('goodbye, world!')# 下面的代码会输出什么呢?
foo()

当然上面的这种情况我们很容易就能避免,但是如果项目是由多人协作进行团队开发的时候,团队中可能有多个程序员都定义了名为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 m2m1.foo()
m2.foo()

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

test.py

from module1 import foo
from module2 import foo# 输出goodbye, world!
foo()

test.py

from module2 import foo
from module1 import foo# 输出hello, world!
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

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

练习

练习1:实现计算求最大公约数和最小公倍数的函数。

参考答案:

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 factordef lcm(x, y):"""求最小公倍数"""return x * y // gcd(x, y)
练习2:实现判断一个数是不是回文数的函数。

参考答案:

def is_palindrome(num):"""判断一个数是不是回文数"""temp = numtotal = 0while temp > 0:total = total * 10 + temp % 10temp //= 10return total == num
练习3:实现判断一个数是不是素数的函数。

参考答案:

def is_prime(num):"""判断一个数是不是素数"""for factor in range(2, int(num ** 0.5) + 1):if num % factor == 0:return Falsereturn True if num != 1 else False
练习4:写一个程序判断输入的正整数是不是回文素数。

参考答案:

if __name__ == '__main__':num = int(input('请输入正整数: '))if is_palindrome(num) and is_prime(num):print('%d是回文素数' % num)

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

变量的作用域

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

def foo():b = 'hello'# Python中可以在函数内部再定义函数def bar():c = Trueprint(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 definedfoo()

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

再看看下面这段代码,我们希望通过函数调用修改全局变量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关键字来指示变量来自于嵌套作用域,请大家自行试验。

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

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

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

def main():# Todo: Add your code herepassif __name__ == '__main__':main()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/619277.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot 整合支付宝实现在线支付方案(沙箱环境)

文章目录 1.理解沙箱环境2.沙箱环境接入准备2.1 访问开发者控制台2.2 获取重要信息2.3 处理秘钥 3.接入支付宝支付的流程4.实现支付4.1 添加 SDK 依赖4.2 创建配置类4.3 支付宝订单管理接口实现流程4.4 支付宝支付接口实现流程 5.支付宝支付功能演示7.总结 TIP:对于…

软件测试常见问题2

1.用jmeter怎么进行测试? 使用JMeter进行测试的步骤如下: 启动JMeter,右键点击测试计划,选择添加->Threads(Users)->线程组,在线程组下创建请求。在请求中添加HTTP请求信息头,右键点击HTTP请求&…

15-MT-Channel based Broker和Kafka Channel模式

部署MT-Channel based Broker URL: https://knative.dev/docs/install/yaml-install/eventing/install-eventing-with-yaml/#optional-install-a-broker-layer kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.12.1/mt-channel…

2023年复盘—坚持长期主义

大家好,这里是大话硬件。 按照往年的逻辑,很多知识博主都会在12月份发出2023年的年终总结出来,但是今年好像很多博主还没有更新类似总结文章。去年我是在12月24号更新的,今年的总结文章在上周有一些简单的构想,但是没有时间写,因此,这篇文章对2023 年进行一个复盘。 复…

【UEFI基础】EDK网络框架(VLAN)

VLAN VLAN代码综述 在MNP中有很多的VLAN介绍,MNP存在的一个重要原因也是为了处理VLAN,而本文介绍的NetworkPkg\VlanConfigDxe\VlanConfigDxe.inf其实只是一个帮助模块,真正的VLAN配置还是在MNP中。 VLAN同样是一个UEFI Driver Model&#…

[蓝桥杯 2013 省 B] 翻硬币

题目背景 小明正在玩一个“翻硬币”的游戏。 题目描述 桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零),比如可能情形是 **oo***oooo,如果同时翻转左边的两个硬币&…

力扣(leetcode)第680题验证回文串II(Python)

680.验证回文串II 题目链接:680.验证回文串II 给你一个字符串 s,最多 可以从中删除一个字符。 请你判断 s 是否能成为回文字符串:如果能,返回 true ;否则,返回 false 。 示例 1: 输入&#x…

Redis实现分布式会话

Redis实现分布式会话 1 什么是分布式会话 1 这是我么之前学过的注册登录模式 2 如果非常多的人访问,因为单台服务器的访问承受能力是有限的,那么我们就想用多态服务器来承担压力 3 一般通过负载均衡的方式来实现,来分担服务器的压力。 4 负…

算法每日一题:统计出现过一次的公共字符串 | 哈希 | getOrDefault()

大家好,我是星恒 今天给大家带来的又是一道经典的哈希的题目;这道题本身并不难,但他将getOrDefault()函数利用的简直不要太优雅了,很适合作为理解Map应用的学习题目 题目:给你两个字符串数组 words1 和 words2 &#x…

【PlantUML】- 时序图

写在前面 本篇文章,我们来介绍一下PlantUML的时序图。这个相对类图来讲,比较简单,也不需要布局。读完文章,相信你就能实际操作了。 目录 写在前面一、基本概念二、具体步骤1.环境说明2.元素3.语法4.示例 三、参考资料写在后面系列…

作业-去重复统计(2)

扑克牌组合 题目描述 小明从一副扑克牌中(没有大小王,J认为是数字11,Q是12,K是13,A是1)抽出2张牌求和,请问能够组合出多少个不相等的数,按照由小到大输出这些数。 输入 第一行是一个…

软件测试|Python数据可视化神器——pyecharts教程(十)

使用pyecharts绘制漏斗图 简介 漏斗图(Funnel Chart)是一种用于可视化数据流程或转化率的图表类型。它通常由一系列阶段组成,每个阶段都有一个名称和一个值,表示在该阶段的转化量或数据流程的进展情况。漏斗图的名称来源于其外观…

Web自动化测试,一定得掌握的 8 个核心知识点

使用 cypress 进行端对端测试,和其他的一些框架有一个显著不同的地方,它使用 javascript 作为编程语言。传统主流的 selenium 框架是支持多语言的,大多数 QA 会的python 和 java 语言都可以编写 selenium 代码,遇到需要编写 js 代…

linux文件描述符管理

在实际的项目开发中,文件描述符是经常用到的并且在释放资源过程中也是很容易忽略的,使用之后不释放就会增加cpu负担,无异于内存泄漏;所以时刻掌握文件描述符的状态是非常重要的!下面介绍文件描述符的管理方法。 1. 文…

好用的便签有哪些?windows便签工具在哪打开?

每当我8点准时上班,在等待电脑开机的过程,我都会习惯性地思考整理今天要晚上的任务,列出所要完成的待办事项。随着每一项任务的清晰呈现,我的心情也逐渐明朗起来。当然了,这个时候,我迫切需要一款好用的便签…

VS游戏打包教程

我用得天天酷跑小游戏做的例子 1:安装打包插件 2:在解决方案里新建一个项目 3:新建一个setup项目 4:界面如下(通过右键folder,可以创建folder目录和输出) 5:素材文件 6:素材放完了就项目输出 7:创建快捷方式 右键这个主输出选择第一个create shortcut 8:将这个快捷方式,拖到,…

算法通关村番外篇-LeetCode编程从0到1系列一

大家好我是苏麟 , 今天开始带来LeetCode编程从0到1系列 . 编程基础 0 到 1 , 50 题掌握基础编程能力 大纲 1768.交替合并字符串389. 找不同28. 找出字符串中第一个匹配项的下标242. 有效的字母异位词459. 重复的子字符串283. 移动零66. 加一1822. 数组元素积的符号1502. 判断能…

02二位数组算法

1.杨辉三角 算法思想&#xff1a; a[i][j]a[i-1][j]a[i-1][j-1];代码&#xff1a; #include<iostream> using namespace std; const int N 1e2 10; int a[N][N]; int main() {int n; cin >> n;a[1][1] a[2][1] a[2][2] 1;for (int i 3; i < n; i) {for…

【已解决】C语言如何使用宽字符输出中文

本博文源于笔者正在运行的程序想要在宽字符下运行中文wchar_t wstr[10]L"12凡人";想要正确输出它。 问题来源 想要正确输入宽字符下的中文 问题解决方案 #include<stdio.h> #include<locale.h> int main() {setlocale(LC_ALL, "zh-cn");wc…

Canopen学习笔记——sync同步报文增加数据域(同步计数器)

1.Canfestival同步报文sync的设置 在OD表中的配置如下&#xff1a; 如果0x1006索引的同步报文循环周期时间设置为0则禁用同步报文&#xff0c;这里要注意的就是&#xff0c;上面第一张图也提到了&#xff0c;时间单位是us。第二张图&#xff0c;我的0x1006就设置为0xF4240,也就…