8.1 다형성
다형성(Poylmorphism)은 객체지향 프로그래밍(OOP)의 기본 개념 중 하나로, 서로 다른 클래스의 객체들이 동일한 인터페이스를 사용할 수 있도록 해줘.
Python에서 다형성은 동적 타입 지정과 상속을 통해 이뤄져. 다형성의 중요한 측면은 메소드 오버로딩과 기본 클래스의 메소드를 서브 클래스에서 재정의하는 것이야.
다형성의 주요 개념:
다양한 객체를 위한 통일된 인터페이스:
다형성은 다양한 클래스의 객체들이 서로 다른 메소드 구현을 가질 수 있는 통일된 인터페이스를 사용할 수 있게 해줘.
동적 타입 지정:
Python에서는 변수의 타입이 런타임 동안에 결정되기 때문에, 요구하는 인터페이스를 지원하는 모든 클래스의 객체와 함께 작동할 수 있어.
상속:
클래스 계층 구조를 생성하여 서브 클래스가 기본 클래스의 속성과 메소드를 상속 받고, 특정 기능을 위해 이를 재정의할 수 있게 해줘.
다형성의 예:
간단한 다형성 예시: 세 개의 클래스 모두 move()
라는 메소드를 가지고 있어. 이걸 통해 우리는 이 객체들과 동시에 작동할 수 있는 코드를 작성할 수 있지.
class Car:
def move(self):
pass
class Human:
def move(self):
pass
class Bird:
def move(self):
print("카!")
car = Car()
human = Human()
bird = Bird()
for it in [car, human, bird]:
it.move()
여기서 "공통 인터페이스"는 메소드의 이름이거나, 메소드가 인자를 가지고 있다면 그 시그니처야.
8.2 메소드 재정의
다형성은 자주 상속과 함께 사용되어, 기본 클래스가 공통 인터페이스를 정의하고, 하위 클래스가 특정 세부 사항을 구현하는 클래스 계층을 만들어.
class Employee:
def get_salary(self):
return 1000
class FullTimeEmployee(Employee):
def get_salary(self):
return 5000
class PartTimeEmployee(Employee):
def get_salary(self):
return 3000
class Intern(Employee):
pass
def print_salary(employee):
print(employee.get_salary())
employees = [FullTimeEmployee(), PartTimeEmployee(), Intern()]
for employee in employees:
print_salary(employee)
이 예시는 앞의 예시와 비슷하지만 중요한 차이점을 가지고 있어 — 우리 클래스들은 get_salary
메소드를 가질 필요가 없어, 왜냐면 기본 클래스에 항상 존재하니까. 이제 모든 클래스의 "공통 인터페이스"는 단순한 get_salary()
메소드가 아니라, Employee
클래스 전체와 그 모든 메소드와 속성이야.
8.3 서브 클래스 메소드 호출
아래 코드를 잘 봐:
우리는 기본 클래스 Employee에만 있는 print_salary()
메소드를 호출하고, 그것이 기본 클래스의 다른 메소드인 get_salary()
을 호출해. 그래서 화면에 어떤 급여가 출력될까?
class Employee:
def print_salary(self):
salary = self.get_salary()
print(salary)
def get_salary(self):
return 1000
class FullTimeEmployee(Employee):
def get_salary(self):
return 5000
class PartTimeEmployee(Employee):
def get_salary(self):
return 3000
class Intern(Employee):
pass
employees = [FullTimeEmployee(), PartTimeEmployee(), Intern()]
for employee in employees:
employee.print_salary()
중요!
아래에 적힌 내용을 읽어봐 — 이건 중요해.
만약 print_salary()
메소드를, 예를 들어 FullTimeEmployee
클래스에 대해서 호출하면, 해당 메소드는 클래스에 선언된 것이 없기 때문에 기본 클래스의 메소드가 호출될 거야.
하지만 이 print_salary()
메소드는 FullTimeEmployee
타입의 self
객체에서 get_salary()
메소드를 호출할 거야, Employee
가 아니라. 그래서 FullTimeEmployee
클래스의 get_salary()
메소드가 호출되게 돼!
게다가, FullTimeEmployee
클래스는 필요에 따라 기본 클래스의 get_salary()
메소드를 호출할 수 있어. 예를 들어, 우리는 기본 급여에서 퍼센트로 급여를 계산하고 싶을 수도 있어:
class FullTimeEmployee(Employee):
def get_salary(self):
base_salary = super().get_salary()
return base_salary * 5
# 500%
class PartTimeEmployee(Employee):
def get_salary(self):
base_salary = super().get_salary()
return base_salary * 3 # 300%
8.4 메소드 오버로딩
Python에서 메소드 오버로딩(method overloading)은 동일한 이름이지만, 다른 인자를 가진 여러 메소드를 생성할 수 있는 능력이야. 하지만 Python에서는 다른 언어(C++나 Java)를 사용하는 것처럼 메소드 오버로딩이 순수하게 지원되지 않아.
Python에서는 기본 인수, *args
그리고 **kwargs
를 사용하여 메소드 오버로딩을 흉내낼 수 있어.
메소드 오버로딩 예시
전달된 인수의 수에 따라 다른 동작을 하는 함수를 구현할 수 있어. 이걸 여러가지 방법으로 할 수 있는데, 예를 들어:
class Example:
def display(self, a=None, b=None):
if a is not None and b is not None: print(a, b)
elif a is not None: print(a)
else: print("인수 없음")
obj = Example()
obj.display(1, 2) # Output: 1 2
obj.display(1) # Output: 1
obj.display() # Output: 인수 없음
전달된 데이터 유형도 고려할 수 있어 — 타입 검사를 통해서 간단하게:
class Example:
def mod(self, a, b):
if type(a) == int and type(b) == int: print(a % b)
elif type(a) == float or type(b) == float: print(round(a / b))
else: print("도움말: a와 b는 int 또는 float이어야 합니다.")
obj = Example()
obj.mod(5, 2) # Output: 1
obj.mod(5.0, 2) # Output: 2
obj.mod("5", 2) # Output: 도움말: a와 b는 int 또는 float이어야 합니다.
잘못된 데이터 유형이 전달된 경우, 도움말이나 심지어 인터넷 문서 링크를 출력할 수 있어 — 이것도 매우 인기 있는 해결책 중 하나야.
GO TO FULL VERSION