関数、クロージャ、ラムダ
再利用可能なヘルパー関数を定義し、デフォルト引数を使い、面接問題のソートや関数型パターンにラムダ式を適用します。
「関数、クロージャ、ラムダ」はCoddyKit上の無料DSA Interview Prepレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはDSA Interview Prep学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 DSA Interview Prepコースには全4レッスンが含まれています。
Pythonで関数を定義する
Pythonでは関数は第一級オブジェクトです。関数を渡したり、返したり、保存したりできます。名前の付け方がよいヘルパー関数は、面接用のコードを読みやすくします。まずエッジケースに対処してください。
def is_palindrome(s: str) -> bool:
'''Return True if s reads the same forwards and backwards.'''
s = s.lower()
return s == s[::-1]
print(is_palindrome('Racecar')) # True
print(is_palindrome('hello')) # False
# Functions as values
checks = [is_palindrome]
print(checks[0]('level')) # Trueデフォルト引数とキーワード引数
デフォルト値を使うと、呼び出し側は引数を省略できます。ただし、リストのようなミュータブルなデフォルト値は絶対に使わないでください。すべての呼び出し元で共有されてしまいます。Noneをデフォルトにして、関数内でリストを作成するのが解決策です。コードで確認します。
# WRONG: shared mutable default
def bad_append(val, lst=[]):
lst.append(val)
return lst
print(bad_append(1)) # [1]
print(bad_append(2)) # [1, 2] surprise!
# CORRECT: use None sentinel
def good_append(val, lst=None):
if lst is None:
lst = []
lst.append(val)
return lst
print(good_append(1)) # [1]
print(good_append(2)) # [2]*argsと**kwargs
*argsは追加の位置引数をタプルにまとめ、**kwargsは追加のキーワード引数をdictにまとめます。関数を呼び出すときに*を使うと、シーケンスをアンパックすることもできます。
def total(*args):
return sum(args)
print(total(1, 2, 3)) # 6
print(total(1, 2, 3, 4)) # 10
# Unpack a list as positional args
point = [3, 7]
print(max(*point)) # 7
# **kwargs
def greet(**kwargs):
name = kwargs.get('name', 'World')
return f'Hello, {name}!'
print(greet(name='Alice')) # Hello, Alice!ラムダ式
lambdaは、lambda params: expressionのように書く小さな1行関数です。sortedやmaxで簡単なkeyとして使うのに最適です。それ以上複雑な処理には、名前付きのdefのほうが読みやすくなります。
# Lambda as sort key
pairs = [(1, 3), (2, 1), (3, 2)]
sorted_by_second = sorted(pairs, key=lambda p: p[1])
print(sorted_by_second) # [(2,1),(3,2),(1,3)]
# Lambda with map
double = list(map(lambda n: n * 2, [1, 2, 3]))
print(double) # [2, 4, 6]
# Named function is clearer for complex logic
def sort_key(p):
return (p[1], -p[0]) # secondary sort
print(sorted(pairs, key=sort_key))ネストされた関数とクロージャ
クロージャとは、それを囲む関数の変数を記憶する関数です。別の関数の内部でヘルパーを定義すると、外側の変数を使えるため、DFSに便利です。
def make_counter(start=0):
count = [start] # list to allow mutation
def increment():
count[0] += 1
return count[0]
return increment
counter = make_counter()
print(counter()) # 1
print(counter()) # 2
# Alternative: nonlocal keyword
def make_counter2():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment再帰的なDFSでのクロージャ
よく使われるパターンです。メイン関数の内部でdfsヘルパーを定義し、外側の変数に結果を集めさせます。内部関数は、そのスコープを閉じ込めたクロージャです。
def max_depth(root):
'''Closure pattern for tree DFS.'''
max_d = [0] # mutable container for closure
def dfs(node, depth):
if node is None:
return
max_d[0] = max(max_d[0], depth)
dfs(node.left, depth + 1)
dfs(node.right, depth + 1)
dfs(root, 1)
return max_d[0]
# This pattern avoids a class or global state.メモ化のためのfunctools.lru_cache
lru_cache(3.9以降では@cacheも使用可能)は、引数ごとに関数の結果を記憶し、素朴なFibonacciをO(2^n)からO(n)に変えます。引数はハッシュ可能でなければならないため、tupleを使ってください。
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
print(fib(40)) # 102334155 fast!
print(fib.cache_info()) # hits, misses, size高階関数
高階関数とは、関数を受け取るか、関数を返す関数です。sorted、map、filterはいずれも高階関数です。自分で高階関数を書くと、繰り返し現れるパターンを抽象化できます。コードで合成を示します。
def apply_twice(f, x):
return f(f(x))
print(apply_twice(lambda n: n * 2, 3)) # 12 (3*2*2)
# Composing transformations
def compose(f, g):
return lambda x: f(g(x))
double = lambda n: n * 2
add_one = lambda n: n + 1
double_then_add = compose(add_one, double)
print(double_then_add(5)) # 11 ((5*2)+1)ヘルパー関数を使った再帰
多くの解法では、準備とエッジケースを処理する外側の関数と、再帰を実行する内部のヘルパーを組み合わせます。公開APIをすっきり保ち、ロジックを分離できます。コードで確認します。
def flatten(nested):
'''Flatten an arbitrarily nested list.'''
result = []
def _flatten(lst):
for item in lst:
if isinstance(item, list):
_flatten(item)
else:
result.append(item)
_flatten(nested)
return result
print(flatten([1, [2, [3, 4], 5], 6]))
# [1, 2, 3, 4, 5, 6]functools.partialによる部分関数
functools.partialは一部の引数をあらかじめ設定し、新しい呼び出し可能オブジェクトを返します。引数なしの関数を要求するAPIに対して、特定のパラメーターを固定した関数を渡したい場合に便利です。
from functools import partial
def power(base, exp):
return base ** exp
square = partial(power, exp=2)
cube = partial(power, exp=3)
print(square(5)) # 25
print(cube(3)) # 27
print(list(map(square, [1, 2, 3, 4]))) # [1, 4, 9, 16]独自の比較関数のためのlambda
Python 3では2引数の比較関数が廃止されましたが、cmp_to_keyを使えば、難しい順序付けで再び利用できます。たとえば、連結した値が最大になるように数値を並べる場合です。コードで確認します。
from functools import cmp_to_key
def largest_number(nums):
def compare(a, b):
if a + b > b + a: return -1
if a + b < b + a: return 1
return 0
strs = list(map(str, nums))
strs.sort(key=cmp_to_key(compare))
result = ''.join(strs)
return '0' if result[0] == '0' else result
print(largest_number([3, 30, 34, 5, 9])) # '9534330'理解度チェック
理解度を簡単に確認しましょう。関数、クロージャ、ラムダについて学んだことを示す時間です。深呼吸して、始めてください。🚀
レッスンのまとめ
まとめ:ミュータブルなデフォルト値は絶対に使わず(Noneを使います)、クロージャを使うと内部のヘルパーから外側の変数にアクセスできるためDFSに役立ち、lru_cacheなどのfunctoolsのツールが高速な解法を支えます。
よくある質問
「関数、クロージャ、ラムダ」レッスンは無料ですか?
はい。「関数、クロージャ、ラムダ」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、DSA Interview Prepコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 DSA Interview Prepコースには全4レッスンが含まれています。
「関数、クロージャ、ラムダ」で何を学びますか?
再利用可能なヘルパー関数を定義し、デフォルト引数を使い、面接問題のソートや関数型パターンにラムダ式を適用します。 ブラウザで直接実行するハンズオンコードでDSA Interview Prepを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
DSA Interview Prepを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのDSA Interview Prepは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「関数、クロージャ、ラムダ」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このDSA Interview Prepレッスンでコードを書いて実行できますか?
はい。すべてのDSA Interview Prepレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- リスト、タプル、スライス
- Pythonの辞書と集合
- 内包表記と組み込み関数
- 関数、クロージャ、ラムダ