PythonIAQ: Infrequently Answered Questions

by Peter Norvig

Q:Infrequently Answered Questionって何ですか?

ある種の質問は、答えを知る人がほとんどいない、あるいはポイントが曖昧である、理解しづらい問題であるという理由によって(とはいえ、あなたにとってはとても重要なものかもしれません)、めったに答えられることがありません。 Java_IAQ のために、このIAQという用語を作ったのですが、それはとても有名な、 About.comの都市伝説リスト でも見つけられます。PythonのFAQはたくさん見つかりますが、Weiyang Chenによる、 このページの中国語訳 を除けば、これは唯一のIAQです(FAQリストのいくつかには、 C への皮肉が含まれています)。

Q:finally節内のコードの実行は失敗することが無いのですが、これって正しいですか?

何が無いって? 確かに、ほとんど無いことではあります。finally節内のコードは、try節で例外が発生するか、sys.exitが呼ばれているかどうかを評価したあとで実行されます。しかし、finally節は(プログラムの)処理が渡されない限り、実行されることがありません。これは次の例のように、選択肢の内容にかかわらず起きることです。

try: if choice: while 1: pass else: print "Please pull the plug on your computer sometime soon..." time.sleep(60 * 60 * 24 * 365 * 10000) finally: print "Finally ..."




Q:ポリモーフィズムはすばらしい。どんな型を要素にもつリストでもソートできるから。これって正しい?

間違い。こんな風に見なされます。

>>> x = [1, 1j]
>>> x.sort()
Traceback (most recent call last):
  File "<pyshell#13>", line 1, in ?
    x.sort()
TypeError: cannot compare complex numbers using <, <=, >, >=

(この1jという数字は-1の平方根です)問題なのは(現在の実装の)sortメソッドが、要素を比較するのに__lt__メソッドを使っていることで、これは複素数型を受け付けません(順序づけできないため)。珍らしいことに、complex.__lt__は複素数と文字列、リストやそのほか複素数型以外の型との比較を全く苦にしません。ですから回答は、__lt__メソッドをサポートする1つながりのオブジェクトをソートすることはできるというものです(実装が変われば、その他のメソッドでも可能です)。

質問の前半「ポリモーフィズムはすばらしい」には、私も賛成です。でもPythonはいくつものPython型(シーケンス型や数値型)が非公式に定められているため、場合によってはポリモーフィズムが難しくなっています。

Q:Pythonで++xとかx++とかできる?

文学的には、イエスでありノーでもあります。実際的にはノーです。どういうことかわかりますか?

  • イエス 、++xは合法的なPythonコードです。でも、あなたがC++やJavaのプログラマーであるときに考えるものとは異なります。+は前置の単項演算子で、++xは+(+(x))とパースされます。これは(少なくとも数値型では)単にxという結果となります。
  • ノー 、x++それ自身が正しい表現ではありません。正しい文脈で使われたとしてもです。例を挙げましょう。x++ -yはx + + (-(y))とパースされます。これは数値型ではx - yと等価です。もちろん、++xを(限られた範囲で)よろしく処理してくれるクラスを自分で作ることはできます。例えばある数を保持して、それを単項の+演算子が0.5づつ増やして行くようなものです(もし ランダムなアルゴリズム がお好みなら、1と多分0.5づつでもいい)。でも...
  • ノー 、そんなのは馬鹿げています。より良いのはx += 1と書くことです。この書式はPython 2.0に追加されたものです。

より深遠な問いは、なぜPythonはx++と書くことを許さないのかという点です。私が思うに、これはPythonが式の中での代入を許さないのと同じ理由によります。つまり、Pythonは文と式を明確に区別しようとしているということです。もし、これらをはっきりと区別したいなら、++という書き方を許さないことは、おそらく最も良い選択です。言い換えれば、関数型言語の支持者による、文も式であるべきだという主張です。私はこの件については、デンマークの同胞、Bjarne Stroustrupに賛成しています。彼は 『C++の設計と進化』 の中でこう言っています。「私が言語をゼロから設計していたら、たぶんAlgol68の流儀を一歩押し進めて、あらゆる文と宣言を、値を持つ式にしただろう」

Q:ostreamにcout << x << y のようなC++の文法は使えますか?

使えます。``print x, y''のように書くのは気が進まないなら、以下のようにしてもいいでしょう。

import sys

class ostream:
    def __init__(self, file):
        self.file = file
        
    def __lshift__(self, obj):
        self.file.write(str(obj));
        return self

cout = ostream(sys.stdout)
cerr = ostream(sys.stderr)
nl = '\n'
--------------------------------------------------------------------------------
cout << x << " " << y << nl

(この文書では横線の上にファイル中のコードを、線の下に使用例を記しています)このようにすることで、異なる文法を手にすることになりますが、出力のための新たな約束事が提供された訳ではなく――すでにPythonにある、文字列に関する約束事をまとめただけです。JavaのtoString()と同様のものです。C++は全く異なる方法をもちます。オブジェクトを文字列に変換する正統的な方法の代わりに、オブジェクトをストリームとしてprintするための正統的な方法(いや、正統的っぽい方法というべき――多くのC++コードが未だにprintfを使っているから)です。このストリームを使ったアプローチはより複雑ですが、利点があります。それは本当に大きなオブジェクトをprintしたい際に、一時的にでも巨大な文字列を作らずに済むからです。

Q:C++のprintfみたいなのが良い時には?

Pythonでprintfを定義するのは悪くないアイデアです。printf("%d = %s", num, result)がprint "%d = %s" % (num, result)より自然だと議論することもできます。それは括弧がよりこなれた位置にあるからです(そして%を省略できる)。

def printf(format, *args): print format % args,

こんなワンライナーの中でさえ、微妙な点があります。まず、末尾にカンマを付けるかどうかを決めなければなりません。よりC++っぽくするならカンマをつけるでしょう(それは改行つきでprintしたい時に、整形済み文字列の末尾にそう付加しなくてはならないことを意味する)。2つめに、このコードは痕跡と空白を表示するでしょう。printの代わりにsys.stdout.writeを使いたいと思うかもしれません。3つめは、よりCっぽくすることに何か良いことがあるのでしょうか? そう、関数が使用できる場所では、lambda式や第一引数をマップした文ではなく、出力関数が必要なのかもしれません。実際、そのような関数は手軽なので、大抵の場合、整形を行なわないものも欲しくなります。

def prin(x): print x,

これで、 map(prin, seq)はseqの各要素を出力するようになりますが、map(print, seq)は文法エラーになります。おそらく、おっちょこちょいなプログラマ(ええ、そう、私のこと。でも自覚症状はありますよ)が、これらの関数を1つにまとめるのはすばらしいアイデアだと思いつくでしょう。こんな風に。

def printf(format, *args): print str(format) % args,

このとき、printf(42)、printf('A multi-linen message')、printf('%4.2f', 42)はすべて動作します。しかし``よいアイデア''だと思ったことは、printf('100% guaranteed')やフォーマッティング文字列を表す文字%とともに実行したとたん、``前に考えたこと''へと変わってしまいます。このバージョンのprintfを実装したなら、こんなコメントが必要でしょう。

def printf(format, *args): 
    """最初の引数をフォーマット文字列として引数を整形、出力する。
    formatが文字列でない場合は、strによって変換される。
    You must use printf('%s', x) instead of printf(x) if x might
    xが%やバックスラッシュを含む時はprint(x)の代わりに
    print('%s',x)を使う必要がある。"""
    print str(format) % args,

Q:辞書のリテラルにもっといい文法はありませんか?キーのすべてが識別子なんです。

ええ。キーの前後に引用符を付けなければならないのに、うんざりするのは私も一緒です。特に大きな辞書リテラルを扱う場合には。まず、Pythonに使える変更を施すことを考えて、特別な文法を加えてみます。{'a':1,'b':2}と書かなければならない物を{a = 1, b = 2}という感じになるでしょう。Python 2.3から、dict(a = 1, b = 2, c = 3, dee = 4)という文法を使えるようになりました。これでほぼ充分だと思います。Python 2.3以前を使うときは、1行の関数を使っています。

def Dict(**dict): return dict
--------------------------------------------------------------------------------
>>> Dict(a=1, b=2, c=3, dee=4)
{'a':1, 'b':2, 'c': 3, 'dee': 4}

読者から提案されたのだけれど、Perlにはハッシュを示す特別な表記法があり、("a", 1, "b", 2}か(a => 1, b => 2)でハッシュのリテラルを記述できます。その通り、ただしまったくの真実という訳でもありません。"man perlop"は"記号 => は単にコンマ演算子の同義語です。"...そして実際、(a, 1, b, 2)と書くことができます。このときaとbは剥き出しの文字となります。けれど、Dag Asheimが明らかにしたところによると、もしstrictを有効にすれば、「stringsまたは=>演算子を使う必要がある」というエラーがでるはず。またLarry Wallは"Perl 6に裸の単語(barewords)はないだろう"ということを明らかにしています。

Q:同じようなショートカット用のオブジェクトはないですか?

いかにも。あなたがしたいことが幾つかのフィールドにデータを格納するオブジェクトを作成することであれば、以下のようにします。

class Struct:
    def __init__(self, **entries): self.__dict__.update(entries)
--------------------------------------------------------------------------------
>>> globals = Struct(answer=42, linelen = 80, font='courier')
>>> globals.answer
42
>>> globals.answer = 'plastics'
>>> vars(globals)
{'answer': 'plastics', 'font': 'courier', 'linelen': 80}

基本的に、我々がここで行なっているのは、匿名クラスの作成です。そう、グローバルのクラスはStructだということは知っていますが、そこにスロットを追加したいのです。これは新しい名前を持たないクラスを作成するのに似ています(多分に似た方法が、lambdaによる匿名関数の作成)。私はStructによる混乱を憎んでいます。それは簡潔な方法ではありますが、以下のようなメソッドを追加しようとした時、それぞれの構造を見栄えよく出力するさまざまなバージョンを作る羽目になるからです。

def __repr__(self):
    args = ['%s=%s' % (k, repr(v)) for (k,v) in vars(self).items()]
    return 'Struct(%s)' % ', '.join(args)
--------------------------------------------------------------------------------
>>> globals
Struct(answer='plastics', font='courier', linelen=80)

Q:オブジェクトを作成するのはすばらしいですね。更新についてはどうですか?

ええ、辞書はupdateメソッドをもっているので、dが辞書であればd.update(Dict(a=100, b=200)) とできます。もしくはxが辞書かオブジェクトであれば、update(x, a=100, b=200)とするような関数を定義することも可能です。

import types
  
def update(x, **entries):
    if type(x) == types.DictType: x.update(entries)
    else: x.__dict__.update(entries)
    return x

これは特に、コンストラクタを書くのに有効でしょう。

def __init__(self, a, b, c, d=42, e=None, f=()):
  update(self, a=a, b=b, c=c, d=d, e=e, f=f)

Q:辞書のデフォルト値を、0や[]なんかにできる?

何かを数えようという、あなたの気持ちには共感します。count[x] += 1と書けるなら、count[x] = count.get(x, 0) +1と書かなければならないのに比べて、ずいぶん洗練されているでしょう。Python 2.2では、このような目的から組み込みのdictクラスを簡単にサブクラス化できるようにになりました。私のバージョンはDefaultDictといいます。copy.deepcopyの使い方を記しておきます。これは同じ[]をデフォルト値として共有する辞書内の、すべてのキーを保持しません(我々は0をコピーするために時間を無駄にしているが、この時間の消費は、初期化に比べて更新や読み出しが多いのならそんなに悪くない)。

class DefaultDict(d
ict):
    """Dictionary with a default value for unknown keys."""
    def __init__(self, default):
        self.default = default

    def __getitem__(self, key):
        if key in self: return self.get(key)
        return self.setdefault(key, copy.deepcopy(self.default))
--------------------------------------------------------------------------------
>>> d = DefaultDict(0)
>>> d['hello'] += 1
>>> d
{'hello': 1}
>>> d2 = DefaultDict([])
>>> d2[1].append('hello')
>>> d2[2].append('world')
>>> d2[1].append('there')
>>> d2
{1: ['hello', 'there'], 2: ['world']}
def bigrams(words):
    "Counts of word pairs, in a dict of dicts."
    d = DefaultDict(DefaultDict(0))
    for (w1, w2) in zip([None] + words, words + [None]):
        d[w1][w2] += 1
    return d
    
>>> bigrams('i am what i am'.split())
{None: {'i': 1}, 'i': {'am': 2}, 'what': {'i': 1}, 'am': {None: 1, 'what': 1}}

注意すべきなのは、DefaultDictのない2文節の例、d[w1]「w2] += 1ではこのようにしなくてはならないということです。

d.setdefault(w1,{}).setdefault(w2, 0); d[w1][w2] += 1 

Q:ねえ、マトリックスの置換を行なうコードを0.007KB以内で書ける?

そんな質問は2度としないでください。マトリックスがシーケンスのシーケンス(行列)を意味するなら、zipが職務を全うします。

>>> m = [(1,2,3), (4,5,6)]
>>> zip(*m)
[(1, 4), (2, 5), (3, 6)]

これを理解するには、f(*m)がapply(f, m)に似ていることを知っていなくてはなりません。これは古くLISPにその源をもつ問いで、この問いに対する解答は、Pythonのmap(None,*m)にあたるものですが、Chih-Chung Changによって提案されたzip版は、より短くなっています。これは”“Lettermanの馬鹿なプログラマのトリック”に登場するトリックを解くためにしか役立たないと思うかも知れませんが、私はある時こんな問題に直面しました。データベースの行が与えられており、その各行は順に並んだ値のリストとなっています。このそれぞれのカラムに見られる一意な値のリストを見つけだすというものです。私が書いたのはこのようなコードです。

possible_values = map(unique, zip(*db))

Q:f(*m)トリックはクールですね。同じ文法はメソッドでも動きますか? x.f(*y)というやつです。

この質問は、よくある誤解を明らかにします。メソッド呼び出しにはどんな文法もありません!関数を呼び出す文法があり、またオブジェクトからフィールドを取り出すための文法も存在します。またバウンドメソッドのようなものもあります。これら3つの特徴を合わせてx.f(y)が1つの文法に見えるよう連携させます。これは実際に(x.f)(y)の同等物であり、同時に(getattr(x, 'f'))(y)の同等物となります。あなたが疑っているのは分かってますよ。見てください。

class X:
    def f(self, y): return 2 * y
--------------------------------------------------------------------------------
>>> x = X()
>>> x.f
<bound method X.f of <__main__.X instance at 0x009C7DB0>>
>>> y = 21
>>> x.f(y)
42
>>> (x.f)(y)
42
>>> (getattr(x, 'f'))(y)
42
>>> xf = x.f
>>> xf(y)
42
>>> map(x.f, range(5))
[0, 2, 4, 6, 8]

そんなわけで、質問に対する答えはこうなります。メソッド呼び出しの引数に*yや**y(あるいはそのほか関数呼び出しの引数として与えられるもの)を与えることはできます。なぜならメソッドコールはそのまま関数呼び出しだからです。

Q:Pythonでは、1行もコードを書かずに抽象クラスを実装することができますか?4行以内なら可能ですか?

Javaにはabstractキーワードがあり、抽象クラスを定義できます。これはインスタンスを作れませんが、クラス内の抽象クラスをすべて実装することで、サブクラス化が可能です。一部だけで知られた事実ですが、Pythonでもほとんど同じ方法でabstractキーワードを用いることができます。異なるのは未実装のメソッドを呼び出すと、コンパイル時ではなく実行時にエラーになることです。比較してみましょう。

## Python
class MyAbstractClass:
    def method1(self): abstract

class MyClass(MyAbstractClass): 
    pass
--------------------------------------------------------------------------------
>>> MyClass().method1()
Traceback (most recent call last):
    ...
NameError: name 'abstract' is not defined
 /* Java */
public abstract class MyAbstractClass {
    public abstract void method1();
}

class MyClass extends MyAbstractClass {}
--------------------------------------------------------------------------------
% javac MyAbstractClass
MyAbstractClass.java:5: 
  class MyClass must be declared abstract. 
  It does not define void method1() from class MyAbstractClass.

Python言語リファレンスでabstractキーワードを探すのに、あんまり時間を使わないでくださいね。載ってませんから。私が言語仕様に追加したのです、実装コードゼロで!何をしたかをご説明しましょう。method1を呼び出すと、変数abstractは存在しないためNameErrorが発生するというわけです(誰かがabstractという変数を定義している可能性があるため、それは詐欺じゃないかというに違いない。でも、あるコードが依存している変数を再定義すると、どんなプログラムでも中断することになるだろう。唯一の違いは、われわれは変数定義の不備に頼っているのであって、変数定義に立脚していないということだ)。

もし、abstractキーワードの代わりに関数abstract()を書きたいなら、NameErrorの代わりにNotImplementedErrorを送出する関数を定義できます。これには、もうちょっとだけセンスが必要でしょう(もちろん誰かがabstract関数を、引数なしで何かをさせるように再定義してしまったら、エラーメッセージを受け取ることはできない)。abstractのエラーメッセージを作成するのは良いことのように見えます。特に何が不正な呼び出しを行なっているのか、スタックフレームを覗き込みたいときには。

def abstract():
    import inspect
    caller = inspect.getouterframes(inspect.currentframe())[1][3]
    raise NotImplementedError(caller + ' must be implemented in subclass')
--------------------------------------------------------------------------------
>>> MyDerivedClass().method1()
Traceback (most recent call last):
    ...
NotImplementedError: method1 must be implemented in subclass

Q:列挙型(enums)をPythonで使うにはどうしたらいいですか?

Pythonでこの問いへの答えが見つからないのは、いくつかの解答があり、あなたがenumをどんなものであるとみなしているかに依存するからです。もし、何らかの変数を欲していて、それぞれが一意なinteger変数でよければ、こう書けます。

red, green, blue = range(3)

この方法の難点は、新しい変数を左側に追加したいと思うと、右側の要素の番号までインクリメントしなければならない点です。これはそんなに悪いものではありません、例えあなたが思い違いをしてPythonがエラーを出したとしても。enumsをクラスの中に隔離すれば、コードの衛生状態はさらに良くなるでしょう。

class Colors:
    red, green, blue = range(3)

これで、Colors.red はゼロを返し、dir(Colors)も使いやすいものになるかもしれません(__doc__と__module__エントリを無視しなくてはならないけれど)。もし、どの列挙子(列挙型のメンバ)がどの値を持つのかまでコントロールしたいなら、何問か前のStruct関数を、以下のように使うことができます。

Enum = Struct
Colors = Enum(red=0, green=100, blue=200)

これらの簡潔なアプローチで普通はこと足りますが、さらに多くを望む人もいます。 python.org 、 ASPN 、faqts などでEnumの実装が見つかります。これは私のバージョンで(ほとんど)すべての人のすべての内容を含み、それでいて適度な簡潔さを保っています(44行あり、そのうち22が実コード)。

class Enum:

    """Create an enumerated type, then add var/value pairs to it.
    The constructor and the method .ints(names) take a list of variable names,
    and assign them consecutive integers as values.    The method .strs(names)
    assigns each variable name to itself (that is variable 'v' has value 'v').
    The method .vals(a=99, b=200) allows you to assign any value to variables.
    A 'list of variable names' can also be a string, which will be .split().
    The method .end() returns one more than the maximum int value.
    Example: opcodes = Enum("add sub load store").vals(illegal=255)."""
  
    def __init__(self, names=[]): self.ints(names)

    def set(self, var, val):
        """Set var to the value val in the enum."""
        if var in vars(self).keys(): raise AttributeError("duplicate var in enum")
        if val in vars(self).values(): raise ValueError("duplicate value in enum")
        vars(self)[var] = val
        return self
  
    def strs(self, names):
        """Set each of the names to itself (as a string) in the enum."""
        for var in self._parse(names): self.set(var, var)
        return self

    def ints(self, names):
        """Set each of the names to the next highest int in the enum."""
        for var in self._parse(names): self.set(var, self.end())
        return self

    def vals(self, **entries):
        """Set each of var=val pairs in the enum."""
        for (var, val) in entries.items(): self.set(var, val)
        return self

    def end(self):
        """One more than the largest int value in the enum, or 0 if none."""
        try: return max([x for x in vars(self).values() if type(x)==type(0)]) + 1
        except ValueError: return 0
    
    def _parse(self, names):
        ### If names is a string, parse it as a list of names.
        if type(names) == type(""): return names.split()
        else: return names

使い方の例を示します。

>>> opcodes = Enum("add sub load store").vals(illegal=255)
>>> opcodes.add
0
>>> opcodes.illegal
255
>>> opcodes.end()
256
>>> dir(opcodes)
['add', 'illegal', 'load', 'store', 'sub']
>>> vars(opcodes)
{'store': 3, 'sub': 1, 'add': 0, 'illegal': 255, 'load': 2}
>>> vars(opcodes).values()
[3, 1, 0, 255, 2]

注意点して欲しいのは、メソッドが階層化されていて、そのためコンストラクタの後で、.strs、.ins、.valsを1行の中に組み合わせられるということです。dirとvalを使う際、自分で定義した変数以外であれば、これらは何と一緒に使われても乱雑さとは無縁です。列挙される変数のすべてをイテレートするために、for x in vars(opcodes).values()が使えます。また、integerではない値を列挙型変数の値として使いたい場合、.strsと.valsメソッドを使います。最後に、変数の名前または値を複製しようとするとエラーになります。時によっては値を複製したいと思うこともあるでしょう(例:エイリアスのため)。そのような時には、ValueErrorを送出している行を削除するか、例えばvars(opcodes)['first_op'] = 0というように使います。もし最も嫌な点を挙げれば、それはvalsとvaluesが混乱する可能性です。valsのためにもっと良い名前を考えた方が良いかもしれません。

Q:なぜPythonにはデータ型``Set''が無いの?

この質問が最初にポストされたときにはありませんでした。多くのプログラマーは、Set型の代わりに辞書を使っていましたが、最新のPython 2.4では 集合(set)型_ がネイティブでサポートされています。

Q:Boolean型を使うべきですか?

この質問が最初にポストされたとき、PythonにはBoolean型がありませんでしたが、Python 2.3からは bool型があります。

Q:Pythonで(評価式 ? 真の場合 : 偽の場合)というコードと同じことはできますか?

JavaやCには三項演算子(評価式 ? 真の場合 : 偽の場合)があります。Pythonはこの演算子の導入に抵抗していますが、やがてリリースされるPython 2.5には(真の場合 if 評価式 else 偽の場合)という書式で許可されることになります。これにより、式と文が明確に区別されているというPythonの特徴が侵されてしまいますが、この判断はたくさんの人の要望に対する妥協なのです。

Python 2.5がやってくるまではどうしたらいいか?いくつかのオプションがあります。

  1. [偽の場合, 真の場合][評価式]としてみる。注意すべきは、この式が偽の場合、真の場合の両者を評価しており、再帰的な呼び出しやコストのかかる計算には向かないことです。testがブール型でない値を返すなら、試して見るといいでしょう。
  2. [真の場合, 偽の場合][not 評価式]これらはいずれも読みやすくありません。
  3. 評価式 and 真の場合 or 偽の場合 いかにもそれらしいと思う人も、混乱するという人もいるでしょう。これは真の場合が偽でないと保証された場合にのみ有効です。
  4. (評価式 and [真の場合] or [偽の場合])[0] ならこの制約を避けられます。
  5. [lambda: 真の場合, lambda: 偽の場合][not not 評価式]()は、(可読性を除いて)ほとんどすべての制限を回避しますが、私はこれを使うことをお勧めできません。この書式を関数の中にラップすることだってできるのです。任意のキーワードと同じ挙動をする変数の、承認されている命名規則では、後にアンダースコアを付加します。そんな訳で次。
  6. if_(評価式, 真の場合, lambda: 偽の場合) ここで私たちはこう定義します。
def if_(test, result, alternative=None):
    "testが真ならresultを、偽であればalternativeを'実行'する。'実行'は対象が呼び出し可能であれば呼び出し"
    if test:
        if callable(result): result = result()
        return result
    else:
        if callable(alternative): alternative = alternative()
        return alternative

>>> fact = lambda n: if_(n <= 1, 1, lambda: n * fact(n-1))
>>> fact(6)
720
  1. 今や、幾つかの理由によって、あなたは"if (評価式) ..."より "if(評価式, ..."という書式をとても好むようになります(そして、偽の場合の式から離れられなくなる)。こうすることができます。
def _if(test):
    return lambda alternative: \
               lambda result: \
                   [delay(result), delay(alternative)][not not test]()

def delay(f):
    if callable(f): return f
    else: return lambda: f

>>> fact = lambda n: _if (n <= 1) (1) (lambda: n * fact(n-1))
>>> fact(100)
9332621544394415268169923885626670049071596826438162146859296
3895217599993229915608941463976156518286253697920827223758251
185210916864000000000000000000000000L

模試キmガコレヲヨmail奈良、キmハ館sue gottaゲン呉de shockヲeラレルhas陀(ナンdemoヨイノ奈良)

Q:なぜ、良くある型のいくつかがPythonには無いの?

Pythonの優れた点の1つに、数値型、文字列、リスト、辞書(そして今や集合とブール型)と、(データ型に関して)たくさんの選択肢を持っているという点があります。しかし、いくつかの主要な型がまだ漏れています。私自身にとって、最も重要なものはミュータブルな文字列です。文字列 += xという処理を何度も繰り返すのは遅いですし、複数の文字からなるリスト(または部分文字列のリスト)を操作する時には、いくつもの有益な文字列関数の使用を断念しなくてはなりません。1つめの可能性がarray.array('c')です。もう1つにはUserString.MutableStringがあり、その目的とする利用方法は、実践的というより教育的なものです。3つめはmmapモジュールであり、4つめがcStringIOです。この両者とも完全なものではありませんが、これらの両者とも十分な選択肢を提供しています。これらの後で、私はよく、ソートを持ったキューを欲しがっていたことを思い出しました。標準ライブラリには Queueモジュール がありますが、これはスレッドのキューに特化しています。あまりにもたくさんのオプションがあるため、標準ライブラリのQueueには近づきたくありません。しかし、自分で実装した3種類のキュー、つまりFIFO、LIFO、優先順位付きを提案するつもりです。

"""
This module provides three types of queues, with these constructors:
  Stack([items])  -- Create a Last In First Out queue, implemented as a list
  Queue([items])  -- Create a First In First Out queue
  PriorityQueue([items]) -- Create a queue where minimum item (by <) is first
Here [items] is an optional list of initial items; if omitted, queue is empty.
Each type supports the following methods and functions:
  len(q)          -- number of items in q (also q.__len__())
  q.append(item)  -- add an item to the queue
  q.extend(items) -- add each of the items to the queue
  q.pop()         -- remove and return the "first" item from the queue
"""

def Stack(items=None):
    "A stack, or last-in-first-out queue, is implemented as a list."
    return items or []
    
class Queue:
    "A first-in-first-out queue."
    def __init__(self, items=None): self.start = 0; self.A = items or []
    def __len__(self):                return len(self.A) - self.start
    def append(self, item):           self.A.append(item)
    def extend(self, items):          self.A.extend(items)

    def pop(self):
        A = self.A
        item = A[self.start]
        self.start += 1
        if self.start > 100 and self.start > len(A)/2:
            del A[:self.start]
            self.start = 0
        return item
    
class PriorityQueue:
    "A queue in which the minimum element (as determined by cmp) is first."
    def __init__(self, items=None, cmp=operator.lt):
          self.A = []; self.cmp = cmp;
          if items: self.extend(items)
      
    def __len__(self): return len(self.A)
    
    def append(self, item):
        A, cmp = self.A, self.cmp
        A.append(item)
        i = len(A) - 1
        while i > 0 and cmp(item, A[i//2]):
            A[i], i = A[i//2], i//2
        A[i] = item
    
    def extend(self, items):
        for item in items: self.append(item)
    
    def pop(self):
        A = self.A
        if len(A) == 1: return A.pop()
        e = A[0]
        A[0] = A.pop()
        self.heapify(0)
        return e
    
    def heapify(self, i):
        "Assumes A is an array whose left and right children are heaps,"
        "move A[i] into the correct position.  See CLR&S p. 130"
        A, cmp = self.A, self.cmp
        left, right, N = 2*i + 1, 2*i + 2, len(A)-1
        if left <= N and cmp(A[left], A[i]):
            smallest = left
        else:
            smallest = i
        if right <= N and cmp(A[right], A[smallest]):
            smallest = right
        if smallest != i:
            A[i], A[smallest] = A[smallest], A[i]
            self.heapify(smallest)

``items or []''という表現に注意しましょう。これは以下のようなことをするには、とても悪い表現です。

def Stack(items=[]): return items

この例が示すように、デフォルトでは要素なしの、空のリストです。何かを行なえば、異なるスタックが同じリストを共有します。デフォルトの値をNoneにすることで(falseという値は、正しい入力範囲から外れる)自分で配列の並び替えを行なえるため、それぞれのインスタンスが生のリストを個別に持ちます。この形式を使うことで、あり得る難点を例示します。

s = Stack(items)

上記のようなことをしたいユーザーは、sとitemが同一になることに気づくかもしれませんが、それはitemsが空の時にしか起こりません。私が言いたいのは、この難点はそれほど深刻なものではない、なぜならこのような約束ごとは明示的に行なわれるからです(代わりに、ユーザーはitemsが変更されていないことに気づくかもしれません。それはitemsが空っぽのときにだけあり得ます)。

Q:PythonでSingletonパターンはどうやるの?

あなたが期待しているのがインスタンスを1度だけ作成でき、別のインスタンスを作成しようとすると例外を送出するクラスだと仮定してお話します。私の知るうちで最も簡単な方法は、方針を強制するような関数を定義して、その関数をクラスのコンストラクタで呼び出すようにすることです。

def singleton(object, instantiated=[]):
    "Raise an exception if an object of this class has been instantiated before."
    assert object.__class__ not in instantiated, \
        "%s is a Singleton class but is already instantiated" % object.__class__
    instantiated.append(object.__class__)
    
class YourClass:
    "A singleton class to do something ..."
    def __init__(self, args):
        singleton(self)
        ...

メタクラスで複雑にすることもできます、その方法ではクラスをYourClass(Singleton)と書くことができますが、なぜ面倒なのでしょうか。4人組(GoF)以前、すべての研究者が(公式な名前でない)``singleton''は簡単なアイデアであり、簡潔なコードで実装するのがふさわしく、総合的な宗教などではないとしているのです。

Q:"ニュース"が無いのは良いニュース?

あなたの質問は、Pythonに new キーワードが無いのは良いことか、と仮定します。実に良いじゃないですか。C++では、 new はスタックというよりヒープ割り当ての目印に使われます。そんなわけで、このキーワードは有効です。Javaでは、すべてのオブジェクトはヒープに割り当てられています。そのため new には現実的な意味がありません。それはコンストラクタと、その他の静的なメソッドを区別する覚え書きとして用意されているに過ぎません。でもこの区別は、おそらくJavaに利益以上の損害を与えているでしょう。それはこの識別子が低水準のものであり、実際には遅らせられるべき実装上の判断を強制するからです。私はPythonが、コンストラクタ呼び出しを一般の関数呼び出しと同じ文法にするという、正しい選択をしたと思っています。

例をあげると、boolクラスの登場以前に、私達がその働きをするクラスを実装したいと思ったとします。このクラスを組み込み型と区別するためBoolと呼びます。このとき、Bool型のオブジェクトは、trueとfalseが1つづつしか存在できないといった方針を強制したいとします。これを実装する方法の1つは、Boolクラスの名前を_Boolに変更することです(そのためこのクラスを公開できなくなります)、そしてBool関数は次のように定義できます。

def Bool(val):
    if val: return true
    else: return false
    
true, false = _Bool(1), _Bool(0)

このコードは、(確かに一般化されてない、許容量の小さなものではあるけれど)関数Boolを_Boolオブジェクトのファクトリとしています。ポイントはBool(1)を呼ぶプログラマは返されるオブジェクトが新しく生成されたものか、使い回しなのかを(少なくとも不変のオブジェクトでは)知らなくても構わないという点です。Javaとは異なり、Pythonの文法は、この違いの隠蔽を許容します。

ここにはある文学的な混乱があります。ある人々は"Singletonパターン"という語彙をこのタイプのファクトリを表すのに用います。これはコンストラクタに引数として与えられる、それぞれ異なるsingletonオブジェクトです。私が賛成し、信じられるものは、前の質問にあるSingletonの定義です。このパターンをクラスの中に隠蔽することもできます。我々はこれを"CachedFactory"と呼んでいます。このアイデアはこんな風に書けます。

class Bool:
    ... ## see_here_ for Bool's definition
    
Bool = CachedFactory(Bool)

この関数の引数にリストの(1,)与えてBool(1)を呼び出すと、1度目はオリジナルのBoolクラスを委譲したオブジェクトを得ることになります。しかし次回以降のBool(1)の呼び出しは、キャッシュに保持されている最初のオブジェクトを返します。

class CachedFactory:
    def __init__(self, klass):
        self.cache = {}
        self.klass = klass
    
    def __call__(self, *args):
        if self.cache.has_key(args):
            return self.cache[args]
        else:
            object = self.cache[args] = self.klass(*args)
            return object

気を付けなくてはならないことの1つに、クラスとコンストラクタには何も残されていないということです。このパターンは、呼びだし可能な何とでも動作します。普通に関数に適用すれば、"Memoizationパターン"と呼ばれるでしょう。実装は同じで、名前が変わります。

class Memoize:
    def __init__(self, fn):
        self.cache = {}
        self.fn = fn
    
    def __call__(self, *args):
        if self.cache.has_key(args):
            return self.cache[args]
        else:
            object = self.cache[args] = self.fn(*args)
            return object

こうして、fact = Memoize(fact)が可能になり、O(n)ではなくO(1)だけの時間で階乗を計算できるようになりました。

Q:シェルみたいなヒストリの機構ができますか?

ええ。こんなのがお望みですか?

>>> from shellhistory import h
h[2] >>> 7*8
56
h[3] >>> 9*9
81
h[4] >>> h[2]
56
h[5] >>> 'hello' + ' world'
'hello world'
h[6] >>> h
[None, 9, 56, 81, 56, 'hello world']
h[7] >>> h[5] * 2
'hello worldhello world'
h[8] >>>  h[7] is _ is h[-1]
1

これがどんな風にうごいているか?変数sys.ps1はシステムプロンプトです。デフォルトで、これは'>>>'という文字列ですが、何でもほかのものに変えられます。文字列以外のオブジェクトを指定すると、そのオブジェクトの__str__メソッドが呼ばれます。なので我々は、文字列メソッドが(ヒストリを表わす)hというリストに(変数 _ のような)直近の結果を追加するようなオブジェクトを作成することにします。そして'>>>'の後にその文字列を含めたプロンプト文字列を返します。少なくとも、このような計画となります。(少なくともIDLE 2.2のWindowsでは)、sys.ps1.__str__はプロンプトが表示される前に、1度ではなく3度呼ばれます。何故かは聞かないでください。この現象と戦うため、ヒストリのリストの最後の要素に存在しない時にだけ、_ の内容を追加するようにしました。また、わざわざNoneをヒストリのリストに追加しません。それはPythonの対話型シェルのループで表示されないようにするためです。またhにそれを追加しないのは、循環性は問題点を表示したり、比較したりすることにつながるからです。もうひとつ事態を複雑にしているのは、Pythonのインタプリタが'n' + sys.ps1を実際に表示しようとすることです。('n'を個別に表示すべき、あるいは'n' + str(sys.ps1)を表示するなら)それはsys.ps1に__radd__メソッドが必要だということを意味します。最後に、私のクラスの最初のバージョンでは、Pythonのセッション(または.pythonの起動ファイル)で一番最初の入力を取り込むと終了してしまっていました。しばらく調査した後で、変数 _ は最初の式が評価されるまでは束縛されないことが分かりました。そのため私は _ が解放されている場合に例外を補足しています。このコードはわれわれに与えられています。

import sys
    
h = [None]
    
class Prompt:
    "Create a prompt that stores results (i.e. _) in the array h."
    def __init__(self, str='h[%d] >>> '):
        self.str = str;
        
    def __str__(self):
        try:
            if _ not in [h[-1], None, h]: h.append(_);
        except NameError:
            pass
        return self.str % len(h);
    
    def __radd__(self, other):
        return str(other) + str(self)
    
sys.ps1 = Prompt()

Q:私の関数の実行時間は、どんな風に計ればいい?

簡潔な答えがあります。

def timer(fn, *args):
    "Time the application of fn to args. Return (result, seconds)."
    import time
    start = time.clock()
    return fn(*args), time.clock() - start

>>>timer(max, range(1e6))
(999999, 0.4921875)

これは 自作のユーティリティモジュール を使った、もう少し複雑な答えです。

Q:起動ファイル.pythonをどんな風にしてる?

現在のところ、こんな感じだけれど、これまでも大きく変わり続けてきました。

from __future__ import nested_scopes
import sys, os, string, time
from utils import *

################ Interactive Prompt and Debugging ################
    
try:
    import readline
except ImportError:
    print "Module readline not available."
else:
    import rlcompleter
    readline.parse_and_bind("tab: complete")
    
h = [None]
    
class Prompt:
    def __init__(self, str='h[%d] >>> '):
        self.str = str;
    
    def __str__(self):
        try:
            if _ not in [h[-1], None, h]: h.append(_);
        except NameError:
           pass
        return self.str % len(h);

  def __radd__(self, other):
        return str(other) + str(self)
  
if os.environ.get('TERM') in [ 'xterm', 'vt100' ]:
    sys.ps1 = Prompt('\001\033[0:1;31m\002h[%d] >>> \001\033[0m\002')
else:
    sys.ps1 = Prompt()
sys.ps2 = ''

Amit J. Patel, Max M, Dan Winkler, Chih-Chung Chang, Bruce Eckel, Kalle Svensson, Mike Orr, Steven Rogers、そのほかアイデアや情報収集に貢献してくれた人々へ。

Peter Norvig


from translator:

この文書は、 http://www.norvig.com/python-iaq.html で公開されているエッセイ「The Python IAQ:Infrequently Answered Questions」の日本語訳です。

筆者のPeter Norvig氏といえば今ではGoogleのサーチ担当ディレクタを勤めているけれど、「Artificial Intelligence: A Modern Approach」の著者としても有名な方。こちらのサイト http://aima.cs.berkeley.edu/ には、書籍のサンプルコードのPython版なども掲載されています。そういえば第2版の日本語版が計画されているというウワサを聞いたことがあります。大著だからそうそう簡単には出てこないと思うけれど、あれはどうなっているのだろう?

この文書は、Daily Python URLか何かで見つけたんだったような。Python 2.4の登場に合わせてSet型やBoolean型の内容が変わっていたり、結構メンテナンスされている模様。きっと2.5が出たら三項演算子についても書き変わるんだろうなあ。パターンについてや三項演算子についての文章を読むと、Norvig氏のPythonに対するスタンスが良く分かり、別の意味でも面白いです。

翻訳に手をつけはじめたのは、けっこう前のことだったのですが、思った以上に時間をかけてしまいました。文章中に出てくる 『C++の設計と進化』 の文章については、同書の日本語版(ソフトバンク、2005)より引用しております。誤訳やタイプミスなどありましたら、turky __at__ peignot.netまでご連絡くださいませ。

最後に、この日本語訳については、Peter Norvig氏の許可を得て公開しております。快諾をいただいたNorvig氏に深く感謝いたします。

Comments