본문 바로가기

Computer Engineering/Python

Fluent Python Chapter 5. 일급 함수

Chapter1의 Introduction 부분에서 이야기 한 것 처럼 지난 5년간 다양한 언어나 소프트웨어를 공부하고 이용하여 소프트웨어를 개발했는데 이것 저것 하다보니 자주 사용하는 언어임에도 불구하고 파이썬을 잘 활용하고 있느냐에 대한 답변을 자신있게 하기 어렵다고 느껴서 Fluent Python이라는 책을 공부하며 정리하고 있습니다. 올 해에는 새로운 기술들도 좋지만 기존에 활용하던 언어나 프레임워크 그리고 소프트웨어를 더 잘 사용할 수 있도록 깊게 공부할 수 있는 한해를 만들고 싶은 소망을 가지고 있습니다. 21장 까지 정리를 성공하고 맛있는걸 먹으면서 스스로 축하할 날이 어서 왔으면 좋겠네요!

 

지난 chapter 정리

Fluent Python Chapter 1. 파이썬 데이터 모델 (Feat. 일관성 있는 언어)

Fluent Python Chapter 2-1. Sequence (Sequence의 분류, listcomp, generator, tuple, slicing

Fluent Python Chapter 2-2. Sequence (bisect, deque, memoryview)

Fluent Python Chapter 3. Dictionary (feat. hashtable)

Fluent Python Chapter 4. 텍스트와 바이트 (feat. 깨지지마라..)

 

Chapter5 Introduction

Chapter5는 세번 째 Part인 객체로서의 함수의 첫 번째 Chapter입니다. 파이썬의 함수가 일급 객체임을 설명하면서 파이썬에서 함수를 객체로 활용할 수 있는 다양한 방법들을 소개할 것 같습니다. 특히, 일급 객체로 구현함으로써 함수를 하나의 데이터형으로 사용하고, 함수형 프로그래밍으로의 확장이나 함수 속성에 접근해서 프레임워크나 도구나 이 속성 정보를 사용할 수 있게 돕습니다. 

 

일급 객체 / 일급 함수

일급 객체 (출처: 위키 백과)

컴퓨터 프로그래밍 언어 디자인에서, 일급 객체(영어: first-class object)란 다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체를 가리킨다. 보통 함수에 매개변수로 넘기기, 수정하기, 변수에 대입하기와 같은 연산을 지원할 때 일급 객체라고 한다.
일급 함수 (출처: 위키 백과)
 
함수를 다른 변수와 동일하게 다루는 언어는 일급 함수를 가졌다고 표현합니다. 예를 들어, 일급 함수를 가진 언어에서는 함수를 다른 함수에 매개변수로 제공하거나, 함수 함수를 반환할 수 있으며, 변수에도 할당할 수 있습니다.
 
 
먼저 위키 백과에 나온 일급 객체와 일급 함수의 정의를 살펴봤습니다. 저는 결국 하나의 함수를 데이터형으로 사용하고, 이 데이터형의 객체를 다양한 연산(매개변수,  변수 할당, 함수의 반환값으로 이용)을 지원하는 것이라고 정의를 내렸습니다.
 
 
책에는 아래와 같은 작업을 수행할 수 있는 객체라고 소개되어 있습니다.
- runtime에 생성할 수 있음.
- 데이터 구조체의 변수나 요소에 할당할 수 있다.
- 함수 인수로 전달할 수 있다.
- 함수 결과로 반환할 수 있다.
 

함수를 객체처럼 다루기

# jupyter에서 함수를 runtime에 생성
def my_add(a, b):
    '''Same as a + b.'''
    return a + b
    
my_add(1,2)
3

# 함수 객체의 여러 속성 중 하나인 __doc__
my_add.__doc__ 
'Same as a + b.'

# function 클래스의 객체
type(my_add)
function

# 함수를 변수에 할당
my_function = my_add

my_function(1,2)
3

# 함수를 argument로 전달해서 고위함수 만들기
from functools import partial

my_add_with_three = partial(my_add, 3)

list(map(my_add_with_three, (1, 2))
[4, 5]

 

high-order function

함수를 인수로 받거나, 함수를 결과로 반환하는 함수를 high-order function이라고 한다.

파이썬에서 대표적인 고위함수로는 map(), filter(), reduce()가 있다. listcomp가 나오면서 사용이 약간 줄었다.

def factorial(n):
    '''return n!'''
    return 1 if n < 2 else n * factorial(n-1)


list(map(factorial, range(6)))
[1, 1, 2, 6, 24, 120]

[factorial(n) for n in range(6)]
[1, 1, 2, 6, 24, 120]

 

 

Lambda (익명함수)

lambda 키워드는 파이썬 표현식 내에 익명 함수를 생성한다.

익명함수는 아주 간단한 구문에만 사용하기 위해 설계되었으므로 try, while, 할당 문등을 사용할 수 없다.

대부분의 고위함수의 인수로 사용하는 경우에만 사용하는 것이 좋다. (for 가독성)

car_brands = ['BMW', 'Benz', 'Porsche']

sorted(car_brands, key=lambda word: word[::-1])
['BMW', 'Porsche', 'Benz']

책에서 소개하는 복잡한 lambda 코드 리팩토링 방법.

https://docs.python.org/3/howto/functional.html#small-functions-and-the-lambda-expression

 

Functional Programming HOWTO — Python 3.10.1 documentation

In this document, we’ll take a tour of Python’s features suitable for implementing programs in a functional style. After an introduction to the concepts of functional programming, we’ll look at language features such as iterators and generators and r

docs.python.org

 

Fredrik Lundh once suggested the following set of rules for refactoring uses of lambda:

  1. Write a lambda function.
  2. Write a comment explaining what the heck that lambda does.
  3. Study the comment for a while, and think of a name that captures the essence of the comment.
  4. Convert the lambda to a def statement, using that name.
  5. Remove the comment.

I really like these rules, but you’re free to disagree about whether this lambda-free style is better.

 

 

7가지 스타일의 callable 객체

- 사용자 정의 함수

- 내장 함수 -> len(), time.strftime() 처럼 C언어로 구현된 함수

- 내장 메서드 -> dict.get() 처럼 C언어로 구현된 메서드

- 메서드 -> 클래스 본체에 정의된 함수

- 클래스 -> __new__()메서드를 실행해서 객체를 생성하고, __init__()으로 초기한 후 최종적으로 호출자에 객체를 반환.

- 클래스 객체 -> __call__ magic method를 구현하면 클랫의 객체를 함수로 호출 될 수 있다.

- 제너레이터 함수 -> yield 키워드를 사용하는 함수나 메서드. 이 함수가 호출되면 generator객체를 반환한다

# 사용자 정의 함수
type(my_add)
function

# 내장 함수
type(len)
builtin_function_or_method

# 내장 메서드
type(dict.get)
method_descriptor

# 메서드 - 클래스 본체에 정의된 함수
class MyClass:
    def __init__(self, age):
        self.age = age
jordan = MyClass(age=1)

type(jordan.__init__)
method

 

함수 인트로스펙션 및 Annotation

함수가 객체로서 어떤 속성을 가지고 있는 살펴보자. 

dir(my_add)
['__annotations__',
 '__call__',
 '__class__',
 '__closure__',
 '__code__',
 '__defaults__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__get__',
 '__getattribute__',
 '__globals__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__kwdefaults__',
 '__le__',
 '__lt__',
 '__module__',
 '__name__',
 '__ne__',
 '__new__',
 '__qualname__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__']
 
 # 일반 객체에는 존재하지 않는 함수 속성 나열하가
 sorted(set(dir(my_add)) - set(dir(jordan)))
 ['__annotations__',
 '__closure__',
 '__code__',
 '__defaults__',
 '__get__',
 '__globals__',
 '__kwdefaults__',
 '__name__',
 '__qualname__']

 

일반 객체에 존재 하지 않는 몇가지 속성을 살펴보자.

def my_function(nickname='jordan', *hobby, favorite_language=None, **kwarg):
    print(f'nickname: {nickname}')
    print(f'hobby: {hobby}')
    print(f'favorite_language: {favorite_language}')
    print(f'kwarg: {kwarg}')
 
my_function('MJ', 'tennis', 'basketball', 'golf', favorite_language="python")

nickname: MJ
hobby: ('tennis', 'basketball', 'golf')
favorite_language: python
kwarg: {}

my_function.__defaults__
 ('jordan',)

my_function.__kwdefaults__
{'favorite_language': None}

__defaults__ -> positional arguments 와 keyword argument인수의 기본 값을 가진 튜플.

__kwdefaults__ -> 키워드 전용 인수의 기본값

 

그리고 inpsect 모듈을 사용하면 함수의 인수에 대한 정보를 깔끔하게 추출할 수 있다.

from inspect import signature

sig = signature(my_function)
str(sig)
"(nickname='jordan', *hobby, favorite_language=None, **kwarg)"


for name, param in sig.parameters.items():
    print(param.kind, ':', name, '=', param.default)
POSITIONAL_OR_KEYWORD : nickname = jordan
VAR_POSITIONAL : hobby = <class 'inspect._empty'>
KEYWORD_ONLY : favorite_language = None
VAR_KEYWORD : kwarg = <class 'inspect._empty'>

 

파이썬3에서 도입된 typehinting 덕분에 생산성을 크게 늘릴 수 있습니다. 각 매개변수에는 콜론(:) 뒤에 annotation 표현식으 추가해서 type에 대한 메타정보를 표시할 수 있습니다.

annotation은 전혀 처리되지 않고 함수 객체 안의 __annotations__속성에 저장됩니다.

def my_add(a: int, b:int) -> int:
    '''Same as a + b.'''
    return a + b
    
 my_add.__annotations__
 {'a': int, 'b': int, 'return': int}

annotation은 동작에는 영향을 주지 않고, IDE, 프레임워크, 데커레이터가 사용할 수 있는 메타데이터입니다. 이 데이터 또한 inspector모듈로 추출할 수 있습니다. 

 

 

함수형 프로그래밍을 위한 패키지

operator 모듈 -> add, mul, itemgetter, attrgetter등 

functools 모듈 -> reduct, all, any, partial 등

 

 

마무리

이것으로 5장인 일급 함수 (First class function)의 여러 부분을 살펴보고 정리해봤습니다. 일급 함수라는 말은 함수를 정말 다양한 operation에 사용할 수 있음을 의미합니다. 함수를 다양한 방법으로 활용하는 방법과 객체로서 여러 속성을 살펴봤습니다. 6장과 7장에서의 내용이 기대되네요. 

 

Reference

- Fluent Python Chapter5

- 위키피디아

- 파이썬 공식 문서