機能的な関数の作成

Beginner

This tutorial is from open-source community. Access the source code

はじめに

このセクションでは、関数を使って他の関数を作成するアイデアについて説明します。

はじめに

次の関数を考えてみましょう。

def add(x, y):
    def do_add():
        print('Adding', x, y)
        return x + y
    return do_add

これは、別の関数を返す関数です。

>>> a = add(3,4)
>>> a
<function add.<locals>.do_add at 0x7f27d8a38790>
>>> a()
Adding 3 4
7

ローカル変数

内部関数が外部関数で定義された変数をどのように参照するかを見てみましょう。

def add(x, y):
    def do_add():
        ## `x` と `y` は `add(x, y)` の上で定義されています
        print('Adding', x, y)
        return x + y
    return do_add

さらに、add() が終了した後でも、それらの変数が何らかの方法で生き続けていることにも注意してください。

>>> a = add(3,4)
>>> a
<function do_add at 0x6a670>
>>> a()
Adding 3 4      ## これらの値はどこから来ているのでしょうか?
7

クロージャ

内部関数が結果として返される場合、その内部関数は「クロージャ」と呼ばれます。

def add(x, y):
    ## `do_add` はクロージャです
    def do_add():
        print('Adding', x, y)
        return x + y
    return do_add

重要な特徴:クロージャは、後で関数が正常に実行するために必要なすべての変数の値を保持します。 クロージャを、関数とそれが依存する変数の値を保持する追加の環境の両方と考えてください。

クロージャの使用

クロージャは Python の重要な機能です。ただし、その使い方はしばしば微妙です。一般的な用途は以下の通りです。

  • コールバック関数での使用。
  • 遅延評価。
  • デコレータ関数(後述)。

遅延評価

次のような関数を考えてみましょう。

def after(seconds, func):
    import time
    time.sleep(seconds)
    func()

使用例:

def greeting():
    print('Hello Guido')

after(30, greeting)

after は、後で、提供された関数を実行します。

クロージャは、追加の情報を持ち運びます。

def add(x, y):
    def do_add():
        print(f'Adding {x} + {y} -> {x+y}')
    return do_add

def after(seconds, func):
    import time
    time.sleep(seconds)
    func()

after(30, add(2, 3))
## `do_add` は x -> 2 と y -> 3 の参照を持っています

コードの繰り返し

クロージャは、過度のコードの繰り返しを避けるための手法としても使用できます。コードを生成する関数を書くことができます。

演習 7.7:繰り返しを避けるためのクロージャの使用

クロージャのより強力な機能の 1 つは、繰り返しコードを生成する際の使用です。演習 5.7 に戻ると、型チェック付きのプロパティを定義するコードを思い出してください。

class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price
  ...
    @property
    def shares(self):
        return self._shares

    @shares.setter
    def shares(self, value):
        if not isinstance(value, int):
            raise TypeError('Expected int')
        self._shares = value
  ...

そのコードを何度も何度も繰り返して入力する代わりに、クロージャを使って自動的に生成することができます。

typedproperty.py というファイルを作成し、次のコードを入れます。

## typedproperty.py

def typedproperty(name, expected_type):
    private_name = '_' + name
    @property
    def prop(self):
        return getattr(self, private_name)

    @prop.setter
    def prop(self, value):
        if not isinstance(value, expected_type):
            raise TypeError(f'Expected {expected_type}')
        setattr(self, private_name, value)

    return prop

次に、このようなクラスを定義して試してみましょう。

from typedproperty import typedproperty

class Stock:
    name = typedproperty('name', str)
    shares = typedproperty('shares', int)
    price = typedproperty('price', float)

    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

インスタンスを作成し、型チェックが機能することを確認してみましょう。

>>> s = Stock('IBM', 50, 91.1)
>>> s.name
'IBM'
>>> s.shares = '100'
... 型エラーが発生するはずです...
>>>

演習 7.8:関数呼び出しの簡略化

上記の例では、ユーザーは typedproperty('shares', int) のような呼び出しを入力するのが少々面倒だと感じるかもしれません。特に、それが何度も繰り返される場合です。typedproperty.py ファイルに次の定義を追加します。

String = lambda name: typedproperty(name, str)
Integer = lambda name: typedproperty(name, int)
Float = lambda name: typedproperty(name, float)

次に、Stock クラスを書き換えて、これらの関数を代わりに使用するようにします。

class Stock:
    name = String('name')
    shares = Integer('shares')
    price = Float('price')

    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

ああ、少しはましです。ここでの主な要点は、クロージャと lambda はしばしばコードを簡略化し、厄介な繰り返しを排除するために使用できるということです。これは多くの場合、良いことです。

まとめ

おめでとうございます!あなたは Returning Functions の実験を完了しました。あなたのスキルを向上させるために、LabEx でさらに多くの実験を行うことができます。