Pythonでパスを連結する際には、os.path.join()を使うと思うのですが、'/'から始まるパスを連結する場合は注意が必要だった、というメモです。
確認環境
C:\Users\hoge>docker --version Docker version 20.10.6, build 370c289 python:3.8.10
そもそもos.path.join()とは
引数で与えられたパスを連結します。
docs.python.org
リファレンスの説明が英語なのですが、
If a component is an absolute path, all previous components are thrown away and joining continues from the absolute path component.
「コンポーネントが絶対パスの場合、それまでのコンポーネントはすべて捨てられ、絶対パスのコンポーネントから結合が続けられます」、ということだそうです。
どういう条件ならば「それまでのコンポーネントはすべて捨てられ」になるのか確かめる
/tmp/a/bというパスを基に、
- tmp, /tmp, tmp/, /tmp/
- a, /a, a/, /a/
- b, /b, b/, /b/
の組み合わせ64種+/tmp/aでの16種、計80種の結果を確かめた。
# | 1 | 2 | 3 | 期待値 | 得られた結果 | 一致 |
---|---|---|---|---|---|---|
1 | tmp | a | tmp/a | tmp/a | TRUE | |
2 | tmp | /a | tmp/a | /a | FALSE | |
3 | tmp | a/ | tmp/a/ | tmp/a/ | TRUE | |
4 | tmp | /a/ | tmp/a/ | /a/ | FALSE | |
5 | /tmp | a | /tmp/a | /tmp/a | TRUE | |
6 | /tmp | /a | /tmp/a | /a | FALSE | |
7 | /tmp | a/ | /tmp/a/ | /tmp/a/ | TRUE | |
8 | /tmp | /a/ | /tmp/a/ | /a/ | FALSE | |
9 | tmp/ | a | tmp/a | tmp/a | TRUE | |
10 | tmp/ | /a | tmp/a | /a | FALSE | |
11 | tmp/ | a/ | tmp/a/ | tmp/a/ | TRUE | |
12 | tmp/ | /a/ | tmp/a/ | /a/ | FALSE | |
13 | /tmp/ | a | /tmp/a | /tmp/a | TRUE | |
14 | /tmp/ | /a | /tmp/a | /a | FALSE | |
15 | /tmp/ | /a/ | /tmp/a/ | /a/ | FALSE | |
16 | /tmp/ | /a/ | /tmp/a/ | /a/ | FALSE | |
17 | tmp | a | b | tmp/a/b | tmp/a/b | TRUE |
18 | tmp | a | /b | tmp/a/b | /b | FALSE |
19 | tmp | a | b/ | tmp/a/b/ | tmp/a/b/ | TRUE |
20 | tmp | a | /b/ | tmp/a/b/ | /b/ | FALSE |
21 | tmp | /a | b | tmp/a/b | /a/b | FALSE |
22 | tmp | /a | /b | tmp/a/b | /b | FALSE |
23 | tmp | /a | b/ | tmp/a/b/ | /a/b/ | FALSE |
24 | tmp | /a | /b/ | tmp/a/b/ | /b/ | FALSE |
25 | tmp | a/ | b | tmp/a/b | tmp/a/b | TRUE |
26 | tmp | a/ | /b | tmp/a/b | /b | FALSE |
27 | tmp | a/ | b/ | tmp/a/b/ | tmp/a/b/ | TRUE |
28 | tmp | a/ | /b/ | tmp/a/b/ | /b/ | FALSE |
29 | tmp | /a/ | b | tmp/a/b | /a/b | FALSE |
30 | tmp | /a/ | /b | tmp/a/b | /b | FALSE |
31 | tmp | /a/ | b/ | tmp/a/b/ | /a/b/ | FALSE |
32 | tmp | /a/ | /b/ | tmp/a/b/ | /b/ | FALSE |
33 | /tmp | a | b | /tmp/a/b | /tmp/a/b | TRUE |
34 | /tmp | a | /b | /tmp/a/b | /b | FALSE |
35 | /tmp | a | b/ | /tmp/a/b/ | /tmp/a/b/ | TRUE |
36 | /tmp | a | /b/ | /tmp/a/b/ | /b/ | FALSE |
37 | /tmp | /a | b | /tmp/a/b | /a/b | FALSE |
38 | /tmp | /a | /b | /tmp/a/b | /b | FALSE |
39 | /tmp | /a | b/ | /tmp/a/b/ | /a/b/ | FALSE |
40 | /tmp | /a | /b/ | /tmp/a/b/ | /b/ | FALSE |
41 | /tmp | a/ | b | /tmp/a/b | /tmp/a/b | TRUE |
42 | /tmp | a/ | /b | /tmp/a/b | /b | FALSE |
43 | /tmp | a/ | b/ | /tmp/a/b/ | /tmp/a/b/ | TRUE |
44 | /tmp | a/ | /b/ | /tmp/a/b/ | /b/ | FALSE |
45 | /tmp | /a/ | b | /tmp/a/b | /a/b | FALSE |
46 | /tmp | /a/ | /b | /tmp/a/b | /b | FALSE |
47 | /tmp | /a/ | b/ | /tmp/a/b/ | /a/b/ | FALSE |
48 | /tmp | /a/ | /b/ | /tmp/a/b/ | /b/ | FALSE |
49 | tmp/ | a | b | tmp/a/b | tmp/a/b | TRUE |
50 | tmp/ | a | /b | tmp/a/b | /b | FALSE |
51 | tmp/ | a | b/ | tmp/a/b/ | tmp/a/b/ | TRUE |
52 | tmp/ | a | /b/ | tmp/a/b/ | /b/ | FALSE |
53 | tmp/ | /a | b | tmp/a/b | /a/b | FALSE |
54 | tmp/ | /a | /b | tmp/a/b | /b | FALSE |
55 | tmp/ | /a | b/ | tmp/a/b/ | /a/b/ | FALSE |
56 | tmp/ | /a | /b/ | tmp/a/b/ | /b/ | FALSE |
57 | tmp/ | a/ | b | tmp/a/b | tmp/a/b | TRUE |
58 | tmp/ | a/ | /b | tmp/a/b | /b | FALSE |
59 | tmp/ | a/ | b/ | tmp/a/b/ | tmp/a/b/ | TRUE |
60 | tmp/ | a/ | /b/ | tmp/a/b/ | /b/ | FALSE |
61 | tmp/ | /a/ | b | tmp/a/b | /a/b | FALSE |
62 | tmp/ | /a/ | /b | tmp/a/b | /b | FALSE |
63 | tmp/ | /a/ | b/ | tmp/a/b/ | /a/b/ | FALSE |
64 | tmp/ | /a/ | /b/ | tmp/a/b/ | /b/ | FALSE |
65 | /tmp/ | a | b | /tmp/a/b | /tmp/a/b | TRUE |
66 | /tmp/ | a | /b | /tmp/a/b | /b | FALSE |
67 | /tmp/ | a | b/ | /tmp/a/b/ | /tmp/a/b/ | TRUE |
68 | /tmp/ | a | /b/ | /tmp/a/b/ | /b/ | FALSE |
69 | /tmp/ | /a | b | /tmp/a/b | /a/b | FALSE |
70 | /tmp/ | /a | /b | /tmp/a/b | /b | FALSE |
71 | /tmp/ | /a | b/ | /tmp/a/b/ | /a/b/ | FALSE |
72 | /tmp/ | /a | /b/ | /tmp/a/b/ | /b/ | FALSE |
73 | /tmp/ | /a/ | b | /tmp/a/b | /a/b | FALSE |
74 | /tmp/ | /a/ | /b | /tmp/a/b | /b | FALSE |
75 | /tmp/ | /a/ | b/ | /tmp/a/b/ | /a/b/ | FALSE |
76 | /tmp/ | /a/ | /b/ | /tmp/a/b/ | /b/ | FALSE |
77 | /tmp/ | /a/ | b | /tmp/a/b | /a/b | FALSE |
78 | /tmp/ | /a/ | /b | /tmp/a/b | /b | FALSE |
79 | /tmp/ | /a/ | b/ | /tmp/a/b/ | /a/b/ | FALSE |
80 | /tmp/ | /a/ | /b/ | /tmp/a/b/ | /b/ | FALSE |
今回はdockerを使って確かめました。こんな感じです(Powershellから実行しましたので、\"hoge\"とエスケープしています)↓
docker run --rm -it python:3.8.10 python3 -c 'import os;print(os.path.join(\"tmp\",\"a\"))' > result.log docker run --rm -it python:3.8.10 python3 -c 'import os;print(os.path.join(\"tmp\",\"/a\"))' >> result.log docker run --rm -it python:3.8.10 python3 -c 'import os;print(os.path.join(\"tmp\",\"a/\"))' >> result.log ...
「コンポーネントが絶対パスの場合、それまでのコンポーネントはすべて捨てられ、絶対パスのコンポーネントから結合が続けられます」というのは、「/」から始まるパスを絶対パスとしてみなしているということのようですね。
No.50や、No.53などを見ればわかりますが、第2引数、第3引数によらず、「/」から始まるとそこを起点にパスが連結されていくようです。第2引数以降に「/」から始まるパスを指定する場合は意図しない結果となるかもしれないので、注意が必要ですね。
参考にしました
ワンライナーでimportする際の書き方を参考にしました
www.lifewithpython.com