Python
查看版本命令:
1 | python -V |
Python解释器
1 | #!/usr/bin/python3 |
关于实例中第一行代码**#!/usr/bin/python3** 的理解:
分成两种情况:
(1)如果调用python脚本时,使用:
1
python script.py
#!/usr/bin/python 被忽略,等同于注释。
(2)如果调用python脚本时,使用:
1
./script.py
#!/usr/bin/python 指定解释器的路径。
脚本语言的第一行,目的就是指出,你想要你的这个文件中的代码用什么可执行程序去运行它,就这么简单。
#!/usr/bin/python3 是告诉操作系统执行这个脚本的时候,调用 /usr/bin 下的 python3 解释器;
#!/usr/bin/env python3 这种用法是为了防止操作系统用户没有将 python3 装在默认的 /usr/bin 路径里。当系统看到这一行的时候,首先会到 env 设置里查找 python3 的安装路径,再调用对应路径下的解释器程序完成操作。
#!/usr/bin/python3 相当于写死了 python3 路径;
#!/usr/bin/env python3 会去环境设置寻找 python3 目录,推荐这种写法。
From/Import
import my_module
from my_module import * 有什么区别?
在 Python 中,import my_module 和 from my_module import * 都是用于导入模块的语句,但它们在导入方式、使用方法以及潜在影响上都存在明显区别:
导入内容
import my_module:这种方式会将整个my_module模块导入到当前命名空间中,通过my_module.的方式来访问模块内的函数、类、变量等成员。 例如,假设my_module模块中有一个名为add_numbers的函数,要调用它就需要写成my_module.add_numbers(3, 5)。from my_module import *:该语句会将my_module模块中所有定义的(没有以下划线_开头的)函数、类、变量等直接导入到当前命名空间中,不需要通过模块名作为前缀, 可以直接使用。 比如,对于my_module里的add_numbers函数,导入后可以直接写add_numbers(3, 5)。
命名空间管理
import my_module:能较好地管理命名空间,避免命名冲突。因为模块内的所有成员都被封装在my_module这个命名空间下, 只有显式使用my_module.才能访问。 即使当前脚本中定义了与my_module内同名的变量或函数,也不会相互影响。from my_module import *:会将模块中的所有成员直接暴露在当前命名空间中,如果当前脚本或其他已导入模块中存在同名的对象,就会产生命名冲突,导致程序运行出现意外结果。 例如,当前脚本中有一个名为count的变量,而my_module中也有一个count函数,使用from my_module import *导入后,原本的count变量就会被count函数覆盖。
性能影响
import my_module:在导入模块时,Python 解释器只是将模块对象加载到内存中,并在需要时通过模块名去访问其中的成员,相对来说比较高效。from my_module import *:虽然在使用上较为便捷,但解释器需要遍历模块中的所有成员,并将它们逐个导入到当前命名空间,在处理大型模块时, 可能会消耗更多的时间和资源。
假设 my_module.py 中有以下内容:
1 | # my_module.py |
(1)使用 import my_module 时:
必须通过 模块名.成员名 的方式调用,成员被 “包裹” 在模块的命名空间中:
1 | import my_module |
(2)使用 from my_module import * 时:
模块中的成员(非下划线开头的)会被直接导入当前命名空间,可直接通过成员名调用,无需模块名前缀:
1 | from my_module import * |
requirements.txt
requirements.txt 是 Python 项目中用于记录依赖包及其版本的配置文件,方便在不同环境中快速安装相同的依赖,确保项目运行环境一致。
1 | # 在项目根目录执行,将当前环境所有已安装的包导出 |
注意:
pip freeze会导出当前环境中所有安装的包(包括项目无关的),可能包含冗余内容。工具(需先安装:pip install pipreqs),它会扫描项目代码,只导出实际使用的依赖:
1
pipreqs . --encoding=utf8 # 在项目根目录执行,--encoding 解决中文编码问题
每个项目单独维护
requirements.txt,避免全局环境污染(建议结合虚拟环境venv或conda使用)。
n = yield r
n = yield r 是生成器(协程)中最核心的一行代码,它的执行逻辑比较特殊,需要拆分来看——这不是一个简单的赋值语句,而是“先返回,后接收”的两步操作。
拆分理解
“返回”和“接收”两个动作 可以把 n = yield r 拆成两个逻辑步骤:
第一步:执行 yield r:生成器暂停,返回 r 的值给调用者(比如通过 send() 或 next() 调用的地方)。
第二步:接收值并赋值给 n:当生成器被再次唤醒(比如调用 send(value) 时),会将 send() 传入的 value 赋值给 n,然后继续执行后面的代码。
用“时间线”理解
第一次启动生成器(c.send(None)): - 生成器执行到 n = yield r 时,先执行 yield r:返回 r 的初始值 '',然后生成器暂停(停在这一行,不再往下走)。 - 注意:此时 n 还没有被赋值(因为还没收到任何值)。
当生产者调用 c.send(1) 时: - 生成器从暂停的 n = yield r 处恢复,将 send(1) 传入的 1 赋值给 n。 - 接着执行后面的代码(if not n: ...、打印消费信息、给 r 赋值 '200 OK')。 - 再次进入循环,回到 n = yield r 这一行,执行 yield r:返回 r 的新值 '200 OK',然后生成器再次暂停,等待下一次唤醒。
后续每次 send(n) 都会重复第二步: - 唤醒生成器 → 把传入的值给 n → 执行代码 → 到 yield r 处返回 r 并暂停。
3.
一句话总结:
n = yield r 可以读作: “先返回 r 的值,然后暂停;等下次被唤醒时,接收一个值并赋给 n,再继续执行”。 它的关键在于:**yield 的返回动作发生在赋值给 n 之前**,两者不是同时执行的,而是被“暂停-唤醒”分割成了两个阶段。这也是生成器能实现“双向通信”的核心原因——既可以返回数据,又能接收外部传入的数据。