티스토리 뷰

Python

[python]1. Pythonic

anuiRin 2021. 6. 28. 23:59

Pythonic

Pythonic 이란 파이썬을 파이썬답고 파이썬스럽게 사용하기 위한 코드 작성 가이드 라인입니다.

코드의 스타일 뿐만 아니라 알고리즘이나 로직을 구현할 때도 보다 파이썬 다운 방법으로 구현하도록 안내하는 방법입니다.

Python은 다른 언어에 비해 자유도가 크기 때문에 pythonic이란 개념이 생겨나게 되었습니다.

, 어떤기능을 구현할 수 있는 방법이 많다는 뜻입니다.

대표적인 예로는 has_key라는 dictionary안의 key를 찾는 python2 코드는 python3에서 pythonic하지 않다는 이유로 삭제되고 이는 in으로 대체되었습니다.

in을 사용하면 has_key보다 영어문장처럼 python code를 작성할 수 있습니다.

def main():
    capital = {"korea":"seoul", "china":"beijing", "UK":"london"}
    if "korea" in capital: # True
        print ("korea exists in the dictionary")

    if "USA" in capital: #False
        print ("USA exists in the dictionary")
if __name__ == "__main__":
    main()

변수

변수란 메모리가 어떤 값을 가리키고 있는 이름입니다.

파이썬만이 지닌 몇가지 변수의 특징에 대해 정리해봅시다.

다른 언어와 달리 자료형을 명시하지 않아도 됩니다.

또한, python에서 변수를 선언하려면 선언과 동시에 할당을 해야 합니다.


Scope

python에서 변수의 유효범위를 계산할 때는 namespace를 기반으로 계산합니다.

어떤 변수가 사용되었을 때 그 변수가 namespace에 없다면 NameError Exception을 일으킵니다.

Python namespace에는 built-in, global, enclosed, local이 있습니다.

built-in은 파이썬에 내장되어있는 namespace이고, 코드 어느곳에서나 사용할 수 있습니다.

global은 파일단위의 모듈안에서 접근가능합니다.

또한 어떤 모듈을 import할 때 그 모듈의 global변수도 접근가능합니다.

enclose는 외부함수와 내부함수가 있을 때 내부함수에서 외부함수의 변수에 접근할 수 있음을 뜻합니다.

local은 클래스나 함수내부에서만 접근이 가능합니다.

python에서의 namespace 규칙을 LEGB라고 합니다.

변수의 namespace를 확인하는 순서는 local, enclosed, global, built-in 순서입니다.

따라서 전역변수를 수정하려면 global keyword를 이용해 명시적으로 전역변수를 수정함을 명시해야 합니다.

또한 함수안에 함수가 정의되어있는 형태인 nested function의 경우 외부함수의 지역변수를 내부함수에서 수정하기 위해서는 그냥 수정할 경우 내부함수의 지역변수로 인식하고 error를 출력하거나 원했던 동작을 수행하지 못합니다.

외부함수의 변수가 전역이 아닌 경우, 내부함수에서 nonlocal keyword를 이용해 외부함수의 변수를 수정할 수 있습니다.

nonlocal keyword는 다른 용어로 free variable이라고도 불립니다.

free variable이란, 함수내에서 사용되었지만 함수내에서 정의되지 않은 변수를 의미합니다.

nonlocal이 이와 같은 역할을 하므로 두 용어는 같은 의미로 사용됩니다.


First-Class

First-Class Citizen

first-class citizen 속성을 가진다는 것은 어떤 개체를 다른 개체의 매개변수로 전달하거나, 함수의 반환 값으로 사용하거나, 변수에 값으로 할당할 수 있는 것을 의미합니다.

, 어떤 자료형이나 함수, 클래스가 그 자체로 매개변수로 전달되거나 반환 값으로 사용할 수 있으면 그것은 first-class citizen 속성을 가지고 있다고 할 수 있습니다.

예를 들어 C언어에서 array는 그 자체로 매개변수나 반환 값으로 사용될 수 없고 array의 첫번째 주소 값을 전달하므로 first-class citizen이라고 할 수 없습니다.

또한 함수가 first-class citizen속성을 가진다면 first-class function 이라고 부릅니다.

python first-class function을 지원합니다.

, 함수를 매개변수로 전달하거나 return값으로 사용할 수 있습니다.

first-class function과 비슷한 개념으로 High-Order function이 있습니다.

함수가 매개변수로 전달되거나, 함수를 반환 값으로 사용할 때 high-order function이라고 합니다.

이를 이용하면 map이라는 python 내장함수를 사용할 수 있습니다.

map은 첫 번째 매개변수인 함수와 두 번째 매개변수인 리스트를 매핑하여 결과를 반환하는 함수입니다.

이와 같은 내장함수를 사용하면 코드의 가독성이나 효율성을 높일 수 있습니다.

lower_list = ["seoul", "beijing", "london"]
upper_list = []

def upper(list):
    return list.upper()

def main():
    upper_list = map(upper, lower_list)
    print(lower_list)
    print(list(upper_list))

if __name__ == "__main__":
    main()

Nested function

Nested function은 함수로 감싸인 함수를 의미합니다.

함수를 감싸고 있는 함수를 외부함수감싸여진 함수를 내부함수라고 합니다.

내부함수는 한 개 또는 그 이상이 될 수 있습니다.

각각의 내부함수들은 scope chain에 의해서 외부함수의 변수나 매개변수 등에 접근이 가능합니다.

하지만 외부함수에서는 내부함수에 접근할 수 없습니다.

이는 namespaceLEGB 규칙 중에 enclosed에 해당하는 내용입니다.


Closure

Closure는 중첩 함수의 일종이고 함수와 함수가 사용하는 환경(nonlocal 변수들)을 저장하는 것 입니다.

또한, closure는 함수의 반환 값으로 내부 함수를 사용하는 함수입니다.

만약 외부함수가 내부함수를 반환할 때 내부함수가 외부함수의 지역변수인 nonlocal변수를 사용한다면 외부함수가 종료될 때 문제가 될 수 있지 않을까 라는 의문이 들 수 있습니다.

하지만 closure는 함수가 사용하는 환경을 저장하므로 외부함수가 종료되어도 nonlocal 변수를 저장하고 있기 때문에 반환 된 내부함수에서 해당 자원을 사용할 수 있게 됩니다.

파이썬의 개체들은 각자의 속성(attribute)을 가지고, closure의 경우 __closure__의 속성을 가집니다.

만약 외부함수의 반환 값으로 내부함수를 사용하는 closure를 사용할 경우 이 개체는 속성으로 __closure__를 가지고 이 속성이 closure에 필요한 환경 변수를 가지고 있습니다.

, 외부함수가 실행될 때 nonlocal 변수가 생성되고 복사되어 내부함수의 __closure__속성에 저장됩니다.

def closure():
    x = 10

    def inner():
        y = 20
        return x + y

    return inner

if __name__ == "__main__":
    p = closure()
    print(dir(p)) # print attribute of closure function
    print(len(p.__closure__)) # print number of closure attribute
    print(dir(p.__closure__[0])) # print first tuple value of __closure__ attribute
    print(p.__closure__[0].cell_contents) # print saved nonlocal variable of outer function

이런 closure가 사용되는 경우는 크게 3가지 입니다.

첫째, global 변수를 사용하지 않고 nonlocal 변수로 이를 대신할 때 입니다.

Global 변수를 사용하지 않음으로써 information hiding이 가능합니다.

둘째로 class를 사용하지 않기 위해서 입니다.

파이썬은 엄밀하게 말하면 객체지향 언어가 아니기 때문에 다루는 변수나 함수가 많지 않은 경우 클래스로 만드는 것 보다 함수로 만들어 closure를 이용하는 것이 효율적일 수 있습니다.

마지막으로 데코레이터(decorator)을 사용하기 위해서 입니다.

데코레이터이란 어떤 함수를 실행하기 전이나 실행한 후에 특정 기능을 수행하기 위한 기능입니다.

데코레이터에 대해서는 후에 다시 정리하겠습니다.


Partial Application

여러 개의 매개변수를 가지는 함수에서 일부 매개변수의 값이 고정적일 때 매번 이를 작성하는 것은 비효율적일 수 있습니다.

이를 위해 python에서는 partial application이라는 개념을 사용합니다.

Partial Application이란 일부의 매개변수를 지역 변수로 만드는 함수인 wrapping 함수를 만들고, 이를 사용해 가변적인 매개변수만 매개변수로 사용하는 기법입니다.

이를 구현하는 방법을 살펴보기 전에 python에서 가변 매개변수를 처리하는 방식을 살펴봅시다.


*args**kwarg

Python*args**kwargs 형태로 가변 매개변수를 지원합니다.

argskwargs의 명칭은 중요하지 않고 *키워드가 중요합니다.

따라서 args, kwargs는 어떠한 다른 이름으로 바꾸어 쓸 수 있습니다.

argsarguments, kwargskeyword arguments의 약자입니다.

따라서 관례에 따라 *args**kwargs의 형태로 많이 쓰여집니다.

*argsnon-keyworded 가변 인자를 다루고, **kwargskeyworded 가변 인자를 다룹니다.

이때 keywordkey 값이라고 생각 할 수 있습니다.

따라서 argskeyword가 없는 가변 인자 이므로 리스트로 나타낼 수 있고, kwargskeyword가 있는 가변 인자 이므로 dictionary로 나타낼 수 있습니다.

고정된 매개변수, non-keyword 가변인자, keyword 가변인자를 같이 사용할 때는 선언 순서가 중요합니다.

고정된 매개변수, non-keyword 가변인자, keyword 가변인자 순으로 선언해야 하고 그렇지 않는다면 오류가 발생합니다.

def param(fixed, *args, **kwargs):
    print("===fixed argument===")
    print("fixed : {}".format(fixed))
    print("===args===")
    for arg in args:
        print("args : {}".format(arg))

    print("===kwargs===")
    for key, kwarg in kwargs.items():
        print("kwargs : [{}] {}".format(key, kwarg))

def main():
    args = ["seoul", "beijing", "london"]
    kwargs = {"korea" : "seoul", "china" : "beijing", "UK" : "london"}

    param("capital", *args, **kwargs)

if __name__ == "__main__":
    main()

Closure를 이용한 Partial Application

Partial application을 구현하는 방법은 여러가지가 있습니다. 그 중 closure을 이용해 구현한 것을 살펴봅시다.

def partial(func, *partial_args):

    def wrapper(*extra_args):
        args = list(partial_args)
        args.extend(extra_args) # add arguments to the list when declaring and using closure.

        return func(*args)
    return wrapper # use inner function as return value. This means that it is closure.

def logging(year, month, day, title, content):
    print("%s-%s-%s %s:%s" % (year, month, day, title, content))

def main():
    print("=== use logging function ===")
    logging("2021", "6", "17", "2021 spring", "end of semester")
    logging("2021", "6", "17", "2021 summer", "vacation begins")

    print("=== use partial function ===")
    f = partial(logging, "2021", "6", "17")
    f("2021 spring", "end of semester")
    f("2021 summer", "vacation begins")
    # Partial applications can be used to increase readability.

if __name__ == "__main__":
    main() # we can expect to print same values.

partial function을 사용할 때 고정된 매개변수인 "2021", "6", "17"을 closure을 이용해 저장해 놓을 후 wrapper을 return합니다.

f를 이용할 때는 가변적인 매개변수만 매개변수로 전달해줍니다. 

 


Python 내장 모듈을 이용한 partial application

Partial application을 구현 하기 위해서는 high-order function이 지원되어야 합니다.

Pythonhigh-order function을 지원하고 내부적으로 higher-order function을 이용한 기능을 모아 놓은 별도의 모듈인 functools 모듈을 가지고 있습니다.

그 중에는 partial application도 존재합니다.

from functools import partial

def logging(year, month, day, title, content):
    print("%s-%s-%s %s:%s" % (year, month, day, title, content))

def main():
    print("=== use partial function ===")
    f = partial(logging, "2021", "6", "17") # using partial function in functools module
    f("2021 spring", "end of semester")
    f("2021 summer", "vacation begins")

if __name__ == "__main__":
    main()

이렇게 functools 모듈의 partial을 사용해 간단하게 partial application을 구현할 수도 있습니다.


reference

프로그래밍 언어의 개념과 흐름에 대한 고찰, 파이썬답게 코딩하기

 

'Python' 카테고리의 다른 글

[python] 6. Thread (1)  (0) 2021.07.16
5. 동시성과 병렬성  (0) 2021.07.12
[python] 4. Generator, Iterator  (0) 2021.07.08
[python] 3. Decorator  (0) 2021.07.05
[python]2. Basic Pythonic Grammar  (0) 2021.06.30
댓글