作者ThxThx (洗洗睡)
看板Python
标题Re: [问题] 请问为何os.getcwd()不总在sys.path内?
时间Fri Jun 22 16:01:01 2018
原谅我只回关於import的部份XD。这部份我发现第一次遇到很难搞懂又常常忘记,我自己
也是边查边写。
import 的逻辑是什麽!?
======================
这边比较能够细读的官方文件是PEP-328和
https://docs.python.org/3/tutorial/modules.html 。
## Absolute Import
Absolute import 非常简单,只要不是以点(dot)开头的就是absolute import,例如:
import foo
import foo.bar as bar
from foo import bar
Absolute import的逻辑就是先找built-in module,没有的话从`sys.path`的资料夹底下
找一个<name>.py的档案或是<name>的package(package的定义:带有`__init__.py`)。
`sys.path`的组成包含
* 程式进入的script的资料夹
* `PYTHONPATH`环境变数
* 安装python时指定的位置,通常是放安装的档案
## Relative Import
Relative import 以点作为起始,两个点代表上一层,以此类推,而且永远都是用
`from <> import <>` 来叙述。
至於这个"relative"是相对什麽?这个是用当前module的`__name__`来决定
* 如果它是`foo.bar`之类的值,那就是从`foo.bar`来做相对移动。
`foo`就是最上层的module。
* 如果它是`__main__`(当你执行python script的进入点),
那这个script就是最上层的module。
最後,不管是那一种relative import,都不能超过最上层的module。
## Import, the Pythonic Way
首先是,不要使用`from foo import *`这种形式。python的名字是会被覆盖的,也就是
说如果连续两行 `from <name> import *`然後又有重复的名字,那就会被第二行覆盖。
然後,分开程式进入点的档案(`__name__`是`__main__`)还有package。理由是你的进
入点会变成是`__main__`,那就没办法用relative import,那你就只能仰赖`sys.path`
来找你自己开发的档案。如果你不想要自己加工`sys.path`,那就要把你的package放在
程式进入点那层,也就是这样
project/
main.py
setup.py
packageA/
...
tests/
funtionA/
test_XXX.py
...
...
最後,尽量不要加工`sys.path`。原因跟第一点一样是名称冲突的问题:module是依序在
`sys.path`里面找,加工会影响其他module,可能会让後来的module import到错的档案,
这种超级难debug。
## Take Away
* absolute import: 用`sys.path`决定
* relative import: 用`__name__`决定,不能超过top-level module
* 不要用`*`、分开entrypoint和package、不要加工`sys.path`
--
※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 114.44.244.29
※ 文章网址: https://webptt.com/cn.aspx?n=bbs/Python/M.1529654464.A.920.html
1F:→ uranusjr: Absolute import 那边不太对, 它只看 sys.path, 不一定 06/22 19:25
2F:→ uranusjr: 是先找 built-in (其实 Python 直译器本身并不知道哪些 06/22 19:26
3F:→ uranusjr: 模组是 built-in 哪些不是) 06/22 19:26
4F:→ uranusjr: 不过这篇的概念很棒, 可惜我不是板主不能 m... 06/22 19:28
6F:→ ThxThx: 我猜这里的built in是指用C写的那些modules,不过从来没试 06/22 23:31
7F:→ ThxThx: 过XD。 06/22 23:31
8F:推 os653: 想请教一下,那 debug 跟 test 的档案要放哪里? 06/23 08:45
9F:→ os653: 按照此篇所述,debug 跟 test 的档案要放在 project 最上层 06/23 08:45
10F:→ os653: 如此才能不加工 sys.path 又能使用 relative import 06/23 08:46
sys.path和relative import无关
11F:→ os653: 但这很奇怪呀,尤其 test 还有可能依测试目的放不同资料夹 06/23 08:47
我修改了上面project的架构,test资料夹通常会放在top-level。
我假设你说的debug是指开发的时候用来测的script,test是指拿来跑unit test的档案。
Debug我会把他看成是用完就丢不会把他放进版控,所以不是放在最上层就是加`sys.path`
,不然就是用`PYTHONPATH`
test的话有蛮多种的,我是用pytest这个framework,然後执行的时候加`PYTHONPATH`
,像是
PYTHONPATH='./' pytest
另外,如果你写好setup.py,那可以用tox这个工具帮你创好虚拟环境(而且一次跑多个
版本的python),安装你写的package然後跑你要的指令。所以open source常见的组
合就是
tox(创环境) + pytest(找test script来跑) + travis(免洗的标准环境)
当然小project可以不用这麽认真,原则就是自动化test,source code不要乱加东西。
最後请注意,以上讲的都是用absolute import。Relative import是在你写的package里面
用,absolute import是用来找到package的root folder。
※ 编辑: ThxThx (114.44.244.29), 06/23/2018 11:39:54
12F:推 os653: 感谢解说,有空试试看这篇里讲的东西看看会不会比较好用 06/23 15:41
13F:推 clsmbstu: 感谢解说!有几个问题我想再请教一下: 06/24 14:30
14F:→ clsmbstu: 1. PYTHONPATH环境变数可以赋予数个不同的资料夹吗? 06/24 14:31
就跟你设PATH一样用冒号隔开
15F:→ clsmbstu: 2. 如果我没理解错,看起来relative import只能写在「会 06/24 14:33
16F:→ clsmbstu: 被其它script/module import的module」?在程式本身写 06/24 14:34
17F:→ clsmbstu: relative import的话,一定会因为碰到__main__而错误。 06/24 14:34
不像javascript,code爱放哪就放哪
import python module要先找到package(用absolute import)然後再用点(dot)往下钻
relative import是给你在自己的package底下互相引用用的
18F:→ clsmbstu: BTW 对我来说 最常碰到的困扰倒不是因为debug或test档案 06/24 14:41
19F:→ clsmbstu: 我在做的大多是data analyses,所以本来就会把档案归类 06/24 14:45
20F:→ clsmbstu: 成不同的资料夹,例如: 06/24 14:45
21F:→ clsmbstu: projects/crons/month 是产生资料月报有关的档案 06/24 14:47
22F:→ clsmbstu: projects/crons/week 是产生资料周报有关的档案 06/24 14:47
23F:→ clsmbstu: projects/prediction_model 跟公司业务预测模型有关 06/24 14:48
24F:→ clsmbstu: projects/connection 连接公司资料库所需的package 06/24 14:49
25F:→ clsmbstu: projects/utils 是资料前处理、资料清洗的package 06/24 14:50
26F:→ clsmbstu: 这种状况下,relative import却完全帮不上忙 @@ 06/24 14:51
27F:→ clsmbstu: 又要不修改sys.path的话,只能让PYTHONPATH指向多处? 06/24 14:52
设PYTHONPATH是在执行的时候修改sys.path啊:P
如果是这样,那还不如加在sys.path里面,至少不用记得执行的时候要加东西。
你会这样放code可能也有其他的考量,我会这样建议是觉得,随意加sys.path也许没有增
加现在的开发成本,但是绝对是增加维护的成本。哪个成本比较重要就要看你的选择了。
回过头来,现在假设问题就是crons/week和prediction_model里的scripts都需要utils的
package。以前可能是每个script都写很长,但现在如果变成
projects/
crons_week.py
crons_month.py (scripts)
predict.py
...
crons/
... (产生资料报表的功能)
prediction_model/
... (model本身)
utils/
... (其他共用的逻辑)
所有第一层scripts的功能是parse CLI arguments然後呼叫其他资料夹里面的功能,不要
把复杂的逻辑写在这里面。也许这样每个人就很容易了解这些档案在做什麽、每个package
各自拥有什麽功能。
所以就是,我主张每个package把他看成是功能的集合/公约数。不一定要照做,不过
至少你现在知道absolute/relative import的原理了XD
28F:→ clsmbstu: 这也是为什麽我第一个问题那麽问 06/24 14:53
※ 编辑: ThxThx (114.44.244.29), 06/25/2018 01:27:26
29F:推 clsmbstu: Thank you!!! 06/25 10:29