バリケンのPython日記 RSSフィード

2008-12-09

[] イテレータとジェネレータ(3)  イテレータとジェネレータ(3) - バリケンのPython日記 を含むブックマーク はてなブックマーク -  イテレータとジェネレータ(3) - バリケンのPython日記  イテレータとジェネレータ(3) - バリケンのPython日記 のブックマークコメント

この間の続きだよ。

じゃあ、「イテレータ」と「ジェネレータ」に対する実験結果から、「イテレータとは何か」「ジェネレータとは何か」についてまとめてみるね。

イテレータとは

まずはイテレータだよ。

イテレータがなぜうれしいかと言うと、たとえば構造を持ったオブジェクトの大きさを知らなくても、StopIteration例外が発生するまでnext()メソッドを呼ぶことで全ての要素を参照することが出来たりするよ。

実はこれはPythonのfor文が内部的にやっていることだよ。

for x in 構造を持ったオブジェクト:
  処理

のように書くと、まず構造を持ったオブジェクト引数としてiter()関数を呼んでイテレータを生成し、それからStopIteration例外が発生するまでイテレータに対してnext()メソッドを呼んで、その戻り値をxに代入して処理を実行する、ということを繰り返すのがfor文のしくみだよ。

ジェネレータとは

次はジェネレータだよ。

  • ジェネレータとは、値の生成器である
  • イテレータとは異なり、必ずしも「構造を持ったオブジェクトの要素を繰り返し参照するもの」ではない
  • ジェネレータに対してnext()メソッドを呼ぶと、何らかの値が返ってくる
  • 次に返すべき要素がないジェネレータに対してnext()メソッドを呼ぶとStopIteration例外を発生するが、イテレータと異なり必ず「最後の要素」があるわけではない(無限に要素を返すジェネレータもある)
  • ジェネレータの作り方は、ジェネレータ式を使う方法とyield文を使う方法とがある

ジェネレータ式についてはこの間のエントリで軽く触れたけど、次回はyield文を使ったジェネレータの生成方法について勉強してみるよ。

トラックバック - http://python.g.hatena.ne.jp/muscovyduck/20081209

2008-10-28

[] イテレータとジェネレータ(2)  イテレータとジェネレータ(2) - バリケンのPython日記 を含むブックマーク はてなブックマーク -  イテレータとジェネレータ(2) - バリケンのPython日記  イテレータとジェネレータ(2) - バリケンのPython日記 のブックマークコメント

この間の続きだよ。

この間は「ジェネレータ」について挙動を調べてみたけど、今回は「イテレータ」についての挙動を調べてみるよ。

またいきなり天下りな感じだけど、iter()という関数があるみたいだよ。この関数は、引数オブジェクトに対して__iter__()メソッドを呼んで、その戻り値を返すみたい。だから、

iter([1,2,3,4,5])

と、

[1,2,3,4,5].__iter__()

は同じことみたいだよ。ちょっと実験してみよう!

>>> iter([1,2,3,4,5])
<listiterator object at 0x00C38A10>
>>> 

おや、「リストイテレータオブジェクト」というものが返ってきているね。じゃあ、前回同様変数に代入して、色々と調べてみよう!

>>> i = iter([1,2,3,4,5])
>>> dir(i)
['__class__', '__delattr__', '__doc__', '__getattribute__', '__hash__', '__init_
_', '__iter__', '__length_hint__', '__new__', '__reduce__', '__reduce_ex__', '__
repr__', '__setattr__', '__str__', 'next']
>>> i.next()
1
>>> i.next()
2
>>> i.next()
3
>>> i.next()
4
>>> i.next()
5
>>> i.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

どうやら、このオブジェクトはnext()メソッドを呼ぶたびに生成元のリストの要素を一つずつ返すみたいだね。

あと、次の要素がもうない状態でnext()メソッドを実行すると、StopIterationという例外が発生するみたいだよ。前回の「ジェネレータ」に挙動は良く似ているね。

トラックバック - http://python.g.hatena.ne.jp/muscovyduck/20081028

2008-10-16

[] イテレータとジェネレータ(1)  イテレータとジェネレータ(1) - バリケンのPython日記 を含むブックマーク はてなブックマーク -  イテレータとジェネレータ(1) - バリケンのPython日記  イテレータとジェネレータ(1) - バリケンのPython日記 のブックマークコメント

Pythonの「イテレータ」と「ジェネレータ」がよく分かってないから、勉強してみるよ。

と言っても、いきなり「イテレータ」や「ジェネレータ」の説明を読んでも何だか分からないから、まずはPythonシェルで色々と動作させて実験してみるよ。

以前リスト内包表記について勉強したけど、たとえば3の倍数が順に10個入ったリストが欲しいときは、

>>> [x * 3 for x in range(10)]
[0, 3, 6, 9, 12, 15, 18, 21, 24, 27]
>>>

のように書いたよね。この「[x * 3 for x in range(10)]」の角カッコを丸カッコに変えるとどうなるかな?

>>> (x * 3 for x in range(10))
<generator object at 0x00C3AD78>
>>>

おや、「ジェネレータオブジェクト」というものが返ってきているね。

このオブジェクトが一体何なのかを調べてみよう!まずは変数に代入して、dir関数でどんなメソッドや属性を持っているかを調べてみるね。

>>> g = (x * 3 for x in range(10))
>>> dir(g)
['__class__', '__delattr__', '__doc__', '__getattribute__', '__hash__', '__init_
_', '__iter__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr
__', '__str__', 'close', 'gi_frame', 'gi_running', 'next', 'send', 'throw']
>>>

じゃあ、ちょっと天下り的だけど、このオブジェクトに対して「next()」メソッドを何度か呼んでみよう!

>>> g.next()
0
>>> g.next()
3
>>> g.next()
6
>>>

どうやら、このジェネレータオブジェクトはnext()メソッドを呼ぶたびに3の倍数を順に返すみたいだね。

リスト内包表記の場合はメモリを確保して一気に計算してリストを返すけど、ジェネレータオブジェクトの場合はnext()メソッドが呼ばれてから計算しているから、計算資源とメモリにやさしいみたいだよ。

あと、要素がなくなってからnext()メソッドを呼ぶとどうなるかも試してみよう!さっきの続きから試してみると、

>>> g.next()
9
>>> g.next()
12
>>> g.next()
15
>>> g.next()
18
>>> g.next()
21
>>> g.next()
24
>>> g.next()
27
>>> g.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

となって、次の要素がもうないジェネレータオブジェクトに対してnext()メソッドを実行すると、StopIterationという例外が発生するみたいだよ。

2008-10-15

[] クラス(8)  クラス(8) - バリケンのPython日記 を含むブックマーク はてなブックマーク -  クラス(8) - バリケンのPython日記  クラス(8) - バリケンのPython日記 のブックマークコメント

前回もちょこっと触れたけど、Pythonではインスタンス変数カプセル化されないよ。だから、再代入できちゃうよ。

>>> class Person(object):
...   def __init__(self, name, age):
...     self.name = name
...     self.age = age
...   def say(self):
...     print 'My name is %s, I am %d years old.' % (self.name, self.age)
...
>>> person = Person('Taro', 15)
>>> person.say()
My name is Taro, I am 15 years old.
>>> person.name = 'Hanako'
>>> person.age = 17
>>> person.say()
My name is Hanako, I am 17 years old.
>>>

これを防ぐには、インスタンス変数名の先頭にアンダースコアを二つつけるよ。

>>> class Person(object):
...   def __init__(self, name, age):
...     self.__name = name
...     self.__age = age
...   def say(self):
...     print 'My name is %s, I am %d years old.' % (self.__name, self.__age)
...
>>> person = Person('Taro', 15)
>>> person.say()
My name is Taro, I am 15 years old.
>>> person.__name = 'hanako'
>>> person.__age = 17
>>> person.say()
My name is Taro, I am 15 years old.
>>>

実際にはPythonの内部的に別の変数名に変換('_Person__age', '_Person__name')しているだけなので、本気で書き換えようと思えば書き換えることができちゃうよ。

>>> dir(person)
['_Person__age', '_Person__name', '__age', '__class__', '__delattr__', '__dict__
', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__name'
, '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__'
, '__weakref__', 'say']
>>> person._Person__name = 'Hanako'
>>> person._Person__age = 17
>>> person.say()
My name is Hanako, I am 17 years old.
>>>
トラックバック - http://python.g.hatena.ne.jp/muscovyduck/20081015

2008-10-01

[] クラス(7)  クラス(7) - バリケンのPython日記 を含むブックマーク はてなブックマーク -  クラス(7) - バリケンのPython日記  クラス(7) - バリケンのPython日記 のブックマークコメント

メソッド定義

Pythonのクラスにメソッドを定義する場合は、通常の関数定義とは異なり特殊なルールに従う必要があるよ。

その特殊なルールとは、「第一引数に必ずselfを指定すること」だよ。そして、そのクラスのインスタンスを生成して実際にそのメソッドを呼び出すと、自動的にその第一引数インスタンスが代入された状態でメソッドが呼び出されるよ。なので、メソッド呼び出し時に与える引数は、メソッド定義の引数の数よりも一つ少なくなるよ。

あと前回ちょこっと触れたけど、__init__()メソッドという特別なメソッドがあるよ。このメソッドを定義しておくと、インスタンス生成時に自動的にこの __init__()メソッドが呼び出されるよ。なので、インスタンス変数初期化はこの__init__()メソッド内で実施するといいんじゃないかな。

説明を聞いてもピンと来ないから、実際にクラスを定義して、インスタンスを生成して、メソッドを呼び出してみよう!ここでは例として、インスタンス変数nameとageを持ち、sayメソッドで自己紹介をするクラスPersonを定義してみるよ。Pythonではクラスオブジェクトからインスタンスを生成するには、関数呼び出しのようにカッコをつけるよ。

class Person(object):
  def __init__(self, name, age):
    self.name = name
    self.age = age
  def say(self):
    print 'My name is %s, I am %d years old.' % (self.name, self.age)

person = Person('Taro', 15)
person.say()

実行結果だよ。

My name is Taro, I am 15 years old.

おまけで、ほぼ等価なRubyコードを書いてみるよ。

class Person
  def initialize(name, age)
    @name = name
    @age = age
  end
  attr_accessor :name, :age

  def say
    puts "My name is #{@name}, I am #{@age} years old."
  end
end

person = Person.new('Taro', 15)
person.say

Rubyが分かる人なら「なぜattr_accessorを指定しているの?」と疑問を持つかもしれないけど、それはまたこんど。

トラックバック - http://python.g.hatena.ne.jp/muscovyduck/20081001