클래스 내부 데이터를 안전하게 보호하는 캡슐화의 원리와 @property 데코레이터 활용법을 자세히 살펴봐요. 상속에 얽매이지 않고 파이썬 고유의 덕 타이핑을 통해 코드 유연성을 극대화하는 다형성도 알아볼게요. 파이썬 내장 연산자 및 함수와 객체를 자연스럽게 연동하는 매직 메서드의 올바른 코딩 관례까지 한 번에 정리해 두면 객체지향의 큰 그림을 그릴 수 있을 거예요.
목차
- 캡슐화 — 데이터를 안전하게 보호하는 방법
- 다형성과 덕 타이핑 — 같은 이름, 다른 동작
- 매직 메서드(던더 메서드) — Python의 숨겨진 힘
- 실전 예제로 정리하는 OOP 4대 원칙
- 자주 묻는 질문
캡슐화 — 데이터를 안전하게 보호하는 방법
은행 계좌를 한번 생각해봐요. 잔액을 아무나 마음대로 바꿀 수 있으면 큰일 나겠죠? 누군가 실수로 0을 하나 더 붙이거나, 악의적으로 잔액을 마이너스로 만들어버린다면 은행 시스템은 순식간에 엉망이 될 거예요. 캡슐화는 이렇게 클래스 내부 데이터를 보호하고, 정해진 방법으로만 변경할 수 있도록 강제하는 아주 중요한 개념이에요. 객체지향 프로그래밍을 시작할 때 가장 먼저 배우는 핵심 원칙이기도 하죠.
자바나 C++ 같은 다른 프로그래밍 언어를 조금이라도 경험해보셨다면 private이나 protected 같은 접근 제어자 키워드가 익숙하실 거예요. 그런데 파이썬 코드를 짤 때는 이런 명시적인 키워드를 본 적 없으시죠? 맞아요. 파이썬은 언어 차원에서 완벽하게 접근을 차단하는 시스템을 제공하지 않아요. 대신 개발자들끼리의 ‘신뢰’와 ‘관례’를 바탕으로 접근을 제어하죠. 파이썬 철학 중 하나인 “우리는 모두 동의한 성인이다”라는 말이 이 상황을 잘 보여줘요. 강제하지 않을 테니 스스로 알아서 조심하자는 뜻이죠.
파이썬에서는 변수 이름 앞에 밑줄(_)을 붙여서 이 변수가 내부용이라는 의도를 표현해요. 밑줄 하나(_변수)를 붙이면 “이건 클래스 내부에서만 쓸 거니까 관례적으로 밖에서는 건드리지 마세요”라는 정중한 경고예요. 시스템적으로 강제성은 전혀 없지만, 이 경고를 무시하고 접근했다가 나중에 코드가 꼬이면 그건 접근한 사람의 책임이 되는 거예요.
하지만 조금 더 강력한 보호가 필요할 때가 있죠? 그럴 땐 밑줄 두 개(__변수)를 붙이면 외부에서 쉽게 접근할 수 없게 돼요. 마치 자바의 private처럼 동작하는 것처럼 보이죠. 이게 가능한 이유는 파이썬 캡슐화와 네임 맹글링 원리 때문이에요.
네임 맹글링(Name Mangling)은 파이썬 인터프리터가 내부적으로 변수 이름을 _클래스명__변수 형태로 슬쩍 바꿔버리는 기능이에요. 그래서 우리가 원래 지어준 이름으로는 외부에서 도저히 찾을 수가 없어서 자연스럽게 접근이 막히는 거죠. 데이터를 이렇게 안전하게 지키면서도, 필요할 때 외부에서 값을 읽고 쓰게 하려면 @property 데코레이터와 setter를 활용하면 완벽해요.
코드로 확인하는 캡슐화와 네임 맹글링
은행 계좌 예제를 통해 어떻게 데이터를 보호하고 안전하게 값을 수정하는지 코드로 직접 확인해봐요. 여기서 중요한 건 ‘올바른 방식’으로만 값을 변경하도록 유도하는 거예요.
코드를 보면 account.__balance로 직접 접근하려 할 때 속성이 없다는 에러가 나요. 네임 맹글링 때문에 내부적으로 이름이 바뀌었기 때문이죠. 하지만 우리가 정성껏 만들어둔 setter를 거치면, 우리가 정해둔 유효성 검사 규칙을 철저하게 지키면서 값을 안전하게 바꿀 수 있다는 걸 알 수 있어요. 이렇게 하면 누군가 실수로 잔액을 음수로 설정하는 끔찍한 논리적 오류를 원천적으로 차단할 수 있답니다. 정말 안심이 되죠?
💡 Tip
파이썬에서는 자바처럼 get_balance(), set_balance() 같은 길고 불편한 메서드를 따로 만드는 것보다 @property를 사용하는 것이 훨씬 “파이썬다운(Pythonic)” 방식이에요. 코드를 읽을 때 그냥 변수에 접근하는 것처럼 자연스럽게 보이기 때문이죠.
다형성과 덕 타이핑 — 같은 이름, 다른 동작
다형성은 한자어 그대로 ‘여러 가지 형태를 가진다’는 뜻이에요. 프로그래밍에서는 같은 메서드 이름을 호출해도 객체의 클래스마다 각자 다르게 동작하는 것을 말해요. 다형성을 잘 활용하면 코드를 훨씬 유연하게 만들어주고, 복잡한 if-elif 조건문 반복 작업을 확 줄여주는 마법 같은 효과를 볼 수 있어요. 보통은 부모 클래스에서 공통 인터페이스(메서드의 기본 틀)를 만들어두고, 자식 클래스에서 이를 구체적으로 자기 입맛에 맞게 구현하는 방식으로 자주 사용해요.
하지만 파이썬은 여기서 한 걸음, 아니 두 걸음 더 나아가요. 자바 같은 엄격한 정적 타입 언어와 달리, 파이썬은 굳이 상속에 의존하지 않고도 다형성을 아주 멋지게 구현할 수 있어요. 객체가 어떤 부모 클래스에게서 상속받았는지 족보를 깐깐하게 따지지 않고, 그저 호출하려는 메서드를 지금 이 순간 가지고 있는지만 확인하는 유연한 방식을 사용하거든요. 이를 파이썬 다형성 구현과 덕 타이핑이라고 불러요.
이름이 좀 귀엽죠? “오리처럼 걷고 오리처럼 꽥꽥거리면, 그건 오리다”라는 재밌는 프로그래밍 격언에서 유래한 말이에요. 즉, 객체의 근본적인 타입이 무엇인지가 중요한 게 아니라, 객체가 당장 할 수 있는 ‘행동’이 중요하다는 실용적인 철학이죠. 어떤 객체든 quack()이라는 메서드만 무사히 실행할 수 있다면, 그게 진짜 생물 오리든, 태엽 감는 로봇 오리든, 나무 조각 오리든 상관없이 오리처럼 취급해주겠다는 관대함이 바로 파이썬의 진짜 매력이에요.
상속 없는 다형성 맛보기
상속을 제대로 받은 도형 객체들과, 도형과는 전혀 관계없는 뜬금없는 외계인 객체가 어떻게 같은 이름의 메서드를 통해 다형성으로 묶이는지 코드로 재미있게 확인해볼게요. 여러 종류의 객체를 리스트에 한 번에 담아두고 반복문으로 쫙 실행할 때, 이 덕 타이핑의 진가가 확실하게 발휘돼요.
상속 관계가 1도 없는 Alien 클래스도 area()라는 이름의 메서드를 우연히 가지고 있기 때문에, 에러 없이 반복문 안에서 다른 도형들과 동일하게 순조롭게 처리돼요. 이것이 바로 파이썬의 유연성을 극대화하는 덕 타이핑의 힘이랍니다. 나중에 새로운 클래스를 추가할 때 기존 코드를 수정할 필요 없이, 그저 필요한 메서드만 똑같이 맞춰서 구현해주면 되니까 유지보수가 정말 편해지겠죠?
매직 메서드(던더 메서드) — Python의 숨겨진 힘
파이썬 오픈소스나 다른 사람의 코드를 보다 보면 양쪽에 밑줄이 두 개씩 붙은 __init__ 같은 독특한 메서드를 자주 보셨을 거예요. 이런 걸 파이썬 생태계에서는 매직 메서드 혹은 던더(Double Under) 메서드라고 불러요. 마법이라는 이름이 붙은 이유는, 우리가 직접 명시적으로 호출하지 않아도 파이썬의 내장 함수나 +, == 같은 기본 연산자가 사용될 때 내부적으로 짝꿍처럼 자동으로 실행되기 때문이에요.
이 매직 메서드를 잘 다루는 것이 초보와 고수를 나누는 기준이 되기도 해요. 매직 메서드를 클래스 안에 잘 정의해두면, 우리가 만든 커스텀 객체도 파이썬의 기본 자료형(리스트나 딕셔너리처럼)과 아주 자연스럽게 어울려서 동작하게 만들 수 있거든요. 예를 들어, 두 객체를 더하고 싶을 때 obj1.add(obj2)라고 촌스럽게 메서드를 호출하는 대신, obj1 + obj2라고 아주 직관적이고 깔끔하게 쓸 수 있게 해주는 고마운 녀석들이죠.
가장 많이 헷갈리시는 것이 객체를 문자열로 표현하는 __str__과 __repr__의 차이일 거예요. __str__은 일반 사용자에게 보여주기 위한 깔끔하고 읽기 편한 문자열을 만들 때 써요. 반면에 __repr__은 개발자가 디버깅을 하거나 로그를 남길 때 객체의 정확한 상태와 타입을 꼼꼼하게 확인하기 위해 쓰죠. 콘솔 창에서 객체 이름만 딱 입력하고 엔터를 쳤을 때 툭 튀어나오는 상세한 결과가 바로 이 __repr__의 반환값이랍니다.
또 한 가지 현업에서 정말 자주 틀리는 중요한 점이 있어요. 2차원 평면의 벡터 같은 객체를 만들 때, 벡터의 크기나 길이를 구하려고 무심코 __len__ 매직 메서드를 쓰는 분들이 꽤 많아요. 직관적으로 ‘길이’니까 len()을 쓰면 될 것 같죠? 하지만 파이썬 매직메서드 올바른 사용법에 따르면, __len__은 리스트나 문자열처럼 ‘요소의 개수(정수)’를 반환할 때 쓰는 것이 확고한 관례예요. 벡터의 대각선 길이나 기하학적인 거리는 소수점이 나올 수 있잖아요? 이럴 때는 거리를 뜻하는 __abs__를 사용해서 abs() 내장 함수와 연동하는 것이 훨씬 올바르고 파이썬다운 코딩 방식이에요.
연산자 오버로딩과 매직 메서드 구현
매직 메서드를 적극적으로 활용해 객체끼리 직관적으로 덧셈 연산을 하고, 크기를 올바른 규칙에 맞게 계산하는 멋진 벡터 클래스를 만들어볼게요.
매직 메서드를 제대로 정의해주면, 우리가 만든 클래스도 파이썬이 원래 제공하는 기본 기능들처럼 아주 자연스럽게 더하고 비교할 수 있어서 전체 코드가 눈에 띄게 깔끔해져요. 여러분의 코드 퀄리티를 한 단계 높이고 싶다면 매직 메서드와 꼭 친해지셔야 해요!
⚠️ Warning
매직 메서드를 정의할 때는 항상 반환값의 타입을 신경 써야 해요. 예를 들어 __eq__는 반드시 불리언(True 또는 False)을 반환해야 하고, __len__은 반드시 0 이상의 정수를 반환해야 해요. 규칙을 어기면 파이썬 내부에서 예상치 못한 에러가 발생할 수 있으니 꼭 주의하세요.
실전 예제로 정리하는 OOP 4대 원칙
지금까지 객체지향 프로그래밍(OOP)의 4대 원칙인 캡슐화, 상속, 다형성, 추상화를 개별적으로 알아봤어요. 하지만 진짜 실력은 이 조각난 지식들을 어떻게 하나의 프로그램으로 엮어내느냐에 달려있죠. 이제 이 원칙들을 하나의 재밌는 예제로 묶어서 총정리해볼게요. 단편적으로 배운 개념들이 어떻게 유기적으로 톱니바퀴처럼 맞물려 돌아가는지 확인해 보는 시간이에요.
간단한 콘솔 기반의 RPG 게임 캐릭터를 만든다고 상상해봐요. 캐릭터의 생존과 직결된 체력 같은 민감한 데이터는 외부에서 마음대로 조작하지 못하게 꽁꽁 숨겨두고(캡슐화), 모든 캐릭터가 공통으로 가져야 하는 이름이나 데미지 입는 기능은 부모 클래스로 하나로 묶어서 재사용할 거예요(상속). 그리고 복잡한 공격 동작은 부모 클래스에서 이름만 껍데기로 정해두고 자식들에게 구현을 미룬 다음(추상화), 전사나 마법사 같은 직업에 따라 무기를 휘두르거나 마나를 소모하는 등 각기 다른 방식의 공격을 실행(다형성)하게 만들 거예요. 생각만 해도 흥미롭지 않나요?
이 과정을 코딩하다 보면 초보자들이 무심코 저지르는 실수들이 있어요. ‘setter를 만들어두지도 않고 속성을 외부에서 마음대로 변경하려고 시도’하거나, ‘객체의 내부 값을 비교해야 하는데 __eq__를 몰라서 쓸데없이 메모리 주소를 비교해버리는 오류’ 같은 것들이죠. 이런 흔한 함정들을 어떻게 우아하게 피해 가는지도 코드의 주석에 꼼꼼하게 담아두었어요. 각 클래스가 어떤 역할을 담당하고 있는지 코드의 흐름을 천천히 따라가며 읽어보세요.
캐릭터 클래스로 보는 OOP 종합 선물 세트
어떠신가요? 이렇게 객체지향의 원칙들을 하나로 정성스럽게 조립해보면, 코드가 논리적으로 훨씬 안전해지고 나중에 새로운 기능을 덧붙이기 쉬워진다는 걸 피부로 느낄 수 있을 거예요. 만약 나중에 게임이 업데이트되어서 ‘궁수’나 ‘도적’ 같은 새로운 직업을 추가해야 한다고 해도 두려울 게 전혀 없어요. 기존에 잘 작동하던 코드는 단 한 줄도 건드리지 않고, 그저 Character를 상속받는 새로운 클래스만 하나 뚝딱 만들면 완벽하게 시스템에 녹아들 테니까요. 이게 바로 우리가 골치 아픈 객체지향 원칙을 힘들게 배우는 진짜 이유랍니다!
자주 묻는 질문
Q. 클래스 내부 변수명 앞에 __(이중 밑줄)을 붙이면 외부에서 절대로 접근할 수 없게 되나요?
파이썬은 완벽한 접근 차단을 지원하지 않아요. __를 붙이면 내부적으로 이름이 _클래스명__변수명으로 슬쩍 바뀌는 ‘네임 맹글링’이 일어날 뿐이에요. 우발적인 접근이나 상속 시 이름이 겹쳐서 충돌하는 것을 막아주는 안전장치 역할이지, 바뀐 진짜 이름으로 호출하면 외부에서도 얼마든지 접근 가능하답니다.
Q. 파이썬에서는 상속을 받지 않으면 다형성을 구현할 수 없나요?
그렇지 않아요. 자바나 C++ 같은 언어와 달리, 파이썬은 객체의 행동(메서드)에 집중하는 ‘덕 타이핑(Duck Typing)’을 전폭적으로 지원해요. 객체들 사이에 상속 족보가 전혀 엮여있지 않더라도, 호출하려는 동일한 이름의 메서드만 가지고 있다면 완벽하게 다형성을 구현하고 활용할 수 있어요.
Q. 벡터 객체 등의 크기나 거리를 구할 때 __len__ 매직 메서드를 사용하면 왜 안 되나요?
파이썬 관례상 __len__은 리스트나 문자열 같은 컨테이너 안에 들어있는 ‘요소의 개수(정수)’를 반환할 때 사용해야 해요. 2차원 평면의 기하학적 크기나 거리는 소수점이 나올 수 있는 물리적인 값이잖아요? 이럴 때는 파이썬의 표준 코딩 컨벤션에 따라 __abs__ 매직 메서드를 사용해 abs() 내장 함수와 연동하는 것이 훨씬 직관적이고 올바른 방법이에요.