いろんなものはつながっている

Effective Pythonを読んでみた(2): 2章 関数

14.Noneを返すよりは例外を選ぶ(Prefer Exceptions to Returning None)

def divide(a,b):
    try:
        return a/b
    except ZeroDivisionError:
        return None

と関数が定義された場合

result = divide(0, 5)
if not result:
   print('Invalid input')

を実行すると、Invalid input が表示される。

なので、0割のようなエラーの場合は、例外を返すようにする。

def divide(a, b):
    try:
        retuan a/b
    except ZeroDivisionError as e:
        raise ValueError('Invalid input') from e

呼び出すほうは

try:
    result = divide(2, 5)
except ValueError:
    print('Invalid input')
else:
    print('Result is %.f', % result)
   
とする。

15.クロージャが変数スコープとどう関わるかを知ってお(Prefer Exceptions to Returning None)

クロージャ内で使用できる変数のスコープはクロージャが定義れた場所と同じ。

def sort_prioirty(values, group):
    def helper(x):
        if x in group:  #groupを参照できる。
            return (0, x)
        return (1, x)
    values.sort(key=helper)

ただし、

def sort_prioirty(values, group):
    found = False
    def helper(x):
        if x in group:  #groupを参照できる。
            found = True
            return (0, x)
        return (1, x)
    values.sort(key=helper)
    return found

としても、クロージャ内でfoundの値は更新されない。

“nonlocal”というstatementがあり、それを使うとクロージャ外の変数をさわれる。
ただ、推奨はしない。そんなことが必要になったらその場面ではクロージャは使わないようにする。
その代わりに、クラスで実現するようにする。

class Sorter(object):
    def __init__(self, group):
        self.group = group
        self.found = False
    def __call__(self, x):
        if x in self.group:
            self.found = True
            return (0, x)
        return (1, x)
       
sorter = Sorter(group)
numbers.sort(key=sorter)

16.リストを返さずにジェネレータを返すことを考える(Consider Generators Instead of Returning Lists)

def index_words(text):
    result = []
    for index, letter in enumerate(text):
        if letter == ' ':
            result.append(index + 1)
    return result

というように、リストを追加していくより、

def index_words(text):
    result = []
    for index, letter in enumerate(text):
        if letter == ' ':
            yield index + 1

ジェネレータで表現したほうがよい。

注意点は、ジェネレータの使用は1回限り。再利用できない。
(あとで例がでてくる)

17.引数に対してイテレータを使うときには確実さを尊ぶ(Be Defensive When Iterating Over Argumentss)

イテレータは1回しか使用できない。

例えば

def read_visit(data_path):
    with open(data_path) as f:
        for line in f:
            yield int(line)

こんな関数で生成されるイテレータを2回呼ぶと

it = read_visit(r’c:\ddd.txt’)
print(list(it))
print(list(it))

>>>
[1,2,3]
[]

と2回目は空が返される。

したがって、イテレータを複数回使うときは、イテレータをコピーして使用する必要がある。

def normalize_func(get_iter):
    total = sum(get_iter()) #新しいイテレータ
    result = []
    if value in get_iter(): #新しいイテレータ
    ...

ただ、上記のようなことを実現するためには呼び出し元が、引数に関数を与えてあげる必要がある。

normalize_func(lambda: read_visit(path))

↑こんな感じ。

ただ、これだと煩雑なので、イテレート可能なクラス(、__iter__メソッドを定義する)を定義してあげて、それを使えばいい。毎回、新規にイテレータを生成して返す。でも、これってイテレータが使われるたびに毎回ファイルを開いてデータを読み込むってことだよな。。

def ReadVisits(object):
    def __init__(self, data_path):
        self.data_path = data_path
    def __iter__(self):
        with open(self.data_path) as f:
            for line in f:
                yield int(line)

また、イテレータはお呼びではない、ということを以下のように判定できる。

def normalize_defensive(numbers):
    if iter(numbers) is iter(numbers):
        raise TypeError('Must supply a countainer')
    ....

18.可変長位置引数を使って、見た目をすっきりさせる(Reduce Visual Noise with Variable Positional Arguments)

可変長の引数は以下のように定義できる。

def log(message, *value):
    if not values:
        print(message)
    else:
        values_str = ', '.join(str(x) for x in values)
        printf('%s' % values_str)

可変長の引数は、関数にタプルで渡される。

可変長引数にジェネレータをわたすと、渡した時点で展開されてしまうので注意が必要。

19.キーワード引数にオプションの振る舞いを与える(Provide Optional Behavior with Keyword Arguments)

引数をキーワードで指定できる。

20.動的なデフォルト引数を指定するときには Noneとドキュメンテーション文字列を使う(Use None and Docstrings to Specify Dynamic Default Arguments)

引数のデフォルト値は関数が定義されたときに1度だけ設定されるため、

def log(message, when=datetime.now()):
print(‘%s: %s’ % (when, message)

といった関数の場合、いつ、log(‘xxxx’)を呼んでも同じ時間が表示されることになる。

とりあえず、デフォルト値はNoneを指定しておいて、都度、呼び出すときに値を設定すればよいみたい。

デフォルト値は関数が定義されたときに1度だけ設定されることから、以下のような動的なものの場合、呼び出し元の間で共有されることになる。

def decode(data, default={}):
    try:
        return json.load(data)
    except ValueError:
        return default

したがって以下のようなことになる。

>>> foo = decode(‘illegal data’)
>>> foo[‘stuff’]=a
>>> bar = decode(‘bad data’)
>>> bar[‘ball’]=b
>>> print(foo)
{‘stuff': a, ‘ball': 1}
>>> print(bar)
{‘stuff': a, ‘ball': 1}

上記のようなことが起こらないようにするためには default に None を設定し、関数の内部で空のdictを設定する

def decode(data, default=None):
    if default == None:
        default = {}
    try:
        return json.load(data)
    except ValueError:
        return default

21.キーワード専用引数で明確さを高める(Enforce Clarity with Keyword-Only Arguments)

なるべくキーワードでの引数指定を使おうね。そのほうがわかりやすいし。

関連記事

コメント

  1. この記事へのコメントはありません。

  1. この記事へのトラックバックはありません。

スポンサード リンク

カテゴリー

スポンサード リンク