文章目录
- Python 学习日志(8.27)
- 一、为什么要自己写函数、跨文件引用
- 二、在 math1 里面写函数
- 三、主文件引用函数的三种方式
- 四、测试代码与 __name__(踩坑预警)
- 不加 __name__ 判断会怎样?
- 把 '__main__' 改成别的会怎样?
- 五、函数传值的类型检查
- raise 和 print/return 的区别
- 常见的异常类型
- 多个 if 还是单个 if
- 六、文件结构
Python 学习日志(8.27)
学习教程:廖雪峰的 Python 教程 - 函数
一、为什么要自己写函数、跨文件引用
Ok啊,朋友们,今天来学习自己写函数和跨文件引用。
上次讲了一些常用的内置函数,接下来讲讲自己写函数和跨文件引用。函数全都写在一个文件里面,第一个问题就是文件太大了,而且很多时候函数不一定会全程用到,并且也不利于维护,所以分开代码逻辑十分重要。
例如老师留下的作业:
请定义一个函数
quadratic(a, b, c),接收 3 个参数,返回一元二次方程ax² + bx + c = 0的两个解。提示:一元二次方程的求根公式为
x = (−b ± √(b² − 4ac)) / 2a,计算平方根可以调用math.sqrt()函数。
>>>importmath>>>math.sqrt(2)1.4142135623730951importmathdefquadratic(a,b,c):pass# 测试:print('quadratic(2, 3, 1) =',quadratic(2,3,1))print('quadratic(1, 3, -4) =',quadratic(1,3,-4))ifquadratic(2,3,1)!=(-0.5,-1.0):print('测试失败')elifquadratic(1,3,-4)!=(1.0,-4.0):print('测试失败')else:print('测试成功')那么我们可以新建一个函数文件,叫math1。
二、在 math1 里面写函数
然后我们在 math1 里面写函数:
importmathdefjueduizhi(x):ifx<=0:return-xelse:returnxdefpingfanggen(a,b,c):ifa<0:print('被除数小于0!')returnNoneelse:d=(b*b)-(4*a*c)e1=-b-(math.sqrt(d))e2=-b+(math.sqrt(d))x1=e1/(2*a)x2=e2/(2*a)returnx2,x1这里要提醒几个事情:
import math是为了引入 Python 自带的数学函数,因为我们要开根号;- 函数格式是
def 函数名(参数): 函数逻辑,最后一定要return函数结果,不然函数写了等于没有写; - 两个文件要放在同一个文件夹,Python 默认只在当前目录找模块;
- 文件(模块)名不能以数字开头,比如
2gongju.py会报错。
可以这么理解:Python 找模块就像你去楼下快递柜取件,快递柜只在你家楼下,你得把快递(文件)放在同一个小区(文件夹)里才取得到。
三、主文件引用函数的三种方式
接下来是主代码文件里面写的代码:
importmath1frommath1importpingfanggen# from math1 import *# 测试:print('pingfanggen(2, 3, 1) =',pingfanggen(2,3,1))print('pingfanggen(1, 3, -4) =',pingfanggen(1,3,-4))ifpingfanggen(2,3,1)!=(-0.5,-1.0):print('测试失败')elifpingfanggen(1,3,-4)!=(1.0,-4.0):print('测试失败')else:print('测试成功')其中import math1、from math1 import pingfanggen、from math1 import *是三种函数的引用方式:
| 引用方式 | 含义 | 调用方法 |
|---|---|---|
import math1 | 导入整个模块 | 用模块名.函数名调用 |
from math1 import pingfanggen | 只引入 math1 里面的 pingfanggen 函数 | 直接用函数名调用 |
from math1 import * | 引入 math1 里面的所有函数 | 直接用函数名调用 |
第一种方式例如:
importmath1 result=math1.pingfanggen(1,-3,2)print(result)日常用第二种就好,简洁高效。
那么上面的函数结果是:
pingfanggen(2, 3, 1) = (-0.5, -1.0) pingfanggen(1, 3, -4) = (1.0, -4.0) 测试成功四、测试代码与name(踩坑预警)
接下来是一个课外知识。测试代码是必不可少的,一般情况下我们会删掉或者注释,但是如果文件里面写了测试代码,因为import的时候,被导入文件里的代码会全部执行一遍。
⚠️ 踩坑预警:如果我们的函数文件里有测试代码,main.py 一导入也会跑!
比如上面的平方根函数,在引入主函数之前想先试试函数效果怎么样,那么我们可以直接在 math1 里面写:
print(pingfanggen(1,-3,2))这当然是允许的,但是有一个问题:会占用计算资源,而且也会输出出来,因为 import 的时候他也会被 Python 执行一遍。小的项目没有什么问题,但是大型项目里面,大家都写测试代码,那么输出就乱七八糟的,但是我们又不想注释或者删掉怎么办?
我们可以在 math1 文件里面这样写:
if__name__=='__main__':# 只有直接运行 pingfanggen.py 时才执行这里print(pingfanggen(1,-3,2))这样的话,如果 math1 文件是被引用后执行的,那么就不会运行print(pingfanggen(1, -3, 2))。
详细讲讲就是这个意思,一个 .py 文件有两种"命运":
| 命运 | 什么时候发生 | __name__的值 |
|---|---|---|
| 直接运行 | 你点运行按钮,或python pingfanggen.py | '__main__' |
| 被导入 | 别的文件import pingfanggen | pingfanggen(模块名) |
__name__是 Python 偷偷塞在每个文件里的隐藏变量,它自己会知道"我现在是被直接运行,还是被别家 import 了"。
所以if __name__ == '__main__':翻译成人话就是:
“如果我是被直接运行的,才执行下面这段测试代码;如果我是被别的文件借走的,就别捣乱。”
不加name判断会怎样?
例如如果不加这个特定语句,我们的 math1 里面写:
importmathdefjueduizhi(x):ifx<=0:return-xelse:returnxdefpingfanggen(a,b,c):ifa<0:print('被除数小于0!')returnNoneelse:d=(b*b)-(4*a*c)e1=-b-(math.sqrt(d))e2=-b+(math.sqrt(d))x1=e1/(2*a)x2=e2/(2*a)returnx2,x1print(pingfanggen(1,-3,2))那么回到主文件运行就会输出:
(2.0, 1.0) pingfanggen(2, 3, 1) = (-0.5, -1.0) pingfanggen(1, 3, -4) = (1.0, -4.0) 测试成功但是如果我们加上__name__判断,变成:
importmathdefjueduizhi(x):ifx<=0:return-xelse:returnxdefpingfanggen(a,b,c):ifa<0:print('被除数小于0!')returnNoneelse:d=(b*b)-(4*a*c)e1=-b-(math.sqrt(d))e2=-b+(math.sqrt(d))x1=e1/(2*a)x2=e2/(2*a)returnx2,x1# print(pingfanggen(1, -3, 2))if__name__=='__main__':# 只有直接运行 gongju.py 时才执行这里print(pingfanggen(1,-3,2))那么回到主文件运行,就会输出:
pingfanggen(2, 3, 1) = (-0.5, -1.0) pingfanggen(1, 3, -4) = (1.0, -4.0) 测试成功这样我们的测试代码就不会被运行出来了。
把 ‘main’ 改成别的会怎样?
肯定有小伙伴问"那如果我把 ‘main’ 改成别的会怎样?",例如:
if__name__=='math1':# 意思变成:"当我被 math1这个名字导入时"当然不会怎么样。
__name__是 Python系统自动决定的隐藏变量,不是你去"填"它,而是它自己根据运行方式变身。
| 运行方式 | __name__的值 |
|---|---|
| 直接运行这个文件 | '__main__' |
| 被别的文件 import | 模块名(就是文件名,不带 .py) |
例如:
print('我的 __name__ 是:',__name__)然后:
- 直接运行
math1.py→ 输出:我的 __name__ 是: __main__ - 在另一个文件里
import math1→ 输出:我的 __name__ 是: math1
同一个变量,两种命运,Python 自己切换,我们什么都不用做。
所以那对单引号的意义是:
if__name__=='__main__':# 问:"我现在是被直接运行吗?"就像门卫问你:“你是本楼住户吗?”——答案只能是"是"(__main__)或"不是"(其他)。你不能给门卫安排一个新答案,只能等他回答。
所以之前的
if __name__ == 'math1':语法上没错,但实际没人这么写——因为我们没法控制别人用什么名字导入文件,这个判断基本不会生效。约定俗成:这个位置就是写'__main__',背下来就行,不用想别的。
反正就是总结成一句话:'__main__'是系统发的"本楼住户"门禁卡,你只管拿它来对比,不用管它从哪来。
顺带一提,__name__这种前后双下划线的变量叫"魔法变量",Python 里还有很多(比如__file__表示当前文件路径、__doc__表示文档说明),以后遇到了知道"带双下划线的都是系统级的,不用自己定义"就行。
五、函数传值的类型检查
然后是函数传值的检查。大家一定听过"程序员做了一个酒吧,然后有个顾客进来点了份炒饭后程序爆炸"的笑话,但实际上就是我们真的不知道其他人会传什么东西进入函数,所以我们可以主动控制函数里面到底什么东西可以过去。例如:
defjueduizhi(x):ifnotisinstance(x,(int,float)):raiseTypeError('bad operand type')elifx<=0:return-xelse:returnx这里可以用isinstance判断,判断是不是我们不允许通过的类型。这里我们选择只能传入int和float,如果用户传了个字符串进来,这里isinstance输出不是整数和浮点数,达到我们的判断条件,就会直接中断程序并主动报错。
raise 和 print/return 的区别
这里使用了raise而不是print,区别就在于print(或者return),那么函数还是会继续进行下去,直到最后才会报错,而raise会直接强制中断程序,把错误信号抛出去,程序正常流程被打断。
打个比方:
return是外卖小哥正常把餐送到你手上,交易完成;raise是小哥发现餐盒洒了,直接按响警报(而不是硬着头皮把洒了的餐送给你)——警报一响,周围所有人都停下,要么有人来处理(try/except),要么整个店歇业(程序崩溃)。
一句话总结:return是"事办成了,交结果";raise是"事办砸了,喊救命"。办砸了不能假装办成,这就是为什么类型不对时用raise而不是return一个奇怪的值。
常见的异常类型
就算我们没写 raise,有些情况 Python 也会自动报错,例如平方根里面有 abc 三个参数,但是只传两个进去,就会自动报错。以下是一些常见的错误:
除了 TypeError,还有一大家子异常类型(Python 内置的):
| 异常名 | 什么时候出现 | 直观例子 |
|---|---|---|
| TypeError | 类型不对 | '1' + 1 |
| ValueError | 值不合法(类型对,值不对) | int('abc')字符串转整数失败 |
| NameError | 名字没定义/拼写错 | 用了没定义的变量 |
| IndexError | 下标越界 | L = [1,2,3]; L[10] |
| KeyError | 字典键不存在 | d = {}; d['xxx'] |
| ZeroDivisionError | 除以零 | 1 / 0 |
| FileNotFoundError | 文件不存在 | open('不存在的.txt') |
| AttributeError | 调用不存在的属性/方法 | 'abc'.append(1)字符串没有 append |
其中 TypeError 和 ValueError 最容易混淆——记住"类型不对是 TypeError(比如拿刀去切水),值不对是 ValueError(比如拿刀切空气)"。
多个 if 还是单个 if
上面代码实际上就可以写成:
defjueduizhi(x):ifnotisinstance(x,(int,float)):raiseTypeError('bad operand type')ifx<=0:return-xelse:returnx区别就在于是使用一个if还是多个if,正常情况下都是多个if,因为:
- 简单直观知道错误条件,便于维护;
- 写在一个 if 里面容易逻辑混淆。
因为
raise会直接中断程序,不会进行下一步的代码解析了,所以可以放心的使用多个if。
六、文件结构
Cpythoncode D:\yingyong\pyhton\pythoncode .venv main.py math1.py