Sunday, November 9, 2014

Python part2

思うところあってPython(3.4.2)を勉強中。公式サイトのドキュメントを見よう真似で

4. More Control Flow Tools

4.1 if Statements

最もよく知られたステートメント型はifで間違いない。

x = int(input("Please enter an interger: "))

if x < 0:
    x = 0
    print('Negative changed to zero')
elif x == 0:
    print('Zero')
elif x == 1:
    print('Single')
else:
    print('More')

実行結果。

$ py ifstmt.py
Please enter an interger: 100
More
[c:python]
$ py ifstmt.py
Please enter an interger: 1
Single
[c:python]
$ py ifstmt.py
Please enter an interger: 0
Zero
[c:python]
$ py ifstmt.py
Please enter an interger: -19
Negative changed to zero

elifは0あるいは複数あっていい。elseはオプションだ。if, elif,elif…のシークエンスはswitch, caseの代替手段になっている。

4.2 for Statement

Pythonでのfor文は、CPascalとは若干異なる。Pascalのような数学的な数の繰り返し、あるいはCのような繰り返しのステップとその停止条件の定義というよりは、あらゆるシークエンス(ListやString)内の要素の繰り返しをする。

words = ['yukari', 'maki', 'zunko']
for w in words:
    print(w, len(w))

実行結果。

$ py iterate.py
yukari 6
maki 4
zunko 5

ループの内側でシークエンスに対する改変を行いたい場合、まずコピーを作ることをお勧めする。シークエンス上での繰り返しはcopyを暗黙的に作るわけではない。スライス表記がこの目的で使えるだろう。

words = ['yukari', 'maki', 'zunko']
for w in words:
    print(w, len(w))

for w in words[:]: # Loop over slice copy of the entire list.
    if len(w) > 5:
        words.insert(0, w)
print('words', words)

実行結果。

$ py iterate.py
yukari 6
maki 4
zunko 5
words ['yukari', 'yukari', 'maki', 'zunko']

4.3 The range() Function

数列上(0,1,2,…)での繰り返しを実装したい場合は、ビルトイン関数range()を使うといい。数学的なprogression(直訳で増殖。訳すのメンドイ)を生成してくれる。

>>> for i in range(5):
...     print(i)
...
0
1
2
3
4

与えた終点は生成されたシーケンスに含まれない。range(10)は10個の値を生成する。レンジの開始点を別の値に設定することができるほか、増分(減産分)を指定することもできる。(ステップということもある)

>>> for i in range(5, 10):
...     print(i)
...
5
6
7
8
9
>>> for i in range(0, 10, 3):
...     print(i)
...
0
3
6
9
>>> for i in range(-10, -100, -30):
...     print(i)
...
-10
-40
-70

シーケンスのインデックスを使った繰り返しを実現したい場合はrange()len()を組み合わせることができる。

words = ['yukari', 'maki', 'zunko']
for w in words:
    print(w, len(w))

for w in words[:]: # Loop over slice copy of the entire list.
    if len(w) > 5:
        words.insert(0, w)
print('words', words)

for i in range(len(words)):
    print(i, words[i])

実行結果。

$ py iterate.py
yukari 6
maki 4
zunko 5
words ['yukari', 'yukari', 'maki', 'zunko']
0 yukari
1 yukari
2 maki
3 zunko

大半のこういうケースでは、enumerate()を使うと便利だ。ループのテクニックを参照してほしい。(for i, v in enumerate(['tic', 'tac', 'toe']):。なるほど理解。)

単にrangeを出力するとおかしなことになるはずだ。

>>> print(range(10))
range(0, 10)

range()によって返されるオブジェクトはリストのように振る舞うが、実際のところは違う。このオブジェクトは、繰り返しの中で期待するシークエンスの次のアイテムを返すものだ。リストを作っているわけではない。つまりスペースを削減している。

こういったオブジェクトをiterableと呼び、供給が可能な限り継続的に次のアイテムを生産する。for文は繰り返しであることを見てきたが、関数listは別だ。これはiterableオブジェクトからリストを作り出す。

>>> list(range(5))
[0, 1, 2, 3, 4]

後ほど、iterableオブジェクトを返す関数や、iterableを引数に取る関数を見ていく。

4.4 break and continue Statements, and else Caluses on Loops

break文はCのようにforwhileループの処理部から脱出する。

ループ文はelseの節を持つことがある。この節は、ループの中でリストが枯渇(for文)、または条件がfalseになる(while文)時に実行される。ただし、ループがbreakにより中断したときは実行されない。下記の例では、この特性を使って素数をピックアップしている。

primes.py

for n in range(2,10):
    for x in range(2, n):
        if n % x == 0:
            print(n, 'equals', x, '*', n//x)
            break
    else:
        print(n, 'is a prime number.')

実行結果。

$ py primes.py
2 is a prime number.
3 is a prime number.
4 equals 2 * 2
5 is a prime number.
6 equals 2 * 3
7 is a prime number.
8 equals 2 * 4
9 equals 3 * 3

よく見てほしい。elseは、ifではなくforに属している。

ループと一緒に使うとき、elseは、if文とセットで使用するより、try文のelse部として利用するのがより一般的だ。try文のelse部は、例外が発生しなかった時に実行される。または、ループ文のelse部は、breakが発生しなかったときに実行される。try文に関する詳細はHandling Exceptionsで見ていく。

continue文は、C同様、ループ内の次の繰り返しを実行する。

even.py

for num in range(2, 10):
        if num % 2 == 0:
            print("Found an even number", num)
            continue
        print("Found a number", num)

実行結果。

$ py even.py
Found an even number 2
Found a number 3
Found an even number 4
Found a number 5
Found an even number 6
Found a number 7
Found an even number 8
Found a number 9

3.4 pass Statements

pass文は何もしない。これはステートメントが構文的に必要だが、プログラムは何もする必要が無い場合に使える。たとえば、

>>> while True:
...     pass # Busy-wait for keyboard interrupt (Ctrl+C)
...

passは、一般に最少のクラスを作るために使われている。

>>> class MyEmptyClass:
...     pass
...
>>>

passが使われている他の場所は、関数のプレースホルダ、あるいは新しいコードを書いているときの条件付きの本体だ。これにより、もっと抽象的なレベルで検討を続けることができる。passは特にわーわー言われることなく無視される。

>>> def initlog(*args):
...     pass # remember to implement this!!
...
>>> initlog
<function initlog at 0x000000000296CBF8>
>>>
>>> initlog('a')
>>>

4.6 Defining Functions

任意の上限までフィボナッチ数列を生成する関数を作ってみよう。

def fib(n):
    """Print a Fibonacci series up to n."""
    a, b = 0, 1
    while b < n:
        print(b, end=' ')
        a, b = b, a + b
    print()

fib(2000)

$ py fibonacchi.py
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

キーワードdefで関数を定義する。続いて関数名と丸括弧内にパラメータのリストが続かなければならない。関数の処理本体は次の行から始まる。インデントされている必要がある。

関数の最初の行はオプションであり文章だ。この文章は関数のドキュメント(docstring)だ。docstringに関する詳細はDocumentation Stringsで扱う。docstringで、オンラインまたは印刷用文書を自動的に生成するツールがある。また、利用者にコードを通して情報を与える。書いたコードに対してdocstringを付与しておくようにしよう。

関数の実行は関数のローカル変数用にシンボルテーブルを導入するところから始まる。もう少し正確にいうと、関数内で割り当てられた全ての変数はローカルシンボルテーブル内に値を格納する。一方で変数の参照はローカルシンボルテーブル内に最初に見つかる。続いて上位関数のローカルシンボルテーブル、グローバルシンボルテーブル、そして最後にビルトイン名のテーブル。従ってグローバル変数は関数の中では、(global文無しでは)直接、値を割り当てることはできない(関数内で参照できるシンボルテーブルにはないってことか)。

関数コールでの実際のパラメータ(引数)は呼び出された関数のローカルシンボルテーブル内に、それがコールされた時に導入される。引数はcall by value(値は常にオブジェクトへの参照であり、オブジェクトの値ではない)で渡される。関数が別の関数を呼び出す場合、新たなローカルシンボルテーブルが呼び出し時に作られる。

関数定義は現在のシンボルテーブル内に関数名を導入する。関数名の値はユーザ定義関数としてインタプリタによって認識される型を持っている。この値には別の名前を割り当てることができ。これも関数として使える。一般的なリネームの仕組みだ。

>>> def fib(n):
...     pass
...
>>> fib
<function fib at 0x0000000002AAC9D8>
>>> f = fib
>>> f(100)

シンボルテーブルってのは現在のコンテキストが持つオブジェクトを格納しているテーブルのことか。locals(), globals()で中身を覗けた。

>>> locals()
{'f': <function fib at 0x0000000002AAC9D8>, '__doc__': None, '__name__': '__main__', '__spec__': None, '__package__': None, '__builtins__': <module 'builtins' (built-in)>, 'MyEmptyClass': <class '__main__.MyEmptyClass'>, 'initlog': <function initlog at 0x000000000296CBF8>, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, 'fib': <function fib at 0x0000000002AAC9D8>, 'fin': <function fin at 0x0000000002AACAE8>}
>>> globals()
{'f': <function fib at 0x0000000002AAC9D8>, '__doc__': None, '__name__': '__main__', '__spec__': None, '__package__': None, '__builtins__': <module 'builtins' (built-in)>, 'MyEmptyClass': <class '__main__.MyEmptyClass'>, 'initlog': <function initlog at 0x000000000296CBF8>, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, 'fib': <function fib at 0x0000000002AAC9D8>, 'fin': <function fin at 0x0000000002AACAE8>}

他の言語から来た人にとって、fibは関数ではなく、それは値を返さないので手続きのように見えるだろう。実際は、return文を持たない関数も値を返す。この値はNone(これはビルトインの名前だ)と呼ばれている。Noneの書き出しはインタプリタによって抑止されているが、print()を使うと確認できる。

>>> print(fib(0))
None

フィボナッチ数列のリストを返すよう関数を直すのは単純だ。

def fib2(n):
    """Return a list containing the FIbonacci series up to n. """
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)
        a, b = b, a+b
    return result

print(fib2(100))

実行結果。

$ py fibonacchi.py
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

この例では次のPythonの特性を紹介した。

  • return文で関数から値を返す。単にreturnとした場合、または、returnの指定がない場合はNoneが返る。
  • result.append(a)はリストオブジェクトresultのメソッドを呼び出す。メソッドはオブジェクトに属する関数であり、obj.methodnameの形で呼び出す。objは何らかのオブジェクト、methodnameはオブジェクトの型によって定義されたメソッドの名前だ。異なる型は、異なるメソッドを定義する。違った型のメソッドは曖昧性をもたらすことなく同じ名前を持てる(classesを使って、独自のオブジェクト型とメソッドを定義することが可能だ。Classesのセクションを参照。) appendメソッドはリストオブジェクトで定義されている。これはリストの終端に新たな要素を追加する。この例ではresult = result + [a]と等価であるが、より効率的だ。

4.7. More on Defining Functions

引数の数が可変である関数を定義することもできる。同時に利用できる3つの形態がある。

4.7.1 Default Argument Values

最も使いやすい形態は引数のデフォルト値を指定するものだ。より少ない引数で関数を呼び出すことができる。(そりゃそうだ)

def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
    while True:
        ok = input(prompt)
        if ok in ('y', 'ye', 'yes'):
            return True
        if ok in ('n', 'no', 'nop', 'nope'):
            return False
        retries = retries - 1
        if retries < 0:
            raise OSError('uncooperative user')
        print(complaint)

ask_ok('Yukarisan maji tenshi')

実行結果。

$ py askok.py
Yukarisan maji tenshia
Yes or no, please!
Yukarisan maji tenshib
Yes or no, please!
Yukarisan maji tenshid
Yes or no, please!
Yukarisan maji tenshie
Yes or no, please!
Yukarisan maji tenshif
Traceback (most recent call last):
  File "askok.py", line 13, in <module>
    ask_ok('Yukarisan maji tenshi')
  File "askok.py", line 10, in ask_ok
    raise OSError('uncooperative user')
OSError: uncooperative user

この関数は幾つかの方法で呼び出すことができる。

  • 最低限、必要な引数だけ:ask_ok('Yukarisan maji tenshi:')
  • オプション引数を1つだけ: ask_ok('Yukarisan maji tenshi:', 2)
  • オプション全部指定: ask_ok('Yukarisan maji tenshi:', 2, 'use yes or no, plz.')

inキーワードは、シークエンスが正しい値を含むかチェックする。

デフォルト値は、definingのスコープで関数の定義位置で評価される。つまり

i = 5

def f(arg=i):
    print(arg)
i = 6
f()

この結果は5だ。

重大な警告: デフォルト値は1回のみ評価される。つまり、デフォルト値がlist, dictionary, クラスインスタンスのような可変オブジェクトの時に違いが発生する。以下の例を見てみよう。

def f(a, L=[]):
    L.append(a)
    return L

print(f(1))
print(f(2))
print(f(3))

$ py default.py
[1]
[1, 2]
[1, 2, 3]

まさかのデフォルト値を設定したリストが共有されてしまう。このようにしたくない場合、以下のようにする。

def g(a, L=None):
    if(L is None):
        L = []
    L.append(a)
    return L

$ py default.py
[1]
[2]
[3]

うわ、めんどくさ…。

4.7.2. Keyword Arguments

関数はkwarg=valueという形式を持つkeyword argumentを使って呼び出すことができる。以下の例で説明する。

def parrot(voltage, state='a stif', action='voom', type='Norwegian Blue'):
    print("-- This parrot wouldn't", action, end=' ')
    print("If you put", voltage, "volts though it.")
    print("-- Lovely plumage, the", type)
    print("It's", state, "!")

# These calls are valid
parrot(1000)
parrot(voltage=1000)
parrot(voltage=10000000, action="VOOOOOOOM")
parrot(action="VOOOOOOOM", voltage=10000000)
parrot('a million', 'bereft of life', 'jump')
parrot('a thousand', state='pushing up the daisies')

# Invalid calls
parrot() # required argument missing
parrot(voltage=5.0, 'dead') # non-keyword argument after a keyword argument
parrot(110, voltage=220) # duplicate value for the same argument
parrot(actor='Makimaki') # unknown keyword argument

関数parrotは1つの必須引数と3つのオプション数を取る。関数を呼び出す際、キーワード引数は実際に定義されている引数に従わなければならない。ただ、その順序を気にする必要はない。同時に同じキーワードに対して複数の値を設定することはできない。この制約は簡単にチェックできる。

>>> def func(a):
...     pass
...
>>> func(0, a=0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() got multiple values for argument 'a'

**nameという形の形式パラメータがあるとき、それは形式パラメータに関連するものを除いた全てのキーワード引数が含まれている辞書を受け取る。これは、*nameという形をした形式パラメータ(こっちは形式パラメータリストの上位の引数を格納したタプルだ)と一緒に使うこともあるだろう。*nameは、**nameの前に置かなければならない。関数を例に示す。

def cheeeshop(kind, *arguments, **keywards):
    print("-- Do you have any", kind, "?")
    print("-- I'm sorry, we're all out of", kind)
    for arg in arguments:
        print(arg)
    print("-" * 40)
    keys = sorted(keywards.keys())
    for kw in keys:
        print(kw, ":", keywards[kw])


cheeeshop("Limburger", "It's very runny, sir",
        "It's very very runny, sir.",
        shopkeeper="Maki Tsurumaki",
        client="Yukari Yuzuki",
        sketch="Cheese Shop Sketch")

実行結果。

$ py formalparam.py
-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir
It's very very runny, sir.
----------------------------------------
client : Yukari Yuzuki
shopkeeper : Maki Tsurumaki
sketch : Cheese Shop Sketch

キーワード引数の名前のリストは、それの中身を書き出す前にkeywards dictionaryのkeys()メソッドの出力をソートしている点に注意してほしい。もしこれをしない場合、引数の書き出し順序は確定しない。

4.7.4 Arbitary Argument Lists

最後に、任意の数の引数をもって関数を呼び出すことができる最も頻度が低い方法について触れておく。これらの引数はタプル内にラップされる。可変の引数の前に、0個ないし通常の引数の引数が並ぶ。

def write_multiple_items(file, separator, *args):
    file.write(separator.join(args))

通常、これらのvariadic(可変な)な引数は形式パラメータのリストの最後に置かれる。関数に渡される残りの引数全てが、この可変パラメータとして扱われるので、まあ仕方がない。*argsの後ろに置かれるどんな形式パラメータも、keyward-onlyな引数だ。つまり、位置が確定した引数というより、キーワードでのみ利用する。

>>> def concat(*args, sep="/"):
...     return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'

4.7.4. Unpacking Argument Lists

引数がリストやタプルに含まれているときは逆の必要が生じるだろう。つまり、それらをばらして、個別の引数を必要とする関数呼び出しに対応させる必要性だ。例えばビルトイン関数range()は、別々にstartstopの引数を必要とする。これらの情報が分かれていない場合は、*オペレータを用いてリスト/タプルの外に引数を引き出すことができる。

>>> list(range(3,6))
[3, 4, 5]
>>> args = [3,6]
>>> list(range(*args))
[3, 4, 5]

同じように、dictionaryも**オペレータを使って引数を引き出すことができる。


d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
parrot(**d)

4.7.5. Lambda Expressions

小さな無名関数をキーワードlamdaを使って作成できる。lambda a, b: a +b、この関数は2つの引数a,bの合計値を返すものだ。ラムダ関数は関数オブジェクトを使えるところなら、どこでも使える。単一の表現しか使えないという文法上の制約がある。意味的には、ラムダ関数は通常の関数定義のシンタックスシュガーだ。ネストされた関数定義のように、ラムダ関数はそのスコープから変数を参照できる。

def make_incrementor(n):
    return lambda x: x + n

f = make_incrementor(42)
print(f(0))
print(f(1))

この例は関数を返り値で返す時にラムダ表現を使っている。次の例は引数にラムダ式を与える。

pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
pairs.sort(key=lambda pair: pair[1])
print(pairs)

4.7.5. Documentation Strings

ドキュメントのお作法の話。関数の宣言の直後に"""で初めて複数行に渡って色々書いたら"""で閉じる。

def my_function():
    """Do nothing, but document it.

    No really, it doen't do anything.
    """
    pass

print(my_function.__doc__)

__doc__で参照できるのが面白いw

4.7.7. Function Annotations

これは完全にオプションだ。ユーザ定義関数についての任意のメタ情報を含めることができる。サードパーティ製のプロジェクトではアノテーションをドキュメント、型チェック、その他の用途、何に使っても自由だ。

アノテーションは関数の__annotations__属性にdictionaryとして格納されている。関数の他の部分に何の影響も与えない。パラメータアノテーションはパラメータ名の後ろにコロンを付けて定義する。この後、アノテーションの値を評価する文が続く。リターンアノテーションはリテラル->で定義する。下記の例は、positional argumentとkeyward argument、および返り値に特に意味のないアノテーションを付けたものだ。

def f(ham: 42, eggs: int = 'spam') -> "Nothing to see here":
    print("Annotations:", f.__annotations__)
    print("Arguments:", ham, eggs)

f("wonderful")

$ py annotation.py
Annotations: {'return': 'Nothing to see here', 'ham': 42, 'eggs': <class 'int'>}
Arguments: wonderful spam

4.8. Intermezzo: Coding Style

タブ使うな、とかそんな話。

Written with StackEdit.

No comments:

Post a Comment