print文は最後に改行してくれるので面倒が無くて便利だけど、改行してほしくないときもある。そういう時は sys.stdout.write() が使える。
>>> import sys >>> sys.stdout.write("Hello, world.") Hello, world.>>>
改行されていないのがわかる。
takatoh's blog – Learning programming languages.
print文は最後に改行してくれるので面倒が無くて便利だけど、改行してほしくないときもある。そういう時は sys.stdout.write() が使える。
>>> import sys >>> sys.stdout.write("Hello, world.") Hello, world.>>>
改行されていないのがわかる。
sys.stderr.write() を使う。
import sys sys.stderr.write("Error: some error.\n")
実行例:
^o^ > python stderr.py Error: some error. ^o^ > python stderr.py > output.txt Error: some error. ^o^ > type output.txt ^o^ > python stderr.py 2> error.txt ^o^ > type error.txt Error: some error.
もうすぐ2.1.0も出そうだというのに、いまさらながら2.0.0にバージョンアップした。
使うのはいつものとおり、One-Click Ruby Installer for Windows。ダウンロードページから、64bit版をダウンロードした。
cf. http://rubyinstaller.org/downloads/
インストール自体はいたって簡単なので省略。あらかじめgemもいくつかインストールされていた。
^o^ > ruby -v ruby 2.0.0p353 (2013-11-22) [x64-mingw32] ^o^ > gem list *** LOCAL GEMS *** bigdecimal (1.2.0) io-console (0.4.2) json (1.7.7) minitest (4.3.2) psych (2.0.0) rake (0.9.6) rdoc (4.0.0) test-unit (2.0.0.0)
ついでなので、Development Kit も入れることにする。同じページから64bit版の DevKit-mingw64-64-4.7.2-20130224-1432-sfx.exe をダウンロード。自己解凍形式のexeファイルなので、C:\DevKit に解凍した。で、あとはインストール作業。次のようにした。
^o^ > cd c:\DevKit ^o^ > ruby dk.rb init [INFO] found RubyInstaller v2.0.0 at C:/Ruby200-x64 Initialization complete! Please review and modify the auto-generated 'config.yml' file to ensure it contains the root directories to all of the installed Rubies you want enhanced by the DevKit. ^o^ > ruby dk.rb install [INFO] Updating convenience notice gem override for 'C:/Ruby200-x64' [INFO] Installing 'C:/Ruby200-x64/lib/ruby/site_ruby/devkit.rb'
これでOKかな。
※ 追記あり
argparse モジュールを使っているうち、気になった、というか気に入らないことがあったのでメモしておく。
とりあえず、こんなスクリプトがあったとする。
import argparse parser = argparse.ArgumentParser(description='Hello world program.') parser.add_argument('name', metavar='NAME', action='store', help='specify name instead "world".') parser.add_argument('-m', '--morning', dest='greeting', action='store_const', const='Good morning', default='Hello', help='good morning') parser.add_argument('-t', '--times', dest='times', type=int, action='store', default=1, metavar='N', help='repeat N times') args = parser.parse_args() for n in range(args.times): print '%s, %s.' % (args.greeting, args.name)
これは以前 argparse を試してみたときのスクリプトにちょっと変更を加えたもので、どこを変えたかというと、位置引数 NAME のデフォルト値をなくして必須の引数にした点だ。
^o^ > python hello2.py -h usage: hello2.py [-h] [-m] [-t N] NAME Hello world program. positional arguments: NAME specify name instead "world". optional arguments: -h, --help show this help message and exit -m, --morning good morning -t N, --times N repeat N times ^o^ > python hello2.py Andy Hello, Andy. ^o^ > python hello2.py -m Andy Good morning, Andy. ^o^ > python hello2.py -m -t 3 Andy Good morning, Andy. Good morning, Andy. Good morning, Andy. ^o^ > python hello2.py usage: hello2.py [-h] [-m] [-t N] NAME hello2.py: error: too few arguments
最後の実行例のように、NAME 引数を省略するとエラーになる。これは期待通りの動作。
で、もうひとつ覚えておいてほしいのは一番最初の -h オプションを指定した実行例。NAME 引数が無いにもかかわらずエラーにならずにヘルプが表示されている。これも期待通りの動作だ。
さて、本題はこれから。一通りの機能を実装し終わったら(そしてこれからも機能を拡張するつもりがあるなら)、スクリプトのバージョンを表示するオプションを追加したくなる。
というわけで、バージョンを表示する -v (–version) オプションを追加したのがこれだ。
import argparse script_version = "v0.1.0" parser = argparse.ArgumentParser(description='Hello world program.') parser.add_argument('name', metavar='NAME', action='store', help='specify name instead "world".') parser.add_argument('-v', '--version', dest='version', action='store_true', help='show version and exit') parser.add_argument('-m', '--morning', dest='greeting', action='store_const', const='Good morning', default='Hello', help='good morning') parser.add_argument('-t', '--times', dest='times', type=int, action='store', default=1, metavar='N', help='repeat N times') args = parser.parse_args() if args.version: print script_version exit() for n in range(args.times): print '%s, %s.' % (args.greeting, args.name)
ところが、これは期待通りには動作しない。
^o^ > python hello2a.py -v usage: hello2a.py [-h] [-v] [-m] [-t N] NAME hello2a.py: error: too few arguments
NAME 引数が無いために、引数が足りないと怒られてしまう。
この問題は、NAME 引数を定義するところで、nargs=’?’ とすることでとりあえず回避できる。
import argparse script_version = "v0.1.0" parser = argparse.ArgumentParser(description='Hello world program.') parser.add_argument('name', metavar='NAME', nargs='?', action='store', help='specify name instead "world".') parser.add_argument('-v', '--version', dest='version', action='store_true', help='show version and exit') parser.add_argument('-m', '--morning', dest='greeting', action='store_const', const='Good morning', default='Hello', help='good morning') parser.add_argument('-t', '--times', dest='times', type=int, action='store', default=1, metavar='N', help='repeat N times') args = parser.parse_args() if args.version: print script_version exit() for n in range(args.times): print '%s, %s.' % (args.greeting, args.name)
^o^ > python hello2b.py -v v0.1.0
が、一方でこれは別の問題を引き起こす。NAME 引数が無くてもスクリプトが動いてしまうのだ。これは期待している動作とは違う。
^o^ > python hello2b.py Hello, None.
NAME 引数がない場合はちゃんとエラーになってほしい。でもって -v オプションを指定したときには NAME 引数が無くてもエラーになってほしくない。思い出してほしいが -h オプションはまさにそのように動作している。どうにかして期待通りに動作するようにはできないものだろうか。argparse のドキュメントを見たもののどうも解決策はなさそうに見える。
cf. http://docs.python.jp/2/library/argparse.html
で、結局、NAME 引数が指定されているかどうかを自前でチェックするようにした。
import argparse script_version = "v0.1.0" parser = argparse.ArgumentParser(description='Hello world program.') parser.add_argument('name', metavar='NAME', nargs='?', action='store', help='specify name instead "world".') parser.add_argument('-v', '--version', dest='version', action='store_true', help='show version and exit') parser.add_argument('-m', '--morning', dest='greeting', action='store_const', const='Good morning', default='Hello', help='good morning') parser.add_argument('-t', '--times', dest='times', type=int, action='store', default=1, metavar='N', help='repeat N times') args = parser.parse_args() if args.version: print script_version exit() if args.name is None: print parser.prog + ": error: too few arguments. type " + parser.prog + " -h for help" exit() for n in range(args.times): print '%s, %s.' % (args.greeting, args.name)
これで期待通り。
^o^ > python hello2c.py -v v0.1.0 ^o^ > python hello2c.py hello2c.py: error: too few arguments. type hello2c.py -h for help
でもやっぱり、argparse で何とかできてほしいなあ。
[追記]
argparse のドキュメントをよくよく読んでみたら、やっぱり解決策があった。
add_argument() の引数に、action=”version” と version=バージョンナンバー を指定すればいい。直したのがこれ:
import argparse script_version = "v0.1.1" parser = argparse.ArgumentParser(description='Hello world program.') parser.add_argument('name', metavar='NAME', action='store', help='specify name instead "world".') parser.add_argument('-v', '--version', action='version', version=script_version, help='show version and exit') parser.add_argument('-m', '--morning', dest='greeting', action='store_const', const='Good morning', default='Hello', help='good morning') parser.add_argument('-t', '--times', dest='times', type=int, action='store', default=1, metavar='N', help='repeat N times') args = parser.parse_args() for n in range(args.times): print '%s, %s.' % (args.greeting, args.name)
実行結果:
^o^ > python hello2d.py -h usage: hello2d.py [-h] [-v] [-m] [-t N] NAME Hello world program. positional arguments: NAME specify name instead "world". optional arguments: -h, --help show this help message and exit -v, --version show version and exit -m, --morning good morning -t N, --times N repeat N times ^o^ > python hello2d.py -v v0.1.1 ^o^ > python hello2d.py Andy Hello, Andy. ^o^ > python hello2d.py usage: hello2d.py [-h] [-v] [-m] [-t N] NAME hello2d.py: error: too few arguments
Python で YAML を読み書きするには、PyYAML というパッケージがある。
というわけで、忘れないようにメモ。
インストールは、ほかのパッケージと同様、pip でインストールできる。
^o^ > pip install pyyaml Downloading/unpacking pyyaml Downloading PyYAML-3.10.tar.gz (241kB): 241kB downloaded Running setup.py egg_info for package pyyaml Installing collected packages: pyyaml Running setup.py install for pyyaml checking if libyaml is compilable Unable to find vcvarsall.bat skipping build_ext Successfully installed pyyaml Cleaning up...
まずはサンプルのYAMLファイルを用意する。
^o^ > type sample_dict.yaml Python: Guido Ruby: Matz Perl: Larry
yaml.load関数を使う。
>>> import yaml >>> f = open('sample_dict.yaml', 'r') >>> data = yaml.load(f) >>> f.close() >>> data {'Python': 'Guido', 'Ruby': 'Matz', 'Perl': 'Larry'} >>> type(data) <type 'dict'>
ちゃんと辞書型のデータになっているのがわかる。ファイルオブジェクトを作らなきゃいけないのが Ruby と比べてちょっと面倒だな。
あ、それから、インポートするのは、pyyaml じゃなくて yaml ね。
書き出すときには、yaml.dump関数。文字列が返ってくるので、これもやっぱりファイルオブジェクトを使ってファイルに書き出す。
>>> f = open('sample_dict2.yaml', 'w') >>> f.write(yaml.dump(data)) >>> f.close() >>> exit() ^o^ > type sample_dict2.yaml {Perl: Larry, Python: Guido, Ruby: Matz}
ふむ、辞書(YAMLではマップって言うんだっけ?)のフォーマットが読み込んだファイルとは違うな。PyYAMLではこういうフォーマットにしかならないんだろうか。
リストでも試してみよう。
^o^ > type sample_list.yaml - Python - Ruby - Perl
読み込み:
>>> import yaml >>> f = open('sample_list.yaml', 'r') >>> data = yaml.load(f) >>> f.close() >>> data ['Python', 'Ruby', 'Perl'] >>> type(data) <type 'list'>
書き出し:
>>> f = open('sample_list2.yaml', 'w') >>> f.write(yaml.dump(data)) >>> f.close() >>> exit() ^o^ > type sample_list2.yaml [Python, Ruby, Perl]
やっぱりフォーマットが違う。
まあ、データの量とかも関係があるのかも。何百行にもなるようなリストを上のようなフォーマットで出力されたら見難くてしょうがない。
urllib とか BeautifulSoupでのスクレイピングとかの練習を兼ねて作ってみた。何に使うかは想像にお任せする;p
# coding: utf-8 import urllib import re import argparse import os from BeautifulSoup import BeautifulSoup script_version = 'v0.0.1' re_image = re.compile(".+\.(jpg|png|gif)") def get_linked_images(soup): for a in soup("a"): for i in a("img"): a2 = i.parent if re_image.match(a2["href"]): image = a2["href"] file = url_to_filename(image) try: print image urllib.urlretrieve(image, file) except IOError: pass def get_embeded_images(soup): for i in soup("img"): image = i["src"] if re_image.match(image): file = url_to_filename(image) try: print image urllib.urlretrieve(image, file) except IOError: pass def url_to_filename(url): filename = url.split('/')[-1] filename = re.sub('\?.+', '', filename) if args.dir: filename = os.path.join(args.dir, filename) return filename parser = argparse.ArgumentParser(description="Download images from web page.") parser.add_argument('url', metavar='URL', nargs='?', action='store', help='specify URL.') parser.add_argument('-v', '--version', dest='version', action='store_true', help='show version and exit') parser.add_argument('-e', '--embeded-image', dest='embeded', action='store_true', help='download embeded images(default)') parser.add_argument('-l', '--linked-image', dest='linked', action='store_true', help='download linked images') parser.add_argument('-d', '--dir', dest='dir', metavar='DIR', action='store', help='download into DIR') args = parser.parse_args() if args.version: print script_version exit() url = args.url if args.dir: os.makedirs(args.dir) print "Download images from " + url + "\n" res = urllib.urlopen(url).read() soup = BeautifulSoup(res) if args.linked: get_linked_images(soup) else: get_enbeded_images(soup)
ようするに、この間の逆をやろうってことだ。
cf. 連続する数列をハイフンでまとめるPythonスクリプト – blog.panickblanket.com
import re def expand_num(s): a = re.split(", *", s) r = re.compile("\d+-\d+") def f(s1): a, b = map(int, s1.split('-')) return range(a, b+1) def g(x): if r.match(x): return f(x) else: return [int(x)] return reduce(lambda x, y: x + g(y), a, []) if __name__ == '__main__': print expand_num("1-3") print expand_num("1-3, 5, 7-8") print expand_num("1, 3-5, 7")
実行結果:
^o^ > python expand_num.py [1, 2, 3] [1, 2, 3, 5, 7, 8] [1, 3, 4, 5, 7]
連続する数列をハイフンでまとめるシェルスクリプト – ザリガニが見ていた…。を見て、面白いことをやっているので Python でやってみた。
cf. Rubyでどう書く?:連続した数列を範囲形式にまとめたい – builder by ZDNet Japan (元記事)
def diff(a): return map(lambda x, y: x - y, a[1:], a[:-1]) def hyphenate_num(s): a = map(lambda x: int(x), s.split()) d = diff(a) b = map(lambda x, y: (x, y), a, d) r = [] flg = False for x in b: if x[1] > 1: r.append(str(x[0])) r.append(', ') flg = False elif x[1] == 1: if not flg: r.append(str(x[0])) r.append('-') flg = True else: r.append(str(x[0])) return ''.join(r) if __name__ == '__main__': print hyphenate_num("1 2 3") print hyphenate_num("1 2 3 5 7 8") print hyphenate_num("1 3 4 5 7")
実行結果:
^o^ > python hyphenate_num.py 1-3 1-3, 5, 7-8 1, 3-5, 7
あんまりきれいなコードじゃないけどできた。やっぱりreduceを使うのほうがいいのかなあ。
理由はどうでもいいんだけど、URLが大量に書かれたファイルがあって、そのひとつひとつをブラウザでチェックすることになった。で、ひとつずつコピペするのなんてかったるくってやってられないわけで、Pythonでスクリプトを書いてみた。
URLのリストはたとえばこんな感じ(実際はもっといっぱいある)。
^o^ > type url.txt https://www.ruby-lang.org/ja/ http://www.python.jp/ http://www.perl.org/ http://www.haskell.org/ http://ocaml.jp/
スクリプトはこんなの。htmlを作るテンプレートエンジンには、Jinja2を使った。
import sys from jinja2 import Environment, DictLoader templates = {'index.html': """ <ul> <li style="list-style-type: none;"> <ul>{% for url in urls %}</ul> </li> </ul> <ul> <li style="list-style-type: none;"> <ul> <li><a href="{{ url }}">{{ url }}</a></li> </ul> </li> </ul> {% endfor %} """ } filename = sys.argv[1] f = open(filename, 'r') urls = [] for line in f: urls.append(line.rstrip('\n')) f.close env = Environment(loader=DictLoader(templates)) tmpl = env.get_template('index.html') html = tmpl.render(urls=urls) print html
Jinja2の使い方はここらへんを参考にした。
cf. Jinja2 利用ノート
cf. http://jinja.pocoo.org/docs/api/#loaders
上のサイトでは、テンプレートローダーにFileSystemLoaderを使って説明されているけど、今回はいくつものテンプレートを使うわけではないし、テンプレート自体も簡単なものなので、DictLoaderを使っている。
スクリプトはこんな風に使う。
^o^ > python genlinks.py url.txt > index.html
できた index.html がこれ。
<html> <body> <ul> <li><a href="https://www.ruby-lang.org/ja/">https://www.ruby-lang.org/ja/</a></li> <li><a href="http://www.python.jp/">http://www.python.jp/</a></li> <li><a href="http://www.perl.org/">http://www.perl.org/</a></li> <li><a href="http://www.haskell.org/">http://www.haskell.org/</a></li> <li><a href="http://ocaml.jp/">http://ocaml.jp/</a></li> </ul> </body> </html>
なんか余計な改行が入ってるけど、まあいいか。
先日のお題を、今度は Haskell でやってみた。Haskell はだいぶ忘れてるな。
乱数の使い方は↓ここを参考にした。
module Main where import System.Environment ( getArgs ) import System.Random import Control.Monad strPool :: String strPool = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" lenPool :: Int lenPool = length strPool - 1 randomStr :: Int -> IO String randomStr n = do lis <- replicateM n $ (getStdRandom $ randomR (0, lenPool) :: IO Int) return $ map (\ x -> strPool !! x) lis main :: IO () main = do argv <- getArgs let n = read $ head argv randStr <- randomStr n putStrLn randStr
実行結果:
^o^ > runhaskell randomString.hs 20 GhDADFMuNNxrUBbpMXw3