オタク日記
(Mac と Linux, 2015Q3)
目次
2015-10-09 (Fri): Django の進化 (その 3)—mod_wsgi2015-09-26 (Sat): Django の進化 (その 2)—DB I/F
2015-09-19 (Sat): Django の進化 (その 1)—Time Zone 再訪
2015-09-16 (Wed): pyvenv と readline
2015-09-12 (Sat): Pip で pyvenv
2015-08-29 (Sat): Pip-tools の様変わり
2015-08-22 (Sat): 重い Python ……
古い日記:
2015Q2
2015Q1
2014Q4
2014Q3
2014Q2
2014Q1
2013Q4
2013Q3
2013Q2
2013Q1
2012 年
2011 年
2010 年
2009 年
2008 年
2007 年
2006 年
2005 年
2004 年
2003 年
2002 年
2001 年
2015-10-09 (Fri): Django の進化 (その 3)—mod_wsgi
これは、Django そのものの進化ではないが、私にとっては、mod_wsgi は 「Django 専用」みたいなものなので…… (私と同じように)そもそも WSGI (Web Server Gateway Interface) って何?という人は、 PEP-3333 を御覧下さい(Python のドキュメントの常ながら、とってもよくできています。) しかし、かつてはこれ (mod_wsgi) はかなり使い辛いものだった——一年前にも 相当難儀したのを憶えている。 この時の「なんだかなぁ」は、つまるところ- Python 3k 未対応
- dev-server と、static files (css, java, img, etc) の扱いが全く違う。
- Apache の側で WSGI 対応にしないといけない(httpd.conf との格闘が必要という事)
但し、Django と一緒に使うに当っては、まだいくつかの「考慮」が必要なようだ。 (真の「必要条件」かどうかは確認していない。)
- Static files を置くディレクトリは「標準」のものを使う:
要は
tts/static/tts/{css|img|js}/
のようにする、という事。('tss' は Django application の名前。) 実は 'tss' が重なるを嫌って後の方を省いていた事があるが、これは話をとても面倒にする。 - db.sqlite3 と、その直上のディレクトリの group は Apache デーモンの group にする必要がある……(OSX MacPorts では、'_www', Ubuntu では 'www-data')
- dev_server は DEBUG=True で、mod_wsgi + Apache は DEBUG=False で: 逆の組合せは原理的に不可能ではない筈だが、開くポートが well-known かどうか(すな わち、起動するのに root privilege が必要かどうか)と相俟って、なかなかうまく行かない。
Wsgi 専用の settings.py を作る
最後の「考慮」はなかなか難物で、かなり四苦八苦させられたが、 今のところ、次のような「策」で満足している。- settings.py:
django-admin startproject my_project
でできるディフォルトのファイル。これは、DEBUG = True のままで、dev_server 専用とし、必要な他の設定をする。 - mod_settings.py: 上の settings.py のコピーを編集して、
DEBUG = False とし、mod_wsgi 専用の settings とする。肝心な差分は
(pvenv) fukuda@tts5:~/pvenv/wrm_cwt/mysite$ diff mod_settings.py settings.py 26c26 < DEBUG = False --- > DEBUG = True 28c28 < ALLOWED_HOSTS = ['localhost', 'ubuntu', 'falcon', 'tts2', 'tts3', 'tts4', 'tts5', 'tts6'] --- > # ALLOWED_HOSTS = ['localhost', 'ubuntu', 'falcon'] .....
ここで、ALLOWED_HOSTS は uncomment するだけでなく、 実際にこのアプリケーションが動いているホストの hostname が含まれていないといけない。 - wsgi.py: これも、'startproject' の際に作られる mysite/wsgi.py を編集して、
import os from django.core.wsgi import get_wsgi_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.mod_settings") application = get_wsgi_application()
のようにする。('mysite.settings' を 'mysite.mod_settings' にした。)
Mod_wsgi のインストール
これまで難航したので今回もしっかり身構えて取り掛かったが、 どうもはなから様子が違う…… 最新版が PyPI に有るってのもさる事ながら、それより、mod_wsgi を Apache ではなくて「Python にインストールする」ってのが驚き。(何だか、「Apache の module」っていうイメージがまるっきり間違っていたような気がしてきた。)
fukuda@tts5:~$ sudo apt-get install apache2-mpm-prefork apache2-threaded-dev (pvenv) fukuda@tts5:~/pvenv$ nano requirements.in (pvenv) fukuda@tts5:~/pvenv$ cat requirements.in django ..... mod-wsgi # new ..... (pvenv) fukuda@tts5:~/pvenv$ pip-compile (pvenv) fukuda@tts5:~/pvenv$ pip-syncとするだけ。
これで、Django の devserver が動くのを確認できている Django project へ行ってから、次のようにする:
(pvenv) fukuda@tts5:~/pvenv/wrm_cwt$ python manage.py collectstatic #1) (pvenv) fukuda@tts5:~/pvenv/wrm_cwt$ python manage.py runmodwsgi #2) Successfully ran command. Server URL : http://localhost:8000/ Server Root : /tmp/mod_wsgi-localhost:8000:1001 Server Conf : /tmp/mod_wsgi-localhost:8000:1001/httpd.conf #3) Error Log File : /tmp/mod_wsgi-localhost:8000:1001/error_log (warn) Request Capacity : 5 (1 process * 5 threads) #4) Request Timeout : 60 (seconds) Queue Backlog : 100 (connections) Queue Timeout : 45 (seconds) Server Capacity : 20 (event/worker), 20 (prefork) Server Backlog : 500 (connections) Locale Setting : en_US.UTF-8ここで
- #1) Apache が static file を扱えるようにするために、一箇所に集める
- #2) これで、まるで dev-server が走るように、Apache を起動できる
- #3) httpd.conf を自動生成している(新しい mod_wsgi の最大のメリット)
- #4) 望み通り multi-threading が実現されている
http://localhost:8000
にアクセスして確認する事ができる。
Root 権限で Apache を走らせる
Port:80 で待ち受けるように設定した Apache を起動するには root 権限が必要になる。(pvenv) fukuda@tts5:~/pvenv/wrm_cwt$ sudo ../bin/python manage.py runmodwsgi \ --port=80 --user www-data --group www-data Successfully ran command. Server URL : http://localhost/ Server Root : /tmp/mod_wsgi-localhost:80:0 Server Conf : /tmp/mod_wsgi-localhost:80:0/httpd.conf Error Log File : /tmp/mod_wsgi-localhost:80:0/error_log (warn) Request Capacity : 5 (1 process * 5 threads) Request Timeout : 60 (seconds) Queue Backlog : 100 (connections) Queue Timeout : 45 (seconds) Server Capacity : 20 (event/worker), 20 (prefork) Server Backlog : 500 (connections) Locale Setting : en_US.UTF-8sudo では、元の PATH を引き継がないので pvenv の python を指定するには 明示的に
../bin/python
とする必要がある。また、--user,
--group の指定も必須(security からの要求?)
Mod_wsgi/Apache をデーモンに
上の #3) の「httpd.conf の自動生成」も大したものであるが、 このディレクトリを恒久的なものにして、daemon にする方法も準備されている。 (いや、もう、感心するのみ。) 上のコマンドに--server-root=
として、サーバの root
directory を指定し、さらに --setup-only オプションを付け加えるだけ。
(pvenv) fukuda@tts5:~/pvenv/wrm_cwt$ sudo ../bin/python manage.py runmodwsgi \ --port=80 --user www-data --group www-data \ --server-root=/etc/mod_wsgi-express-80 --setup-only Successfully ran command. Server URL : http://localhost/ Server Root : /etc/mod_wsgi-express-80 Server Conf : /etc/mod_wsgi-express-80/httpd.conf #1) Error Log File : /etc/mod_wsgi-express-80/error_log (warn) Environ Variables : /etc/mod_wsgi-express-80/envvars Control Script : /etc/mod_wsgi-express-80/apachectl #2) Request Capacity : 5 (1 process * 5 threads) ..... Locale Setting : en_US.UTF-8ここで、
- #1) /tmp の代りに /etc に「恒久的」な、root directory が作られる。
- #2) apachectl まで作ってくれるので、以降はこれを使って daemon を起動できる。(このコマンドは飽く迄 --setup-only なので、daemon の起動はしない。)
(pvenv) fukuda@tts5:~/pvenv/wrm_cwt$ sudo /etc/mod_wsgi-express-80/apachectl start (pvenv) fukuda@tts5:~/pvenv/wrm_cwt$ ps ax | grep httpd 19409 ? Ss 0:00 apache2 (mod_wsgi-express) -f /etc/mod_wsgi-express-80/httpd.conf -DMOD_WSGI_MPM_ENABLE_EVENT_MODULE -DMOD_WSGI_MPM_EXISTS_EVENT_MODULE -DMOD_WSGI_MPM_EXISTS_WORKER_MODULE -DMOD_WSGI_MPM_EXISTS_PREFORK_MODULE -k start 19410 ? Sl 0:00 (wsgi:localhost:80:0) -f /etc/mod_wsgi-express-80/httpd.conf -DMOD_WSGI_MPM_ENABLE_EVENT_MODULE -DMOD_WSGI_MPM_EXISTS_EVENT_MODULE -DMOD_WSGI_MPM_EXISTS_WORKER_MODULE -DMOD_WSGI_MPM_EXISTS_PREFORK_MODULE -k start 19411 ? Sl 0:00 apache2 (mod_wsgi-express) -f /etc/mod_wsgi-express-80/httpd.conf -DMOD_WSGI_MPM_ENABLE_EVENT_MODULE -DMOD_WSGI_MPM_EXISTS_EVENT_MODULE -DMOD_WSGI_MPM_EXISTS_WORKER_MODULE -DMOD_WSGI_MPM_EXISTS_PREFORK_MODULE -k startこれで、
http://localhost/tts/
(localhost から)および
http://tts5/tts/
(local network から)
でアクセスできる事を確認した。
2015-09-26 (Sat): Django の進化 (その 2)—DB I/F
Database を使うには、SQL をもっと知らねば、と思った事も有ったが、 Django からアクセスする限り、それもあまり必要ないようだ——甘いかな。
Query Set
再び、次のような cwt/models.py (つまり、table の構成)を前提にする。# cwt/models.py from django.db import models from timezone_field import TimeZoneField .... class Area(models.Model): ..... area_name = models.CharField(max_length=80, unique=True) time_zone = TimeZoneField(default='UTC') ..... def __str__(self): return "{0}".format(self.area_name) class Party(models.Model): ..... started_on = models.DateTimeField(blank=True, null=True) finished_on = models.DateTimeField(blank=True, null=True) area = models.ForeignKey(Area, null=True, blank=True) ..... def __str__(self): return self.party_nameこのデータベースにアクセスするには、
import wct.models import Aerea, Party area1 = Area.objects.get(area_name="Kitayatsu") party = Party.objects.create(area=area, party_name="Kitadake Sangakukai") party.started_on = timezone.now() party.save() .....とするのが基本。
ClassName.objects.{get|create}()
が key
であるが、これらはいずれも、その object を返す。
ここで、ClassName.objects.{all|filter}()
とすると、
単なる object ではなく、その list (のようなもの) である query set を返す。
これはとても便利に扱えて、例えば
parties = Party.objects.filter(area=area1) #1) party_active = parties.filter(finished_on=None) #2) party_newest = parties.order_by('registered_on').last() #3) for party in parties: #4) party.finished_on = timezone.now() party.save()ここで、
- #1) Party class の全体から、area == area1 となる object の set (query set) を返す。
- #2)
parties
は query set なので、さらにフィルタをかける事もできる。 - #3) ソートも可能。その結果も query set で、さらに例えば、
last()
(最後の要素を取り出す)というメソッドを適用できる。 - #4) query set は for loop のイテレータとしても使える。
ClassName.objects.get()
の代りに
ClassName.objects..filter()
を使う事のメリットと注意事項としては……
- 「例外」をあまり心配しなくて良い:
Xxxx.objects.get()
では、条件に適合する要素の数が、0 であっても、2 以上であっても例外が発生する。一方、Xxxx.objects.filter()
だと、 query set に、0 以上のオブジェクトが入るだけで、例外は発生しない。 但し、空の query set に parties[0] のようにアクセスしようとすると、 IndexError 例外が発生する。 - query set は、負のインデックスを取らない: 上の #3)
で、
qs.last()
とする代りにqs[-1]
としても良さそうなものだが、これは受入れられない。 - qs.first(), qs.last() は便利:
上の項と関連して作られたと思うのだが、これらのメソッドはとても便利。
qs[-1]
の代りに使える、という事の他に、もし、qs
が空でも例外が発生しない——代りに None を返す。 但し、その属性にアクセスしようとするとエラーになるので、 その場合はさすがにqs.last()
が None であるかどうかのチェックは必要。
Migrate/Make Migration
上述のように、Django を使うと非常にスマートに DB へアクセスできる。 しかし、DB の構造(テーブルカラム?)の変更は、なかなかそうは行かない。 かつては、その DB の種類に応じた、SQL コマンドで「変更」の内容を記述する必要が有った。 これは SQL をよく知らない私にとっては「鬼門」で、 散々悩まされたあげく、models.py でのクラス定義は、 設計の段階でよく考え、もし迷ったらアトリビュート (field) はとりあえず付けておく、なんて事をしていた。しかし、Django の開発者達もさすがにこれでは可哀想だと思ったのか (Django-1.5 の頃から?)このステップを自動化する方策を考え出してくれた。 例えば、cwt/models.py の Party class に次のように area というフィールドを付け加えたとすると、
class Party(models.model): ..... area = models.ForeignKey(Area, null=True, blank=True) .....manage.py を使って
(PyVenv) fukuda@falcon:~/PyVenv/wrm_cwt% python manage.py makemigrations cwt Migrations for 'cwt': 0003_party_area.py: - Add field area to party (PyVenv) fukuda@falcon:~/PyVenv/wrm_cwt% python manage.py migrate cwt Operations to perform: Apply all migrations: cwt Running migrations: Rendering model states... DONE Applying cwt.0003_party_area... OKというように、自動で、migration-file を生成し、それを使って DB の変更を実施してくれる。
おおー簡単になったなぁ、目出度し、目出度し……だと良いのだが、 実はこれ、いつもうまく行くとは限らない。 どんな時失敗するかについては確実な事は分ってないが、今のところ、
- 一度に沢山のフィールドを変更した
- 変更の中に文法的に正しくないものが有り、 一旦エラーとなって遣り直した
- 変更が ForeignKey フィールドとその前後を含む
そのような場合、対策としては、
-
cwt/migrations
以下を全部消して、再度 migrations file を作るところから始める。 -
models.py
で追加したフィールドを一旦コメントアウトし、 makemigrations/migrate を実行、 その後またアンコメントして、再度 makemigrations/migrate を実行。
Git との併用
弊社では git を活用して共同開発をしている……と言いたいところだが、 実はまだそこまで行ってなくて、 テストのために、git でサーバの clone を幾つかのプラットフォームに作っているだけ。
これは、まことに具合が良い……が、Django の場合は db.sqlite3
を、tracking file にするかどうか、という問題があった。
要は、テスト用のサイトでは、ソースを触る事は稀だが、
db.sqlite3
だけは試験している間にどんどん変わる。
remote/local 両方の reposite で変更があると勿論の事ながら git pull
だけでは同期できない。
あと、local settings も、元の reposite と完全に同期して欲しくない。
随分試行錯誤したが、今のところ db.sqlite3
(双方でしょっちゅう変わる)
と local_settings.py
(サイト固有の設定をする)については、
sync_db.sqlite3
と sync_local_settings.py
というファイルを作る事で凌いでいる。つまり……
fukuda@lark:/var/lib/git/wrm_cwt.git% sudo git init --bare fukuda@lark:/var/lib/git/wrm_cwt.git% sudo chown -R gitdaemon:root ..として作った remote repository に対し、開発しているサイトで
fukuda@falcon:~/PyVenv/wrm_cwt% cp cwt/local_settings.py cwt/sync_local_settings.py fukuda@falcon:~/PyVenv/wrm_cwt% cp db.sqlite3 sync_db.sqlite3 fukuda@falcon:~/PyVenv/wrm_cwt% git add sync_db.sqlite3 fukuda@falcon:~/PyVenv/wrm_cwt% git add cwt/sync_local_settings.py fukuda@falcon:~/PyVenv/wrm_cwt% git commit -a -m "first release" fukuda@falcon:~/PyVenv/wrm_cwt% git remote add origin git://otacky.jp/wrm_cwt.git fukuda@falcon:~/PyVenv/wrm_cwt% git push origin masterとする。(
local_settings.py
と db.sqlite3
は
.git-ignore
に入っている、と前提。) 公開するサイトで
fukuda@ubuntu:~/pvenv% git clone git://otacky.jp/wrm_cwt.git fukuda@ubuntu:~/pvenv% cd wrm_cwt fukuda@ubuntu:~/pvenv/wrm_cwt% git clone git://otacky.jp/wrm_cwt.git fukuda@ubuntu:~/pvenv/wrm_cwt% cp sync_db.sqlite3 db.sqlite3 fukuda@ubuntu:~/pvenv/wrm_cwt% cp tts/sync_local_settings.py tts/local_settings.pyこの時点で、
tts/local_settings.py
,
mysite/settings.py
を編集。
その後試験を実施し、その後ソースを update するには
fukuda@ubuntu:~/pvenv/wrm_cwt% git pull
とする。もし、データベースそのものを、更新するなら
fukuda@ubuntu:~/pvenv/wrm_cwt% cp sync_db.sqlite3 db.sqlite3
とする。(ubuntu
で集積したデータは失われる)
2015-09-19 (Sat): Django の進化 (その 1)—Time Zone 再訪
「進化」と言っても、何しろ比較の対象が、Django-0.9x の頃なんだから、 「Django が進化した」というより「ようやく自分に理解できかけた」というのが正しい……前にも書いたが、当初「少々ドラスティック過ぎでは」と思われた
- Database の datetime は 'timezone aware' の UTC 一本で
- 表示の際にのみlocal time zone を使う
なので、「逆引き風」に要点を纏めておく。
設定・定義
新環境を作る時など、意外に忘れがちだが、これをやっておかないと、 以下の説明は全く無意味となる。# mysite/settings.py INSTALLED_APPS = ( 'django.contrib.admin', ..... 'timezone_field', ..... ) # TIME_ZONE = 'UTC' TIME_ZONE = 'Asia/Tokyo' #1) ..... USE_TZ = True #2) .....
- #1) local_time のディフォルトの TZ となる。
- #2) これを True にする事で、datetime object が TZ-aware になる。
一方、modles(つまり、テーブル)の定義の方は
# cwt/models.py from django.db import models from timezone_field import TimeZoneField #3) .... class Area(models.Model): ..... area_name = models.CharField(max_length=80, unique=True) time_zone = TimeZoneField(default='UTC') #4) ..... def __str__(self): return "{0}".format(self.area_name) class Party(models.Model): ..... started_on = models.DateTimeField(blank=True, null=True) #5) finished_on = models.DateTimeField(blank=True, null=True) ..... def __str__(self): return self.party_name
- #1) TimeZoneField クラスの import
- #2) このカラムのアトリビュートを TimeZoneField に(詳細は後述)
- #3) 従来通りの DateTimeField だが、TZ-aware になる。
(PyVenv) fukuda@falcon:~/PyVenv/wrm_cwt% python manage.py shell
Python 3.4.3 (default, Aug 26 2015, 18:29:14)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.56)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
TZ-aware
datetime.datetime class object が、Time-Zone aware かどうかは、 つまるところ>>> import datetime >>> from django.utils import timezone >>> now_naive = datetime.datetime.now() >>> now_naive datetime.datetime(2015, 9, 20, 6, 1, 39, 820225) >>> type(now_naive) <class 'datetime.datetime'> >>> now_aware = timezone.now() >>> now_aware datetime.datetime(2015, 9, 19, 21, 2, 8, 245347, tzinfo=<UTC>) >>> type(now_aware) <class 'datetime.datetime'>のように、「tzinfo が付加されるかどうか」に尽きる。この効果は大きいが、 しかし、これがなかなか厄介。例えば、この表示をそのまま使って、TZ-ware な datetime object を作ろうとしても駄目で、
>>> now_by_hand = datetime.datetime(2015, 9, 19, 21, 2, 8, 245347, tzinfo=<UTC>) File "<console>", line 1 now_by_hand = datetime.datetime(2015, 9, 19, 21, 2, 8, 245347, tzinfo=<UTC>) ^ SyntaxError: invalid syntaxなどと言われる。ここは、
>>> import pytz >>> now_by_hand = datetime.datetime(2015, 9, 19, 21, 2, 8, 245347, tzinfo=pytz.UTC) >>> now_by_hand = datetime.datetime(2015, 9, 19, 21, 2, 8, 245347, tzinfo=pytz.timezone('Asia/Tokyo'))とする必要がある。('UTC' と 'Asia/Tokyo' で、「扱い」がこんなに違うのがなんとも……)
Time Zone の設定・表示
このように、単に「TZ を指定するだけ」でも面倒なのに、これを DB とやりとりするとなるとちょっと絶望的な感じがしてくるが、 それを救ってくれるのが、上の #3), #4) で導入される TimeZoneField。>>> from cwt.models.py import Area >>> yatsu = Area.objects.get(area_name="Yatugatake") >>> yatsu.time_zone 'UTC' >>> yatsu.time_zone = 'Asia/Tokyo' >>> yatsu.time_zone <DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD> >>> now_by_hand = datetime.datetime(2015, 9, 19, 21, 2, 8, 245347, tzinfo=yatsu.time_zone) >>> type(yatsu.time_zone) <class 'pytz.tzfile.Asia/Tokyo'>つまり、TimeZoneField と定義された
*.time_zone
attribute
は、文字列で設定(入力)できるが、その実体は tzinfo
に相当する class object である、という事。('*.time_zone'
という名前自体に意味が有る訣ではない事に留意。)
なので、出力の方は、(yatsu.time_zone
が既に tzinfo
class なので)
>>> yatsu.time_zone.tzname(datetime.datetime(2015,1,1)) #6) 'JST' >>> yatsu.time_zone.zone 'Asia/Tokyo' >>> timezone.localtime(now_aware) datetime.datetime(2015, 9, 20, 6, 2, 8, 245347, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>) >>> timezone.localtime(now_aware, yatsu.time_zone) datetime.datetime(2015, 9, 20, 6, 2, 8, 245347, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>) >>> timezone.localtime(now_aware, pytz.utc) datetime.datetime(2015, 9, 19, 21, 2, 8, 245347, tzinfo=<UTC>)#6) の引数は daylight saving time の判定に用いるためのものらしく、 それと関係ない TZ では何を入れても良いようだ。 (このあたり、もうちょっと熟れてくれても良いと思うのだが。) あと、ここでも、TIME_ZONE で指定したディフォルトの TZ が顔を出す。 localtime を求めるのに tzinfo を指定しなければ、その値になるという事。 また、これを変更する事もできて、
>>> timezone.activate(pytz.timezone('Asia/Kathmandu')) >>> timezone.localtime(now_aware) datetime.datetime(2015, 9, 20, 2, 47, 8, 245347, tzinfo=<DstTzInfo 'Asia/Kathmandu' NPT+5:45:00 STD>) >>> timezone.activate(yatsu.timezone) >>> timezone.localtime(now_aware) datetime.datetime(2015, 9, 20, 6, 2, 8, 245347, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>)のようにできる。
2015-09-16 (Wed): pyvenv と readline
Pip + pyvenv 万歳、みたいな事を書いたばっかりだけど、 実は「Ubuntu での依存性解決」の他にも、「なんだかなあ」は有って……
Zsh や Bash におけるのと同様、Python でも readline
は、コマンド入力での行編集から、コマンド・ヒストリー、
「コマンド補完」まで、「なくてはならない」ものだけど、Python
ではなかなか微妙なところがある。
(ちょっと前に悩まされていた、.python_history
の扱いの問題もその一つ。)
OSX では、それに加えて、python-readline モジュールが、(ディフォルトだと) readline (gnu-readline) ライブラリではなく、libedit ライブラリを使うという「ひねり」が入る——両者の間に然程差はないのだけど、 後者は、ちょっと機能が低くて、例えば(かつては)Ctrl-R で incremental search ができなかった。
とは言え、MacPorts できちんと、 Python-3.4 と py34-readline を入れておくと、問題なく Gnu-readline を使った readline (module) が import できる
# OSX に組み込みの Python fukuda@falcon:~% /usr/bin/python Python 2.7.10 (default, Jul 14 2015, 19:46:27) [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import readline >>> readline.__doc__ ’Importing this module enables command line editing using libedit readline.’ >>> # Ctrl-R は無視される # MacPorts の Python3.4 と py34-readline fukuda@falcon:~% python Python 3.4.3 (default, Aug 26 2015, 18:29:14) [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.56)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import readline >>> readline.__doc__ ’Importing this module enables command line editing using GNU readline.’ >>> (reverse-i-search)‘imp’: import readlineと、gnu-readline が読み込まれ、Ctrl-R もちゃんと動作する。 ところが、pyvenv に入ると
(PyVenv) fukuda@falcon:~/PyVenv% python Python 3.4.3 (default, Aug 26 2015, 18:29:14) [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.56)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import readline >>> readline.__doc__ >>> ’Importing this module enables command line editing using libedit readline.’ >>> import readline bck:impとなり、libedit-readline が読み込まれる。Ctrl-R (reverse-i-search) は無視はされないが動作が違う。 pip で readline をインストールしてもこれは変わらない。 しかし、easy_install を使って gnureadline をインストールすると
(PyVenv) fukuda@falcon:~/PyVenv% easy_install ~/Downloads/gnureadline-6.3.3-py3.4-macosx-10.9-x86_64.egg (PyVenv) fukuda@falcon:~/PyVenv% python Python 3.4.3 (default, Aug 26 2015, 18:29:14) [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.56)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import readline >>> readline.__doc__ ’Importing this module enables command line editing using GNU readline.’ >>> (reverse-i-search)‘im’: import\040readlineとなる。(最後の Ctrl-R で、'\040' が挿入されているのは、前の libedit-readline のせい。)
実は、ここまで来るのに長い道程が有ったのだが、 イキサツを一々書いていると話が長くなるので要約すると、
- MacPorts 環境:
- MacPorts で入れた Python3.4 + py34-readline: 問題なく gnu-readline。
- 同 Python3.4 + pip で入れた readline: gnu-readline
- pyvenv 環境:
- pyvenv の Python3.4 (MacPorts の Python3.4 のコピー)のみ: libedit-readline
- 同 Python3.4 + pip の readline: libedit-readline
- 同 Python3.4 + pip の gnureadline: libedit-readline
- 同 Python3.4 + easy_install の gnureadline-6.3.3: gnu-readline.
.python_history
へコマンドをセーブする時、空白の代りに '\040' という文字列を挿入するので、
二つの readline を混在させる訣には行かないのだった。
2015-09-12 (Sat): Pip で pyvenv
Pyvenv (Python Virtual Environment) と Git、 どちらもちょろっと触ってみてはすぐに撃退される、の繰返しだったが、 このところかなり真面目に使って、有難味を再認識している。 というか、(すぐに諦めたりしないで)使い続けないと、 道具の善し悪しは分らない、という事かな?とはいえ、pyvenv の方は環境そのものがかなり洗練されてきた、 というのもあるだろう。 素人が最初の「うう、わけわか、もうやってられねぇ」バリアを超えるには、 それをかなり氐くしておいてくれないと難しい :-)
MacPort 上なら pyvenv はとっても簡単・便利
前にも書いたが、Python 3.x から、pyvenv を最初から含むようになり、これと、新しい pip-tools (pip-compile と pip-sync) を使って
fukuda@hawk:~% sudo port install git fukuda@hawk:~% pyvenv-3.4 pvenv #1) fukuda@hawk:~% cd pvenv fukuda@hawk:~/pvenv% . bin/activate (pvenv) fukuda@hawk:~/pvenv% pip install --upgrade pip #2) (pvenv) fukuda@hawk:~/pvenv% pip install pip-tools #3) (pvenv) fukuda@hawk:~/pvenv% cat requirements.in #4) django pytz django-timezone-field pip-tools nose pyparsing python-dateutil mod-wsgi django-gmapi-new numpy readline matplotlib (pvenv) fukuda@hawk:~/pvenv% pip-compile #5) (pvenv) fukuda@hawk:~/pvenv% pip-sync (pvenv) fukuda@hawk:~/pvenv% pip freeze click==4.1 Django==1.8.3 django-gmapi-new==1.0.1.1 django-timezone-field==1.2 first==2.0.1 matplotlib==1.4.3 mod-wsgi==4.4.13 nose==1.3.7 numpy==1.9.2 pip-tools==1.1.3 pyparsing==2.0.3 python-dateutil==2.4.2 pytz==2015.4 readline==6.2.4.1 six==1.9.0まで、比較的簡単に来られる。とは言え、まあ、ちょっとなんだかなぁ、は有って、
- #1) かつては Python
の版が固定されていた方が安定するだろうという事で、
pyvenv --copies
としていたが、一度、readline か何かで互換性の問題が出てから、pyvenv
として、link (default) を使う事にした。 もう半年経ったが、今のところ、OSX + port でも Ubuntu-14.04, -15.04 でも問題は出ていない。(Ubuntu-14.04 は最近は未確認 :-) - #2) pyvenv がディフォルトで "bootstrap" する pip は -6.0.x で、pip-tools の pip-review が使えない。
- #3) 前にも書いたが pip-tools の最新版に pip-review は無い。
- #4) これを書いておけば、pip-compile, pip-sync が、依存モジュールの推定、モジュールのバージョンアップを自動でやってくれる。
とは言え、かつて MacPorts でもかなり苦労した numpy や matplotlib, readline が、 pip-compile, pip-sync であっさりインストールできたのにはちょっと感激。
Pip-compile は Ubuntu では結構大変
上の「一見とってもラクチン」は、どうやら、MacPorts で依存関係で苦労した時に、library 群を片っ端から(自動で) port してきたせいらしい。しかし、「素の Ubuntu」で同じ事をしようとしても、 pyvenv を作るところまでは同様にやれるが、 pip-compile では、例えば matplotlib や readline で大量のエラーを吐く。どうやら、pip-compile はシステムレベルのライブラリまではインストールしないようだ。 おまけに、このエラーメッセージを見ても、 どのライブラリ・パッケージが不足しているのかよく分らない。 (Ubuntu はライブラリ・パッケージが極端に細分化されている。)
「pyvenv 環境では、Python のパッケージ管理は pip
で」なんて決心した事を後悔し始めた……
が、さすが Ubuntu、悩みがあるところ、必ず解決策が有る。
Matplotlib で途方に暮れていた時、
askubuntu.com
に回答がありました。(しかも、例が matplotlib ドンピシャリだった!ちょっと感激したよ。)
要は、mod-wsgi と matplotlib を pip でインストールするためには、その前に
apt-get build-dep {package}
で、ビルドに必要なライブラリ群を揃える、という事。
#1) fukuda@ubuntu:~$ sudo apt-get install python3.4-dev python3.4-venv #2) fukuda@ubuntu:~$ sudo apt-get install apache2-dev fukuda@ubuntu:~$ sudo apt-get install apache2-mpm-worker apache2-threaded-dev #3) fukuda@ubuntu:~$ sudo apt-get build-dep mod-wsgi #4) fukuda@ubuntu:~$ sudo apt-get build-dep matplotliとすれば良い。ここで、
- #1) Ubuntu ではこれをやらないと (pip 以前に) そもそも pyvenv が無い。
- #2) 次の 2 行は、mod-wsgi が apache の multi-threading を使うために必要。 (何故か build-dep mod-wsgi はこれをカバーしてくれない。)
- #3), #4) mod-wsgi, matplotlib の難物の依存性を解決してくれる。 (実は、何十もの dpk を捜し出してインストールする。)
他の Python パッケージについて、apt-get build-dep
が必要か、はたまた、build-dep だけで、依存性の解決が可能か (mod-wsgi
みたいに、別途 apt-get install が必要ではないか) については、
実はあまりよく分らない。でもまあ、お蔭様で「pip で通す」という決断が、
然程悪くなかった、という気はしている。
2015-08-29 (Sat): Pip-tools の様変わり
あんまり時間が経たないうちに、pyvenv と git の話を書き残しておこうと思っているのだが、なかなかその本題に入れない。virtualenv は何が良いのか、どうしたら良いのがさっぱり分らず、 早々に撃退されてしまい、その後しばらくは羹に懲りていたのだが、 Python-3.x になって、pyvenv というコマンド一発で Python の仮想環境が構築できる(つまり、virtualenvwrapper とか考えなくて良い)事になり、またぞろ使い始めた。
簡素化のもう一つの軸は、パッケージ管理が pip で統一された事。(そもそも pyvenv で環境を作ったら、ディフォルトで pip もインストールされる。) 使ってみると、これはなかなか良い。特に、Linux の上で使う Python アプリを OSX の上で開発したい、なんて時にはとっても便利。
が、使い続けていると不満も出てくる。
最大の「なんだかなぁ」は、port upgrade outdated
や
apt-get upgrade
みたいに、「コマンド一発で環境を
upgrade」ができない事。しかし、同じ不満を持つ人が多かったのか pip-tools
という PyPI パッケージが提供されていて、その中の pip-review を使うと
(PyVenv) fukuda@falcon:~/PyVenv% pip-review Django==1.8.3 is available (you have 1.8.2) (PyVenv) fukuda@falcon:~/PyVenv% pip-review -i Django==1.8.3 is available (you have 1.8.2) Upgrade now? [Y]es, [N]o, [A]ll, [Q]uit yてな具合に、半自動でアップグレードできる事がわかり、 とりあえずそれで満足していた。(常用の Python モジュールが何百も有る訣ではないので、これで十分。)
……という具合に、そこそこ幸せな生活をしていたのだが、 或る日アップグレードしようとしたら、 「pip-review コマンドが無い」と言われるようになった…… pip-tools の基本的な構成が(知らぬ間に)すっかり変ってしまった、 という事らしい。pip-review の代りに、pip-compile pip-sync というコマンドを使って、
(PyVenv) fukuda@falcon:~/PyVenv% pip freeze > requirements.in (PyVenv) fukuda@falcon:~/PyVenv% emacs requirements.in #1) (PyVenv) fukuda@falcon:~/PyVenv% pip-compile # # This file is autogenerated by pip-compile # Make changes in requirements.in, then run this to update: # # pip-compile requirements.in # click==5.1 # via pip-tools #2) django-gmapi-new==1.0.1.1 django-timezone-field==1.2 django==1.8.4 first==2.0.1 # via pip-tools matplotlib==1.4.3 .... pytz==2015.4 readline==6.2.4.1 six==1.9.0 # via matplotlib, pip-tools, python-dateutil (PyVenv) fukuda@falcon:~/PyVenv% pip-sync Collecting pip-tools==1.1.4 Downloading pip_tools-1.1.4-py2.py3-none-any.whl .... Installing collected packages: pip-tools Found existing installation: pip-tools 1.1.3 Uninstalling pip-tools-1.1.3: Successfully uninstalled pip-tools-1.1.3 Successfully installed pip-tools-1.1.4てな具合に使う。上で、
- #1) 編集して、"==" 以下のバージョン情報を外す。 (ついたままにしていると、その版に固定 (pin down) する、という意味になる。)
- #2) requirements.in に無くても、依存関係を推定して、 自動で requirements.txt に加えてくれる。
この tools も大したもんだが、でも使い勝手改善の一番の貢献者は、PyPI パッケージの「安定性」だろう。Readline や Numpy, matplotlib など、 MacPorts で結構悩まされた大物パッケージも、 大抵ノータッチでアップグレードされてきている。
2015-08-22 (Sat): 重い Python ……
Pip や pyvenv にどっぷり首までつかっている毎日だけど、まさか今更 Python 本体で悩む事になろうとは……
事のおこりは、Python
の起動と停止にやたら時間がかかるようになってしまった事。
現在の Mac-mini は結構速くて、Python についても"%
python
"
とタイプしてリターンキーを半分押したあたりでプロンプトが出てるんじゃないか、
と思う程だったのに :-)。
起動も停止も問題で、どちらも 30 秒から 1 分くらいもかかるようになってしまった。
今思えば、よくまあこんなになるまで放置していたな、と思うが、
問題の進行がゆっくりだったせいで我慢できてしまった……
さて、対策。
Py-27, py-34 両方で起きるし、
お決まりの Python 本体や module
群の再インストール等でもまったく改善せず、ちょっと焦ったが、
そのうち、.pyhistory
や .python_history
が異常に大きくなっている事に気がついた。
fukuda@hawk:~% ls -l .py*
-rw------- 1 fukuda staff 610299904 Aug 10 15:21 .pyhistory
-rw-r--r-- 1 fukuda staff 390 Jul 22 2014 .pystartup
-rw------- 1 fukuda staff 714716651 Aug 10 15:20 .python_history
(これは MacBook (Mavericks) での結果で、Mac-mini (Yosemite) では、1GB
を超えていた。)
試しにこれを小さくしたら覿面に起動が早くなった……。
まあ、それはそうだろう……Python ならずとも、1GB のファイルの読み書きは大
変だから。しかし、真の問題は、なんでこんなに history file
が成長してしまったのか?だけど、こっちはどうもよく分らない。
.pyhistory
や .python_history
の中身は全て正しい Python の命令のようなので、
どうやら、Python アプリの実行中にその命令が
.pyhistory
や .python_history
に流れ込んだようだ。
本来それは有り得ない(.pystartup
は対話モードでだけ実行される)ので、
readline module か Python 本体のバグではないかと想像している。
(そもそも、.pyhistory
と .python_history
の両方が作られて大きくなる、というのが摩訶不思議。やっぱり readline
の問題かな。)
しかし今となっては根本原因は「藪の中」になってしまった。
件の「急成長」が、その後全く再現しないから。
とりあえず、history file の長さを制限するために、.pystartup
を
# 2015-08-28 (Fri) 補足: この設定は、Python-2.x でのみ有効(というか必要) import atexit import os import readline histfile = os.path.expanduser("~/.python_history") try: readline.read_history_file(histfile) except FileNotFoundError: pass readline.set_history_length(100000) atexit.register(readline.write_history_file, histfile)として、使っているが、一月余りの使用で、
fukuda@falcon:~% wc .python_history
278 367 4360 .python_history
くらいにしかなってない。(278 行、4.4 kB。この調子では、「リミット」の
100,000
行 (2 MBくらい?) には一生届かない :-)
~/.python_history
が「急成長」を始めたので、
今度は、ファイルが小さいうちに中身を眺めてみた。
すると、何故か同じ「コマンドのシーケンス」が何度も表われる……
最初は訣が分らなかったが、ふと思いついて確かめてみたら、 Python
を終了する度に、.python_history
が二度書かれている。
当然ながら、その度にサイズも倍になる訣ですぐに「指数関数的爆発」になる。
要は、上の
atexit.register(readline.write_history_file, histfile)の行が、「ディフォルトの動作」で書き込んだ
.python_history
の後に、さらに同じ内容を書き足したと思われる。
ややこしいのは、Python-2.7.10 と Python-3.4.3
では、この「ディフォルトの動作」が違っている事で、前者では何もしない。
つまり、Python-2.7.x で、command history
を使うために付け加えた上の .pystartup
のせいで、Python-3.4.x では history file の倍々ゲームになっていた訣だ。
対策としては本来なら、「Python のバージョンを検出して……」
とやるべきなのだろうけど、今や Python-2.7.x とはトンと御無沙汰なので、
先々ひょっこり出てくるかも知れない面倒を避けるために
.pystartup
を空にしてみる事にした。
(全く無くしてしまうと、PYTHONSTARTUP
がこれを指している場合は却って面倒。)
首尾は上々で、.python_history
がちゃんと更新され、
倍々ゲームも無くなった。(但し、当然の事ながら Python-2.7.x
ではヒストリーが使えない。)
274/1,788,204 Taka Fukuda Last modified: 2016-01-25 (Mon) 17:16:13 JST