ラベル python の投稿を表示しています。 すべての投稿を表示
ラベル python の投稿を表示しています。 すべての投稿を表示

2021年11月23日火曜日

MakeじゃないのよSConsは その4

Makeで言うところの暗黙のルール以外を適用したい場合について。

SConsにはProgram(), Object(), Library()などなど、組み込みのBuilderが多数用意されていて、9割方はそれらを使えば済む。しかし、例えばヘッダファイルをビルド時に動的に生成したい場合、Makeほど簡単ではない。

Makeの場合、生成ファイルや依存関係を定義する際に、生成するためのコマンドも指定できる。…と言うか、生成コマンドを省略すると暗黙のルールが適用されるだけ。以下、erbを使ってfoo.h.erbからfoo.hを生成する例。ただし、foo.h.erbの中でfoo.yamlを参照しているものとする。

foo.h : foo.h.erb foo.yaml
	erb $< > $@

一方SConsには、暗黙のルールというものが存在しなさそう。新たにBuilderを定義して使うのが基本っぽい。

env_bld = Builder(action = 'erb $SOURCE > $TARGET')
env = Environment()
env['BUILDERS']['Erb'] = erb_bld

foo = env.Erb('foo.h', 'foo.h.erb')
Depends(foo, 'foo.yaml')

う〜ん、かなりかったるい。Builderの追加先として、しれっと新たなenvironmentを作っているのは、DefaultEnvironment()に追加してもレシーバを省略してErb(...)と書くことができなかったため。何かやり方があるのだろうか?

…で、流石にこれは面倒だと思ったのか、今回のような一点もののケースのためにCommand Builderというものも用意されている。

Command('foo.h', ['foo.h.erb', 'foo.yaml'], 'erb $SOURCE > $TARGET')

こっちの方が、はるかに楽だね。

2021年11月21日日曜日

MakeじゃないのよSConsは その3

ビルドオプションの話(後編)。

前回のの続き。ビルドオプションを指定したければ、Program()やObject()の名前付き引数で、CFLAGSなりLINKFLAGSなりを指定可能だけど面倒。だからデフォルト値を指定したい場合、DefaultEnvironment()を使う。

def_env = DefaultEnvironment()
def_env['CFLAGS'] = '-DDEBUG'

DefaultEnvironment()は、デフォルトで適用される設定が詰まったenvironmentを返してくれるので、それを変更すればOK。上のように変更すれば、Object()とかではデフォルトでCFLAGS = '-DDEBUG'を指定することになる。または、以下のように取得時の引数で変更したい値を渡すことも可能(取得時とは言っても、取得したenvironmentは即捨てているけど)。

DefaultEnvironment(CFLAGS = '-DDEBUG')

わざわざDefaultEnvironment()と修飾しているからには、デフォルトじゃないEnvironment()もある。…と言うか、作成できる。

env = Environment()
env['CFLAGS'] = '-DDEBUG'
# env = Environment(CFLAGS = '-DDEBUG') と同じ

obj =  env.Object('test2.c') # -DDEBUGが付く
obj += env.Object('foo.c')   # -DDEBUGが付く
obj += Object('bar.c')
Program('test2', obj)

Environment()は新たに作成したenvironmentを返すので、それを変更してObject()やProgram()のレシーバとして使えば、変更した設定が適用される。なおEnvironment()が返す値は、その時点でのDefaultEnvironment()が返すenvironmentの複製のようだ。

2021年11月16日火曜日

MakeじゃないのよSConsは その2

ビルドオプションの話(前編)。

デバッグ用マクロを定義したり、最適化オプションを変更したりするのに、コンパイラに与えるオプションを変更したいことはよくある。そんな時、Makeなら

CFLAGS = -DDEBUG

のように暗黙のルールが参照する変数の値を変更しておけば、この設定はほぼMakefile全体に適用される。一方SConsは、

Program('test1', ['test1.c', 'foo.c', 'bar.c', 'baz.c'], CCFLAGS = '-DDEBUG')

のように引数で指定する(のが方法の1つ)。ただしこれだと、test1.c, foo.c, bar.c, baz.cのどれをコンパイルする際にも-DDEBUGオプションが付加される。では、例えばfoo.cとbar.cのコンパイル時のみ-DDEBUGを付けたい場合はどうするか。

objs =  Object('test1.c')
objs += Object('foo.c', CCFLAGS = '-DDEBUG')
objs += Object('bar.c', CCFLAGS = '-DDEBUG')
objs += Object('baz.c')
Program('test1', objs)

急にスクリプト臭くなったな。一々引数で指定したくないとか、デフォルトで指定したオプションを付けて欲しいとか、そう言った話は次回。

2021年10月25日月曜日

MakeじゃないのよSConsは その1

ファイルの更新判定の話。

Makeに慣れ親しんだおじさんがコードをいじりながらビルドしていると覚える違和感は、ファイルの更新判定。SConsはデフォルトでタイムスタンプではなく、MD5 hashで判定する。要するに、ファイルの中身が変わらないと更新されたとみなされない。Makeで部分的に再コンパイルしたいときにtouchするという常套手段は、SConsでは通用しないわけだ。

この仕様のおかげで、依存関係の末端にあるファイルが更新されても最終成果物が再生成されない場合もある。例えば、以下の依存関係に対して

一度フルビルドしてからhello.hのコメントのtypoだけ修正した場合。Makeならtest0.o, hello.oを再生成した後、それらに依存しているtest0まで確実に再生成する。

一方SConsだと、更新されたhello.hに依存しているtest0.o, hello.oを再生成するまでは同じ。しかし、コメント修正程度だったため前と全く変わらないtest0.o, hello.oが生成された場合、test0の再生性は不要だと判断する。

2021年10月14日木曜日

MakeじゃないのよSConsは その0

MakeおじさんがSConsを使ってみた雑感を、ハッハァ〜ンと書き連ねてみる。

まず、公式なUser Guideがあるので、基本的にこっちを読めばいいのだろう。まだ読みかけだけど。

さて、とりあえず触りとして極々簡単な例。以下のような依存関係があるファイル群から、test0をビルドする場合。

SConstructを書くとこうなる。

Program('test0', ['test0.c', 'hello.c'])

依存関係とか良きに計らってくれるので、この程度なら1行で済む。比較対象として、ベタにMakefileを書いてみよう。

all: test0
test0: test0.o hello.o
test0.o: test0.c hello.h
hello.o: hello.c hello.h
clean:
    rm -f test0 test0.o hello.o

やりたいことが明確と言えば明確だけど、面倒なのも確か。

2014年9月27日土曜日

Rubyistが歩んでみる蛇の道はPython その7

Dive into Python 3に戻って、chapter 7と8からイテレータの話。

オブジェクトobjをlistやtupleのようにforでぶん回せる(iterableである)ようにするには、

  1. objが__iter__()メソッドを持っている
  2. obj.__iter__()が返すオブジェクトが__next__()メソッドを持っている
ようにすれば良い。大方の想像通り、iter(xx)するとxx.__iter__()が、next(yy)するとyy.__next__()が実行される。__next__()は、呼ばれる度に列挙される値を1つずつ返すようにする。また、列挙し終えたならStopIteration例外を投げる。端落ちしてもNoneが返されるので、列挙の終了とはみなされない。

では、generatorはどうしてiterableだったのか。別に特別扱いされていたわけでも何でもなく、generatorも上の条件を満たしていたのだ。

>>> def gen():
...   yield 0
...
>>> g = gen()
>>> g.__iter__
<method-wrapper '__iter__' of generator object at 0x10a88cf78>
>>> g == g.__iter__()
True
>>> g.__next__
<method-wrapper '__next__' of generator object at 0x10a88cf78>
>>> g.__next__()
0
>>> g.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
generatorは実は、自分自身を返す__iter__()と、yieldした値を返す__next__()を持っていたのだ。なおgeneratorは、StopIterationを明示的にを投げなくてもreturnするときに投げてくれるようだ。普通の関数と違って、yieldとreturnを区別できるからだろうね。

ちなみに、普通のlistやtupleも例外ではなく、__next__()を持っているオブジェクトを返す__iter__()を持っている。

この緩い条件を満たしたiterableなオブジェクトから、別のiterableなオブジェクトを生成するitertoolsというライブラリが、Pythonには標準で付いている。chapter 8ではitertoolsの中からいくつかの関数を使って見せているけど、詳しくはhelp()で調べてみれば良いだろう(きちんとdocstringが用意されているって、地味に凄いことだよなぁ)。…と、これだけで片付けるのはあんまりなので、iteratorのありがたみを3行で表す(パッと見2行だけど、printの次の空行を含めて3行)。

for p in itertools.permutations(range(10000)):
  print(p)
順列を列挙するコードを自前で書かずに済むのはありがたいけど、それ以前にこのコードが動くこと自体がありがたい(実行に何年かかるか知らないが)。ちょっと考えれば分かるように、10000要素の整数リストを10000!個持つリストを作ってからループを回そうなんて思ったら、どう考えてもメモリが足りない。それでもこのコードが延々と順列を表示し続けてくれるのは、いきなり10000!通り全てを生成しようなんて馬鹿げたことをせず、next()の度に1つずつ列挙してくれているからだろう。ここらへんのケアをおまかせできるのは美味しい。

2014年9月25日木曜日

Rubyistが歩んでみる蛇の道はPython その6

Dive into Python 3のchapter 7を読んでみたのだが、Classes & Iteratorsなんて章の名前を付けている割に、そんなにクラスについて詳しく書いていない。具体的な継承の話なんか、まるで無いし。そこでググってみたところ、こちらの公式チュートリアルが良さそうなので、ざっと読んでみた。

多くの人が真っ先に面食らうのは、Rubyを含む多くのオブジェクト指向な言語ではimplicitに渡されるのが当たり前のselfやらthisやらが、Pythonのメソッド定義ではexplicitに第1引数として、インスタンス自身が渡される点だろう。この第1引数はselfという名前を使うことを推奨されているようだけれど、selfは単なる仮引数の名前であって予約語でも何でも無い。

そしてメソッド定義の中でさえ、インスタンス変数にはselfを介さないとアクセスできない。言い換えると、インスタンス変数はスコープに入らないのだ。Rubyだと

def method
  @member1 = 100
  print(@member2)
end
のようにアクセスできるところが、Pythonだと
def method(self):
  self.member1 = 100
  print(self.member2)
のようにせねばならない。

地味に嫌らしいと思うのが、Pythonではクラスのインスタンスを介してクラス変数にアクセスできる点。Pythonだと

class Foo:
  x = 100

foo = Foo()
print(foo.x)
こんなことが出来てしまう。Rubyでクラス定数を参照しようと思ったら
class Foo
  X = 100
end
print(Foo::X)
print(Foo.new.X) # error
のように、インスタンスではなくクラス名の修飾が必要。

他にも、public, protected, privateと言ったアクセス制限の機能がPythonにはない。_memberみたいに'_'を付けたメンバはprivate扱いしてね、みたいなコーディングルールで凌いでいるようだ。まあLLならそれくらいの緩さでも許されるだろう。

問題は継承。ずばり、Pythonは多重継承が出来る。これだけでもう、Rubyで言うところのmix-inだの、Javaで言うところのinterfaceだの、そんな細かい話は吹っ飛んでしまう。属性の検索が基本的に深さ優先探索で行われるというのも要注意だろう。A, Bの順に多重継承した場合、BよりAの曾祖父の方が先に探索されるのだ。実装を考えればそりゃそうなんだろうけど、普段から多重継承が制限された世界で過ごしていると違和感を禁じ得ない。

2014年9月23日火曜日

Rubyistが歩んでみる蛇の道はPython その5

Dive into Python 3のchapter 6をざっと読んでみた。

Pythonの関数はオブジェクトである。これはRubyの関数(メソッド)と非常に大きく異なる点だ。Pythonだと

>>> def foo():
..  print('foo')
..
>>> f = foo
>>> f()
foo
のように変数に直接関数を代入したり、代入した変数経由で元の関数を呼び出したりできる。…と言うか
>>> foo = 1
>>> foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
>>> foo = f
>>> foo()
foo
こんなことができてしまうということは、fooは定数ですらないようだ。Pythonのdef文は、JavaScriptのfunctionと同じように関数オブジェクトを作って、それを何の変哲も無い普通の変数に代入しているだけみたい。

とにもかくにも、関数がオブジェクトであるということは、100やら'abc'やらと同じように普通に受け渡しが出来る。ここは正直Rubyの弱いところで、基本的に最大1つのブロックをメソッドに渡すことしかできない。一応:funcのようなシンボルでメソッド名を渡したり、lambda等でProcオブジェクトを作って渡したり、呼び出し先でevalしてもらうコードを文字列で渡したりすることはできるのだけれど、自然に関数を扱えるとは言い難い。まあ、Rubyではメソッドが特別扱いされているが故に

print 'ruby'
のように()を付けなくてもメソッド呼び出し出来るのは、ちゃちゃっと使い捨てコードを書くときに楽は楽なんだけど。

もう1点。RubyのProcにはないけどPythonの関数オブジェクトにはある機能に、中断/再開がある。

>>> def gen():
...   v = 0
...   while v <= 50:
...     yield v
...     v += 10
...
>>> g = gen()
>>> type(g)
<class 'generator'>
このように、内部でyield文を使った関数はgeneratorクラスのオブジェクトを返す。そして
>>> next(g)
0
>>> next(g)
10
>>> next(g)
20
>>> next(g)
30
>>> next(g)
40
>>> next(g)
50
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
このようにnext文でgeneratorを呼び出す度に、関数オブジェクトがyieldした値が呼び出し元に返され、また関数オブジェクトの方はyieldしたところで中断する。ノンプリエンプティブなスレッドと言えなくもない。このgeneratorの嬉しいのは、リスト等と同じように
>>> for i in gen():
...   print(i)
...
0
10
20
30
40
50
こんな感じでイテレータとして使えるところ。イテレータの話は次の章で深追いするのかな?

2014年9月11日木曜日

Rubyistが歩んでみる蛇の道はPython その4

Dive into Python 3のchapter 5をさらりと読んでみた。

Rubyを含む他のLLと同様、Pythonでも正規表現は使える。が、意外なことに、reモジュールを明示的にimportしないと使えない。下の例はimport reを端折ると動かない。

>>> import re
>>> str = 'abcdefg'
>>> re.sub(r'c.e', 'XYZ', str)
'abXYZfg'
>>> m = re.search(r'.{4}$', str)
>>> m.group(0)
'defg'
同じことをRubyでやろうとするとこんな感じ。
irb> str = 'abcdefg'
irb> str.sub(/c.e/, 'XYZ')
=> "abXYZfg"
irb> m = /.{4}$/.match(str)
irb> m[0]
=> "defg"

ちなみにPythonのr'...'は、正規表現リテラルではなく単なる文字列リテラル。regular expressionではなくraw stringのr。rを付けるとRubyで言うところの'...'文字列のように、\等が特別扱いされなくなる。

さて、実質定数な正規表現でも、内部的には使う度に文字列から正規表現に変換しなければならないかと言うと、さすがにそんなことはなく、re.compile()を使えば正規表現オブジェクトを作成できる。また、その正規表現オブジェクトは、subだのsearchだの、reモジュールと同名の関数を持っている。もちろん、引数に正規表現文字列は取らないけど。

正規表現のオプションは、正規表現文字列を受け取るreモジュールの関数に引数flagsとして渡してやる。このflagsはデフォルト値0が設定されているので省略可能。Rubyで言うところの

r = /ab c/ix
が、Pythonでは
r = re.compile(r'ab c', re.I | re.X)
となる。ちとクドいね。あと、Rubyの正規表現にフリーフォーマットモード(x)があることを今更知った。

2014年9月9日火曜日

Rubyistが歩んでみる蛇の道はPython その3

Dive into Python 3のchapter 4をざっくり読んでみた。

Pythonは2から3で、文字列の扱いが大きく変わったそうな。具体的には、Unicodeで表現できる文字の並びをstringと、符号無し8ビットの値の並びをbytesを、明確に区別するようになった。

文字列の内部表現に関しては特に規定がなくて、UTF-8にするもUTF-32にするも実装の勝手なのかな。何であれ、Unicodeじゃない文字列を入力して文字列としてPythonで処理したければ、必ず文字コードの変換が入るわけだ。Rubyとの比較はるびまのM17Nの記事が良くまとまっているが、Rubyの場合は内部表現は統一していない。文字コードの変換とそれに関する問題から縁遠いのが長所だが、自分が処理系を実装するなら、面倒だからUTF-8あたりに統一するだろうなぁ。RubyはUnicodeと心中するつもりは無い、みたいなことをどこかで読んだような…。

話をPythonに戻すと、stringやbytesはimmutable。すなわち、stringやbytesのインスタンスは、何かを付け足したり削ったり、部分的に置き換えたりできない。

>>> s = 'abc'
>>> t = s
>>> s += 'def'
>>> s
'abcdef'
>>> t
'abc'
このように'abc'というインスタンスを'abcdef'に変更するわけではなく、あくまで'abcdef'というインスタンスを新たに生成するのだ。ただ、bytesに関してはmutableなbytearrayも存在するので、巨大なバイト列を扱うときなど、適宜bytearrayと使い分ければ良いのだろう。

formatは微妙。どうしてデファクトスタンダードであるCのprintfの書式を踏襲しなかったのか? 雰囲気はシェルスクリプトのpositional変数に似てるけど$記号は使わない。dictのキーとして文字列を指定するときに''で括らない等、文法的にも何となく気持ち悪い。

ソースの文字コードにUTF-8を推奨しているのはいいね。Shift_JISとCRLF改行のファイルを撒き散らすメモ帳は死ね。氏ねじゃなくて死ね。

2014年9月4日木曜日

Rubyistが歩んでみる蛇の道はPython その2

Dive into Python 3のchapter 3をさっくり読んでみた。

まずosモジュール。プラットフォーム依存を極力抑えるために用意されたAPIを詰め込んだモジュールだそうな。そこらへんRubyはUnix寄りな気がするけど、Pythonは中立を目指してるってことか。まあ、chapter 3で紹介されているAPIだけでは、理想を実現しているのかどうか判断しかねるけど。

Rubyと比較すると、DirやFileクラスの特異メソッドに分散している機能が、Pythonではosモジュールに集約されている印象。それでもglobはosに含まれないようだけど。

comprehension記法は、Rubyで言うところのmap(とif clauseをつければselect)の略記法だけど、dictにも使えるところがRubyにはない点。Pythonと比較してみて、今更RubyのHash#mapがHashを返さないことに気が付いた。あと、mapとselectの略記法とは言ったけれど、selectしてからmapするよりは中間オブジェクトが作られなさそう。実装は見てないけど。

2014年9月2日火曜日

Rubyistが歩んでみる蛇の道はPython その1

とりあえずDive into Python 3を、chapter 2までざっくり読んでみた。

まず、Rubyで言うところのirbに相当する機能が、Pythonには標準で備わっている。

% python3
Python 3.4.1 (default, Aug 24 2014, 21:32:40)
[GCC 4.2.1 Compatible Apple LLVM 5.1 (clang-503.0.40)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 1+1
2
>>>
こんな感じで、引数無しで起動すればインタラクティブなモードになる。

Rubyistが注意しないと間違えそうなのは、if文の条件のようなbool型が要求されるところ。Rubyならfalseとnil以外はtrueと同じ扱いなのだが、PythonはFalseとNoneだけでなく、整数の0や浮動小数点数の0.0も偽扱い。0に関してはむしろRubyの方が特殊なのだが、Pythonでは更に要素数がゼロのlist, tuple, set, dictまでもが偽とみなされる。

以下のRubyの例ではどちらのifの中も実行されるが、

# ruby
if 0
  print('実行される')
end
if []
  print('実行される')
end
以下のPythonの例ではどちらのifの中も実行されない。
# python
if 0:
  print('実行されない')

if []:
  print('実行されない')

さて、まだ触りも触りだけど、言語としてはオブジェクトや属性や関数のあり方とか、JavasScriptに近い臭いがする。コードにコメントの形でドキュメントを埋め込む標準形を持っている言語は少なくないけれど、コメントではない__doc__属性を標準で持っているというのは面白い。あと、2系では基本的に整数除算だった/演算子を、3系では実数除算にしてしまったという話はビックリしたのと同時に、2系に留まる人の気持ちが分かった。そんな根源的なところを変えるか?

2014年8月31日日曜日

Rubyistが歩んでみる蛇の道はPython その0

今まで何となく避けてきたPythonに、ちょいと向き合ってみようと思い立った。自分で積極的にPythonのコードを書くようになるかは分からないけど、他人が書いたコードを読みたいな、と。また、新しい言語に触れるってことは、その言語の思想に触れるってことでもあるから、それがまた別の機会で活きるかもしれないし。

さて、ここで問題となるのがバージョン。Pythonは2系と3系で結構変化していて、長らく移行に手間取っているらしい。Rubyに例えると2系へのジャンプみたいなもんかな。これから学ぼうとしている自分にはレガシーな資産も呪縛も無いので、せっかくだから、俺はこの3系を選ぶぜ。早速Homebrewのpython3というformulaを使ったら

% python3 --version
Python 3.4.1
というバージョンのものがインストールされた。

とりあえず教科書はDive Into Python 3にしてみようかと思い、PDF版をiPad AirとiPod touchに放り込んだ。日本語版もあったけど、こちらはPDF版が見当たらない。