search
LoginSignup
9
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

Organization

【Pythonチュートリアル】制御構造ツール

はじめに

Pythonチュートリアル第3版を読み進めて、勉強した内容をメモしていこうという試みです

Pythonチュートリアル 第3版

そして読み終えた暁にはこの試験を受けたいと思います
終わるころには試験も開始している・・・!

開始しとるやないか!

Python 3 エンジニア認定基礎試験

続くといいな、続けばいいな

制御構造ツール

if文

>>> x = int(input(" 整数を入れてください: "))
整数を入れてください: 42
>>> if x < 0:
...     x = 0
...     print(' 負数はゼロとする')
... elif x == 0:
...     print(' ゼロ')
... elif x == 1:
...     print(' ひとつ')
... else:
...     print(' もっと')
...
もっと

for文

  • あらゆるシーケンス(リストや文字列)のアイテムに対し、そのシーケンス内の順序で反復をかける
>>> # 文字列の長さを測る:
... words = ['cat', 'window', 'defenestrate']
>>> for w in words:
...     print(w, len(w))
...
cat 3
window 6
defenestrate 12
  • 反復中のシーケンスを改修する必要があるときは、コピーを取って反復をかけることが推奨される
  • シーケンスに反復をかけることで暗黙にコピーが取られたりしない
>>> for w in words[:]: # リスト全体のスライスコピーにループをかける
...     if len(w) > 6:
...         words.insert(0, w)
...
>>> words
['defenestrate', 'cat', 'window', 'defenestrate']

range()関数

  • 数字の連なりに反復をかける際は、ビルトイン関数のrange()が便利
  • 等差階数を生成する
>>> for i in range(5):
...     print(i)
...
0
1
2
3
4
  • 与えられた終端値は入らない
  • range(10)が生成する10の値はちょうど長さ10シーケンスの各アイテムへのインデックスとなる
  • range()は0以外の数字から始めることもできる
  • 増分(ステップ)を指定することもできる
range(5, 10)
→ 5 から9 まで
range(0, 10, 3)
→ 0, 3, 6, 9
range(-10, -100, -30)
→ -10, -40, -70
  • シーケンスのインデックスで反復をかけたいときは、次のようにrange()len()を組み合わせてもよい
  • このような場合はenumerate()関数を使うのが便利
>>> a = ['Mary', 'had', 'a', 'little', 'lamb']
>>> for i in range(len(a)):
...     print(i, a[i])
...
0 Mary
1 had
2 a
3 little
4 lamb
  • range()関数が返すオブジェクトはさまざまな意味でリストのように振る舞うが、リストではない
  • 反復を掛けることで望みのシーケンスのアイテムを連続的に返すオブジェクトであり、それにより空間を節約する
  • そのようなオブジェクトを反復可能体(iterable)という
    • これはからになるまで連続的にアイテムを供給するもの、そうしたものを期待する関数や構造のターゲットとして適したものをいう
    • 反復可能体を期待する関数や構造のほうは、反復子(iterator)という
    • 反復可能体からリストを生成するlist()関数も反復子である
>>> list(range(5))
[0, 1, 2, 3, 4]

break文とcontinue文、ループにおけるelse節

  • break文はforまたはwhileのループを抜けるもの
  • これらのループ文にはelse節が加えられる
  • else節はリストを使い果たしたり、条件式がfalseになることによってループが終了した場合に実行され、break文で終了した場合には実行されない
>>> 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')
...
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
  • continue文はループの残りを飛ばして次回の反復にいく
>>> for num in range(2, 10):
...     if num % 2 == 0:
...         print("Found an even number", num)
...         continue
...     print("Found a number", num)
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

pass文

プログラム的には何もする必要がないときに使う

>>> while True:
...     pass # ビジーウェイトでキーボード割込(Ctrl+C) を待つ
...

最小のクラスを生成するのによく使われる

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

pass文の他の使い所は、新しくコードを書いているとき関数や条件の本体にプレースホルダとして置いておき、抽象的なレベルについての思考を助けるというという使い方もある

>>> def initlog(*args):
...     pass # 実装を忘れないこと!
...

関数の定義

  • キーワードdefが関数定義の始まりで、続けて関数名、丸括弧囲み仮引数リストを書く必要がある
  • 関数本体の文は次の行からインデントして書く
フィボナッチ級数を任意の上限まで書き出す関数
>>> def fib(n): # フィボナッチ級数をn まで書き出す
...     """n までのフィボナッチ級数を表示する"""
...     a, b = 0, 1
...     while a < n:
...     print(a, end=' ')
...     a, b = b, a+b
...     print()
...
>>> # ではこの関数を呼び出してみよう
... fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

さらに関数定義について

  • 引数の個数が可変の関数を定義することも可能
  • これには3つの形態があり、組み合わせることも可能

引数のデフォルト値

  • 一番使いでがある形態
  • 以下の関数はさまざまな形でコールできる
    • 必須の引数だけを与える
      • ask_ok('Do you really want to quit?')
    • オプション引数を一つ与える
      • ask_ok('OK to overwrite the file?', 2)
    • 全ての引数を与える
      • ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
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('非協力的ユーザー')
        print(complaint)

デフォルト値の評価は一度しか起きない。
デフォルト値が可変オブジェクト、すなわちリスト、ディクショナリ、およびほとんどのクラスのインスタンスである場合、このことが影響する。

例えば、以下の関数は、コールで渡される引数を累積していく

>>> def f(a, L=[]):
...     L.append(a)
...     return L
...
>>> print(f(1))
[1]
>>> print(f(2))
[1, 2]
>>> print(f(3))
[1, 2, 3]
>>>

コール間でデフォルト値を共有されたくないなら、この関数は以下のように書くとよい

>>> def f(a, L=None):
...     if L is None:
...             L = []
...     L.append(a)
...     return L
...
>>> print(f(1))
[1]
>>> print(f(2))
[2]
>>> print(f(3))
[3]

キーワード引数

  • 関数は「キーワード=値」の形でキーワード引数も取れる
>>> def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
...     print("-- This parrot wouldn't", action, end=' ')
...     print("if you put", voltage, "volts through it.")
...     print("-- It's", state, "!")
...

この関数は必須の引数を一個(voltage)、オプション引数を三個取り(state、action、type)、次のいずれの形でもコールできる

>>> # 位置引数一個
>>> parrot(1000)
-- This parrot wouldn't voom if you put 1000 volts through it.
-- It's a stiff !
>>> # キーワード引数一個
>>> parrot(voltage=1000)
-- This parrot wouldn't voom if you put 1000 volts through it.
-- It's a stiff !
>>> # キーワード引数二個
>>> parrot(voltage=1000000, action='VOOOOOM')
-- This parrot wouldn't VOOOOOM if you put 1000000 volts through it.
-- It's a stiff !
>>> # キーワード引数三個
>>> parrot(action='VOOOOOM', voltage=1000000)
-- This parrot wouldn't VOOOOOM if you put 1000000 volts through it.
-- It's a stiff !
>>> # 位置引数三個
>>> parrot('a million', 'bereft of life', 'jump')
-- This parrot wouldn't jump if you put a million volts through it.
-- It's bereft of life !
>>> # 位置引数一個、キーワード引数一個
>>> parrot('a thousand', state='pushing up the daisies')
-- This parrot wouldn't voom if you put a thousand volts through it.
-- It's pushing up the daisies !

しかし次のようなコールは無効である

>>> # 必要な引数がない
>>> parrot()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: parrot() missing 1 required positional argument: 'voltage'
>>> # キーワード引数の後に非キーワード引数
>>> parrot(voltage=5.0, 'dead')
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument
>>> # 同じ引数に値を2度与えた
>>> parrot(110, voltage=220)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: parrot() got multiple values for argument 'voltage'
>>> # 未知のキーワード引数
>>> parrot(actor='John Cleese')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: parrot() got an unexpected keyword argument 'actor'

関数をコールするときは、必ず位置引数が先でキーワード引数を後にしなければならない

キーワード引数はすべて関数定義の仮引数に書いたものと一致している必要があるが(parrot関数では引数actorは無効である)、その順序は問われない。

これはオプションではない引数でも同じだ(たとえばparrot(voltage=1000)も有効である)

引数は値を一度しか取れない。
この制限による失敗の例として以下を示す。
function()のキーワード引数「a」が複数の値を取っているため、型エラーが発生している

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

仮引数の最後が**名前の形になっていると、この引数はディクショナリを受け取る。
このディクショナリには、仮引数に対応するキーワードを除いたすべてのキーワード引数が入っている。
つまりこの形にすれば、仮引数にないキーワードが使える。

またこれは、*名前の形式と組み合わせて使うことができる。(*名前は**名前の前にあること。)

こちらの形式では仮引数にない位置指定型引数をすべて含んだタプルが関数に渡る。
よって次のような関数を定義すると

>>> def cheeseshop(kind, *arguments, **keywords):
...     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(keywords.keys())
...     for kw in keys:
...             print(kw, ":", keywords[kw])
...

次のようにコールでき、出力も以下のようになる

>>> cheeseshop("Limburger", "It's very runny, sir.", "It's really very, VERY runny, sir.", shopkeeper="Michael Palin", client="John Cleese", sketch="Cheese Shop Sketch")
-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir.
It's really very, VERY runny, sir.
----------------------------------------
client : John Cleese
shopkeeper : Michael Palin
sketch : Cheese Shop Sketch

keywordsディクショナリの中身を表示する際に、まずkey()メソッドの結果をソートし、キーワード引数のリストを生成していることに注目。これをしないと引数の表示順序が不定になる。

任意引数のリスト

  • 関数を任意個数の引数でコールできるように書くという方法
  • あまり使われない
  • これによる引数はタプルに纏められる
  • この可変個数の引数より前の部分には通常の引数を置くことができる
>>> def write_multiple_items(file, separator, *args):
...     file.write(separator.join(args))

可変長の引数は仮引数リストの最後に置く。関数に渡される引数の残りをすべて吸い込んでしまうからである。
*args 形式の場合、これより後ろの仮引数はすべて「キーワードオンリー」の引数となる。
つまりキーワード引数としてしか使えず、位置引数にはなれない

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

引数リストのアンパック

引数にしたいものがすでにリストやタプルになっていて、位置指定型引数を要求する関数のためにアンパックしなければならないという、上とは逆の状況がある。

たとえばビルトインのrange() 関数は、開始と停止で別々の引数を想定している。

これらが個別に手に入らないときは、* 演算子を使って関数をコールすることで、リストやタプルからアンパックした引数を渡すことができる

>>> # 個別の引数を使った普通のコール
>>> list(range(3, 6))
[3, 4, 5]
>>> args = [3, 6]
>>> # リストからアンパックした引数でコール
>>> list(range(*args))
[3, 4, 5]

同じように** 演算子を使えば、ディクショナリをキーワード引数にして渡すことができる。

>>> def parrot(voltage, state='a stiff', action='voom'):
...     print("-- This parrot wouldn't", action, end=' ')
...     print("if you put", voltage, "volts through it.", end=' ')
...     print("E's", state, "!")
...
>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
>>> parrot(**d)
-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !

lambda(ラムダ)式

  • キーワードlambdaを使うと小さな無名関数が掛ける
  • lambda a, b: a+bは2つの引数の和を返す関数
  • ラムダ関数は関数オブジェクトが必要な場所すべてに使用できる
  • ただしこの形式には単一の式しかモテないという構文上の制限がある
  • セマンティクス(意味論)的には、この形式は普通の関数定義に構文糖衣をかけてあるだけ
  • 入れ子になった関数定義同様、ラムダ関数からもそれを取り囲むスコープの変数が参照できる
>>> def make_incrementor(n):
...     return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43

上記はラムダ式を使って関数を返す例。
他の用途としては小さな関数を引数として渡すときにも使える

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

ドキュメンテーション文字列(docstring)

  • 1行目はいつでも常にオブジェクトの目的の短く簡潔な要約とすべき
  • オブジェクトの名前や型といった他でも得られることを明示することはしない
    (ただし関数名がすでにその動作を説明する動詞になっている場合は例外)
  • この行は大文字で始まり、ピリオドで終わること
  • 続きがある場合は、ドキュメンテーション文字列の2行目を空行とし、要約と他の記述を視覚的に分離する
  • 以降の行では段落を使い、そのオブジェクトのコールのしかた副作用などを記述する
  • Pythonのパーサは複数行からなる文字列リテラルのインデントを除去しないので、除去したい場合は、ドキュメントを処理するツールの方で行う必要がある
  • まず、最初の行より後にある空白ではない行を、ドキュメンテーション文字列全体のインデント亮の基準とする
    (1行目は使えない。これは引用符の直後にあるのが一般的で、そのインデント量は文字列リテラル本体には反映されない)
  • このインデント量と「等価の」空白を、文字列の各業の頭から除去する

以下は複数行からなるdocstringの例(__doc__で呼び出している部分に注目)

>>> def my_function():
...     """Do nothing, but document it.
...
...     No, really, it doesn't do anything.
...     """
...     pass
...
>>> print(my_function.__doc__)
Do nothing, but document it.

        No, really, it doesn't do anything.

関数注釈(関数アノテーション)

  • 関数注釈は人のオプション
  • 型のメタデータ情報であり、ユーザー定義関数で使用される
  • 注釈(アノテーション)は関数の__annotations__属性にディクショナリとして格納される
  • 関数の他の部分にはいかなる影響も及ぼさない
  • 引数注釈は引数名の後に、コロン、式と続けることで定義でき、この式が評価されて注釈の値となる
  • 返り値注釈はdef分最後のコロンの手前、仮引数リストとの間にリテラル「->」と式を挟むことで定義する
>>> def f(ham: str, eggs: str = 'eggs') -> str:
...     print("Annotations:", f.__annotations__)
...     print("Arguments:", ham, eggs)
...     return ham + ' and ' + eggs
...
>>> f('spam')
Annotations: {'ham': <class 'str'>, 'eggs': <class 'str'>, 'return': <class 'str'>}
Arguments: spam eggs
'spam and eggs'

コーディングスタイル(PEP8から要点のみ)

  • インデントはスペース4 つとし、タブは使わない
  • 79 文字以下で行を折り返す
  • 関数やクラス、さらには関数内の大きめのブロックを分離するのに空白行を使う
  • 可能であればコメント行は独立させる
  • docstring を使う
  • 演算子の周囲やカンマの後ろにはスペースを入れるが、カッコのすぐ内側には入れない
    • a = f(1, 2) + g(3, 4)
  • クラスや関数には一貫した命名を行う
    • クラスにはCamelCase
    • 関数やメソッドにはSnakeCase
  • エンコーディングはるUTF-8 か、さらにプレーンなASCII が望ましい
  • 違う言葉をしゃべる人たちが読んだり保守したりする可能性が少しでもあるコードでは識別子に非ASCII キャラクタを使わない

用語

シーケンス

  • シーケンス型とは文字列やリストのように順序のある要素の集まり
  • 組み込み型には以下の 6 つのシーケンス型がある
    • 文字列
    • ユニコード文字列
    • リスト
    • タプル
    • バッファ
    • xrange オブジェクト

イテラブル(iterable)

  • イテレーション可能な構造をイテラブルという
  • イテレーションとは「繰り返すこと」、「次要素にアクセスすること」
  • 例としてはfor文で回せるオブジェクト
  • 厳密にいうのであればイテレータにできるオブジェクト

イテレーター(iterator)

  • 状態(イテレーションした場所)を記憶しているイテラブルなオブジェクト
  • イテレータはイテラブルなオブジェクトの一つ
  • イテラブルなオブジェクトからiter関数を使いイテレータを作ることができる
  • イテレータはnextメソッドによって状態を進めることができ、イテレーションが不可能な状態になる(この場合だと最後の要素に達する)とStopIterationという例外を発生させる
  • イテレータを進めることができるのはnext関数だけではない

プレースホルダ

アンパック

右辺がタプルなどのとき、左辺に複数の変数を置くことで、その中身を展開できる。
これをシーケンスアンパック (sequence unpack) というが、いまいち「アンパック」という言葉の使い所と意味がよくわからない・・・\(^o^)/

等差階数

よくわからない・・・\(^o^)/

参考情報

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
9
Help us understand the problem. What are the problem?