管理人Kのひとりごと

デジモノレビューやプログラミングや写真など

os.path.join()では'/'が入ったパスを連結するときに注意

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