본문 바로가기
Coding/Python

Python Method Chaining

by Hide­ 2019. 7. 17.
반응형

SQLAlchemy, MongoEngine 또는 Django ORM 등의 쿼리를 보면 Chaining을 통해 쿼리를 쉽게 작성할 수 있다는걸 알 수 있다. 예를 들어 아래와 같은 형태이다.

User.objects.filter(user_id=1).order_by('-user_id')

위 쿼리는 user_id=1의 결과값을 역순으로 정렬하여 return해준다. 쿼리에서 알 수 있듯이 filter().order_by() 형태로 함수들을 Chaining해줬다. 만약 Chaining을 지원하지 않았다면 filter()를 한번해주고 그 결과값을 다시 order_by()를 해주는 형태로 나눠서 작성해야 했을 것이다.

이는 상당히 많은 경우에서 유용한데, 파이썬의 경우 정말 간단한 방법을 통해 Method Chaining을 구현할 수 있다. 먼저 아래와 같은 코드가 있다고 가정한다.

class Engine:
    def __init__(self):
        self.query = 'SELECT * FROM temp'

    def filter(self, **kwargs):
        print(kwargs)
        for args in kwargs:
            self.query += f' {args}={kwargs[args]}'

    def order_by(self, field):
        self.query += f' ORDER BY {field[1:]}'
        self.query += ' ASC' if field.startswith('-') else ' DESC'

    def debug(self):
        print(f'[*] query : {self.query}')


engine = Engine()
engine.filter(user_id=1).order_by('-user_id')
engine.debug()

그냥 간단하게 Django ORM의 형태를 갖춘 클래스이다. 위 코드를 실행시켜보면 AttributeError: 'NoneType' object has no attribute 'order_by'라는 오류가 발생함을 알 수 있다. 해결 방법은 간단하다. 함수 마지막에 self를 리턴해주면 된다. 따라서 위 코드는 아래와 같이 바뀔 수 있다.

class Engine:
    def __init__(self):
        self.query = 'SELECT * FROM temp'

    def filter(self, **kwargs):
        print(kwargs)
        for args in kwargs:
            self.query += f' {args}={kwargs[args]}'
        return self

    def order_by(self, field):
        self.query += f' ORDER BY {field[1:]}'
        self.query += ' ASC' if field.startswith('-') else ' DESC'

    def debug(self):
        print(f'[*] query : {self.query}')


engine = Engine()
engine.filter(user_id=1).order_by('-user_id')
engine.debug()

def filter() 함수의 가장 마지막에 return self가 추가됐음을 알 수 있다. 실행시켜보면,

{'user_id': 1}
[*] query : SELECT * FROM temp user_id=1 ORDER BY user_id ASC

위와 같이 정상적으로 동작한다. 이유는 간단하다. 파이썬은 기본적으로 함수에서 반환값이 존재하지 않을 때 None을 반환한다. 따라서 filter() 함수에서 None이 반환되고, 결과적으로 None.order_by()가 실행되는 형태이므로 당연히 실행이 되지 않는다. 하지만 return self를 통해 객체 자체를 반환함으로써(위 예제에서는 Engine() 객체) Engine객체 내부에 존재하는 order_by() 함수가 정상적으로 실행되는 것이다.

추가적으로 파이썬은 append()처럼 객체를 직접 수정하는 함수는 Side-effect를 방지하기 위해 암묵적으로 None을 리턴한다고 한다.