subprocessのススメ



   岸本 誠
 (@ksmakoto)
最近書いているプログラム

class Ref:
   def __init__(self):
     self.v = None

def gps(i, n, z, v):
  i.v = 1
  while i.v <= n():
     z.v = v()

     i.v += 1
  return 1
i, p, a, z = Ref(), Ref(), Ref(), Ref()
m = 46
i.v = gps(i, lambda: (i.v == 0 and [-1] or [i.v])[0], p,
  lambda: (i.v == 1 and [1] or
  [(gps(a, lambda: i.v, z,
     lambda: (a.v == 1 and [1] or
         [((i.v % a.v == 0 and a.v < i.v)
           and [0] or [z.v])[0]])[0]) == z.v
  and [(p.v < m and [p.v + 1]
     or [i.v * gps(a, lambda: 1, i, lambda: -1)])[0]]
  or [p.v])[0]])[0])
本題
●   標準ライブラリの subprocess モジュールを
    使いましょう、という話
●   おことわり: Windows でも使えるライブラリですが、
    発表者が使っていないため Unix の話しかしません
●   深い話(背景とか)は(Rubyですが)以下の
    田中哲さんの発表資料を見ましょう
    –   Rubyとプロセス spawnについて
        http://www.a-k-r.org/pub/spawn-2009-04.pdf
    –   open3 のはなし
        http://www.a-k-r.org/pub/tokyo-rubykaigi-03-akr-2010.pdf
subprocess モジュールとは
●   Python 2.6 で標準ライブラリに導入
●   Python 3 で commands モジュール等は削除
●   他にも os.system, os.spawn, os.popen[234],
    popen2 モジュールの機能を代替する
●   謎の commands.getstatus
    (ファイルを指定して ls -ld の結果を返す)
ちょっとだけ背景
●   fork と exec
●   Unix の特徴とも言われる
    –   プロセスのクローンと、プログラムのオーバレイに分けた
    –   fork → なんかする → exec / すごくシンプルになった
●   難点(昔) – 重い
    –   コピーオンライト/vfork で解決?
●   変な応用例: DalvikVM – fork だけで exec しない
●   現代の難点
●   マルチスレッドとの相性 - すごく悪い
subprocess.Popen
class subprocess.Popen(args, bufsize=-1,
executable=None, stdin=None,
stdout=None, stderr=None,
preexec_fn=None, close_fds=True,
shell=False, cwd=None, env=None,
universal_newlines=False,
startupinfo=None, creationflags=0,
restore_signals=True,
start_new_session=False, pass_fds=())
なんでこんなキッチンシンクに?
●   キッチンシンク←なんでもかんでも放り込むたとえ




●   fork と exec の間に「いろいろできる」(リダイレクトと
    か)のを、全部引数で指示するから
●   計算機を仮想化する単位であるプロセスを切り離す
    作業だから、仮想化してあるものをここで全部(ファイ
    ルハンドラとか)面倒を見なきゃならないから
ひとつ注意
preexec_fn
exec の前に実行される関数引数。なんでも指定できる
が、可能な限り余計なことはしてはいけない。英語版ドキュ
メントには赤背景で以下のように書いてある。
Warning: The preexec_fn parameter is not safe to
use in the presence of threads in your application.
The child process could deadlock before exec is
called. If you must use it, keep it trivial! Minimize
the number of libraries you call into.
これは libc に由来するスレッド絡みの制限で、実は厄介。
ユースケース
●   標準ライブラリのドキュメントと PEP 324 から

●   以降は from subprocess import * してある
    前提で
シェルスクリプトのコマンド展開

result=`ls -l myfile`

result = check_output([”ls”, ”-l”, ”myfile”])
パイプラインありの展開
result=`dmesg | grep hda`


p1 = Popen(["dmesg"], stdout=PIPE)
p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
p1.stdout.close()
result = p2.communicate()[0]


(コマンド文字列インジェクションの心配がないなら)
result = check_output("dmesg | grep hda", shell=True)
os.system の代替

st = os.system(”cmd arg”)

st = call(”cmd”, ”arg”, shell=True)
(シェル経由で呼ぶのはsystemというAPIの特
徴。シェルが必要ないなら使わない)
os.spawn の代替
pid = os.spawnlp(os.P_NOWAIT, "/bin/cmd", "cmd", "arg")
pid = Popen(["/bin/cmd", "arg"]).pid


ret = os.spawnlp(os.P_WAIT, "/bin/cmd", "cmd", "arg")
ret = call(["/bin/cmd", "arg"])


os.spawnvp(os.P_NOWAIT, path, args)
Popen([path] + args[1:])


os.spawnlpe(os.P_NOWAIT, "/bin/cmd", "cmd", "arg", env)
Popen(["/bin/cmd", "arg"], env={"PATH": "/usr/bin"})
os.popen2

(child_stdin, child_stdout) = 
     os.popen2(cmd, mode, bufsize)

p = Popen(cmd, shell=True, bufsize=bufsize,
     stdin=PIPE, stdout=PIPE, close_fds=True)
(child_stdin, child_stdout) = (p.stdin, p.stdout)
os.popen3
(child_stdin,
child_stdout,
child_stderr) = os.popen3(cmd, mode, bufsize)

p = Popen(cmd, shell=True, bufsize=bufsize,
      stdin=PIPE, stdout=PIPE,
      stderr=PIPE, close_fds=True)
(child_stdin,
child_stdout,
child_stderr) = (p.stdin, p.stdout, p.stderr)
os.popen4
(child_stdin, child_stdout_and_stderr) = 
     os.popen4(cmd, mode, bufsize)

p = Popen(cmd, shell=True, bufsize=bufsize,
     stdin=PIPE, stdout=PIPE,
     stderr=STDOUT, close_fds=True)
(child_stdin, child_stdout_and_stderr) = 
     (p.stdin, p.stdout)
popen2 モジュール
(child_stdout, child_stdin) = 
popen2.popen2("somestring", bufsize, mode)

p = Popen(["somestring"], shell=True,
     bufsize=bufsize, stdin=PIPE,
     stdout=PIPE, close_fds=True)
(child_stdout, child_stdin) = (p.stdout, p.stdin)
まとめ

●   subprocess モジュールは、サブプロセスを作る既存
    の API 全てを代替する
●   全てを代替するために、subprocess の API は大
    変なことになっている
●   ケーススタディをマニュアルから紹介した

Subprocess no susume

  • 1.
    subprocessのススメ 岸本 誠 (@ksmakoto)
  • 2.
    最近書いているプログラム class Ref: def __init__(self): self.v = None def gps(i, n, z, v): i.v = 1 while i.v <= n(): z.v = v() i.v += 1 return 1
  • 3.
    i, p, a,z = Ref(), Ref(), Ref(), Ref() m = 46 i.v = gps(i, lambda: (i.v == 0 and [-1] or [i.v])[0], p, lambda: (i.v == 1 and [1] or [(gps(a, lambda: i.v, z, lambda: (a.v == 1 and [1] or [((i.v % a.v == 0 and a.v < i.v) and [0] or [z.v])[0]])[0]) == z.v and [(p.v < m and [p.v + 1] or [i.v * gps(a, lambda: 1, i, lambda: -1)])[0]] or [p.v])[0]])[0])
  • 4.
    本題 ● 標準ライブラリの subprocess モジュールを 使いましょう、という話 ● おことわり: Windows でも使えるライブラリですが、 発表者が使っていないため Unix の話しかしません ● 深い話(背景とか)は(Rubyですが)以下の 田中哲さんの発表資料を見ましょう – Rubyとプロセス spawnについて http://www.a-k-r.org/pub/spawn-2009-04.pdf – open3 のはなし http://www.a-k-r.org/pub/tokyo-rubykaigi-03-akr-2010.pdf
  • 5.
    subprocess モジュールとは ● Python 2.6 で標準ライブラリに導入 ● Python 3 で commands モジュール等は削除 ● 他にも os.system, os.spawn, os.popen[234], popen2 モジュールの機能を代替する ● 謎の commands.getstatus (ファイルを指定して ls -ld の結果を返す)
  • 6.
    ちょっとだけ背景 ● fork と exec ● Unix の特徴とも言われる – プロセスのクローンと、プログラムのオーバレイに分けた – fork → なんかする → exec / すごくシンプルになった ● 難点(昔) – 重い – コピーオンライト/vfork で解決? ● 変な応用例: DalvikVM – fork だけで exec しない ● 現代の難点 ● マルチスレッドとの相性 - すごく悪い
  • 7.
    subprocess.Popen class subprocess.Popen(args, bufsize=-1, executable=None,stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=())
  • 8.
    なんでこんなキッチンシンクに? ● キッチンシンク←なんでもかんでも放り込むたとえ ● fork と exec の間に「いろいろできる」(リダイレクトと か)のを、全部引数で指示するから ● 計算機を仮想化する単位であるプロセスを切り離す 作業だから、仮想化してあるものをここで全部(ファイ ルハンドラとか)面倒を見なきゃならないから
  • 9.
    ひとつ注意 preexec_fn exec の前に実行される関数引数。なんでも指定できる が、可能な限り余計なことはしてはいけない。英語版ドキュ メントには赤背景で以下のように書いてある。 Warning: Thepreexec_fn parameter is not safe to use in the presence of threads in your application. The child process could deadlock before exec is called. If you must use it, keep it trivial! Minimize the number of libraries you call into. これは libc に由来するスレッド絡みの制限で、実は厄介。
  • 10.
    ユースケース ● 標準ライブラリのドキュメントと PEP 324 から ● 以降は from subprocess import * してある 前提で
  • 11.
    シェルスクリプトのコマンド展開 result=`ls -l myfile` result= check_output([”ls”, ”-l”, ”myfile”])
  • 12.
    パイプラインありの展開 result=`dmesg | grephda` p1 = Popen(["dmesg"], stdout=PIPE) p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) p1.stdout.close() result = p2.communicate()[0] (コマンド文字列インジェクションの心配がないなら) result = check_output("dmesg | grep hda", shell=True)
  • 13.
    os.system の代替 st =os.system(”cmd arg”) st = call(”cmd”, ”arg”, shell=True) (シェル経由で呼ぶのはsystemというAPIの特 徴。シェルが必要ないなら使わない)
  • 14.
    os.spawn の代替 pid =os.spawnlp(os.P_NOWAIT, "/bin/cmd", "cmd", "arg") pid = Popen(["/bin/cmd", "arg"]).pid ret = os.spawnlp(os.P_WAIT, "/bin/cmd", "cmd", "arg") ret = call(["/bin/cmd", "arg"]) os.spawnvp(os.P_NOWAIT, path, args) Popen([path] + args[1:]) os.spawnlpe(os.P_NOWAIT, "/bin/cmd", "cmd", "arg", env) Popen(["/bin/cmd", "arg"], env={"PATH": "/usr/bin"})
  • 15.
    os.popen2 (child_stdin, child_stdout) = os.popen2(cmd, mode, bufsize) p = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE, stdout=PIPE, close_fds=True) (child_stdin, child_stdout) = (p.stdin, p.stdout)
  • 16.
    os.popen3 (child_stdin, child_stdout, child_stderr) = os.popen3(cmd,mode, bufsize) p = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) (child_stdin, child_stdout, child_stderr) = (p.stdin, p.stdout, p.stderr)
  • 17.
    os.popen4 (child_stdin, child_stdout_and_stderr) = os.popen4(cmd, mode, bufsize) p = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) (child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout)
  • 18.
    popen2 モジュール (child_stdout, child_stdin)= popen2.popen2("somestring", bufsize, mode) p = Popen(["somestring"], shell=True, bufsize=bufsize, stdin=PIPE, stdout=PIPE, close_fds=True) (child_stdout, child_stdin) = (p.stdout, p.stdin)
  • 19.
    まとめ ● subprocess モジュールは、サブプロセスを作る既存 の API 全てを代替する ● 全てを代替するために、subprocess の API は大 変なことになっている ● ケーススタディをマニュアルから紹介した