brunch

You can make anything
by writing

C.S.Lewis

by 이권수 Mar 17. 2024

[Python] Protocol

Python에서 Protocol 사용하여 간단하게 인터페이스 만들기


파이썬은 클래스 상속을 통해서 OOP를 지원한다. 클래스를 상속하는 방법은 클래스를 정의할 때 상속받고자 하는 클래스를 추가하면 된다. 예컨대, 시험에 대한 정보를 관리하는 시스템을 개발한다고 해보자. 모든 시험에는 점수가 존재하기 때문에, Test라는 클래스를 정의하여 score를 저장하도록 한다. 그러고 나서, 시험의 종류마다 클래스를 만들 때, Test 클래스를 상속한다.


위 예시에서 보면 show()라는 함수는 MathTest 클래스에 없는 함수이다. 하지만 상속하고 있는 Test 클래스가 가지고 있기 때문에 MathTest로부터 생성한 객체에서 show함수를 수행할 수 있다.


이때, 여러 테스트를 본 학생들을 대상으로 평균 점수를 낸다고 가정해 보자. 국어, 영어, 수학 시험 결과를 각각 저장하고, 저장한 점수의 평균을 출력하고자 한다. 


먼저, Test 클래스에 대해 정의한다. KoreanTest, EnglishTest, MathTest는 Test 클래스를 상속한다. Test 클래스에서 __radd__ 매직함수를 구현한 이유는 추후에 덧셈을 할 때 클래스를 그대로 더하기 위함이다.


그리고, 이제 평균을 구할 수 있는 함수를 정의한다. avarage_scores 함수는 Test 클래스로 구성된 리스트를 파라미터로 전달받는다. 그리고 평균을 구해서 결과를 반환해 준다. 이때 tests 파라미터에는 KoreanTest, EnglishTest, MathTest 클래스가 모두 와도 된다. 왜냐하면 Test라는 클래스를 상속받고 있기 때문이다.

average_scores함수에서 sum(tests)가 가능한 이유는 앞서 __radd__ 함수를 구현했기 때문이다. __add__가 아니라 __radd__함수를 구현한 이유는 sum 함수가 기본적으로 시작이 0인 int에 무언가를 더하는 구조이기 때문이다. __add__는 자신보다 이후에 나오는 것과 덧셈을 할 때 불리고, __radd__는 자신보다 앞서서 나온 것과 덧셈을 할 때 불린다. 즉, 0 + KoreanTest 가 가능하도록 만들기 위해 구현했다고 볼 수 있다.


이후에 Student라는 class를 만들어서 실제 학생의 점수를 구하는 프로그램을 완성하면 끝이 난다. 결과를 보면 (50 + 90 + 30) / 3을 한 56.667이 나온 것을 확인할 수 있다. 


문제는 다른 average_scores라는 함수가 다른 곳에서 사용된다면 어떨까. 예컨대, 피겨 스케이팅 점수를 계산하는 함수가 필요하다고 해보자. Contest라는 함수를 별도로 만들고, 해당 클래스를 상속하는 FigureSkating이라는 클래스를 또 만든다. 그 이후에 Player라는 클래스에서 피겨 스케이팅 점수들을 받아서 평균 점수를 구한다. 


이때 평균 점수를 구하려면 별도의 average_scores2라는 함수를 만들어야 한다. 왜냐하면 average_scores는 Student 클래스에서 학생들의 성적을 계산하기 위한 함수이기 때문이다. 파라미터가 달라졌기 때문에 호환해서 사용하지 어렵다. 그렇다고 서로 다른 성격의 클래스가 동시에 상속하는 무언가를 만들기도 사실 어색한 부분이 있다.


두 함수 간의 로직은 결국 동일하다. 점수를 모두 더해서 평균을 내주는 것이다. 같은 기능을 하는 함수를 여러 개 만드는 것은 매우 비효율적이다.


이럴 때 유용하게 쓸 수 있는 것이 바로 Protocol이다.




Protocol은 Python 3.8 버전 이후부터 사용할 수 있는 새로운 타입이다. typing 패키지를 통해서 사용할 수 있으며, 공통으로 상속해야 하는 경우에 유용하게 만들 수 있다. 위와 같은 경우를 다음과 같은 함수로 간단하게 해결할 수 있다.



Score라는 클래스를 정의하고, 해당 클래스가 Protocol이라는 클래스를 상속하도록 한다. 그러면 파이썬은 score라는 변수를 가지고 있는 모든 클래스는 Score라는 클래스의 일부로 인식한다. 그래서 average_score라는 함수에 List [Score]를 파라미터로 넣게 되면, score라는 변수가 포함된 모든 객체에서 사용할 수 있으며, 심지어 서로 다른 객체여도 상관없이 계산이 가능하다.


아래 결과를 보면, EnglishTest, FigureStaking으로 만든 객체는 모두 Score의 instance로 인식된다. 직접 상속을 표기하지 않았지만, 내부적으로 상속처럼 인식한다는 것이다.


이렇게 Protocol을 사용하면, 동일한 형태를 사용하는 여러 클래스에서 사용할 수 있는 공통함수를 손쉽게 만들어낼 수 있다. 즉, 명시적으로 상속을 표현하지 않더라도, 인터페이스처럼 활용할 수 있다.



참고자료

https://www.pythontutorial.net/python-oop/python-protocol/

https://peps.python.org/pep-0544/#using-protocols


브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari