상속
상속은 객체 지향 프로그래밍의 기본 개념중 하나며, 클래스 간의 기본적인 관계. 즉, 슈퍼클래스와 하위클래스 간의 관계를 나타낸다.
상속은 클래스 계층 구조를 생성하게된다. 특정 수준의 클래스 계층구조 바인딩된 모든 객체는 슈퍼클래스 내부에 정의된 모든 특성들(메서드와 속성)을 상속받는다.
그 말은 즉, 상속은 처음부터 새로 만드는 것이 아니라 이미 정의된 특정 레퍼토리를 활용하여 새로운 클래스를 만드는 방식이다.
새로운 클래스는 기존 특성들을 상속받지만 필요의 경우 새로운 기능을 추가할 수 있다. 이점이 중요하다.
하위 클래스로 갈수록 상위클래스보다 구체적이고 특수화된다. 반대로 상위클래수로 갈수록 추상적이고 단순화된다.
쉽게 정리한다면,
객체 지향 언어의 큰 특징으로 부모 클래스가 가진 모든 속성을 자식에게 물려주는 것을 의미한다.
동일한 코드가 반복되지 않고 공통된 속성을 부모 클래스에서 관리하여 유지보수성을 높일 수 있다.
형식
class 부모클래스:
def __init__(self):
print("부모 클래스 생성자")
def 부모메서드(self):
print("부모 메서드 호출")
class 자식클래스(부모클래스):
def __init__(self):
super().__init__() # 부모 클래스 생성자 호출
print("자식 클래스 생성자")
def 자식메서드(self):
print("자식 메서드 호출")
super()
super() 은 부모 클래스의 메서드나 생성자를 호출하기 위해 사용된다.
super()를 사용하지 않고 부모클래스를 부모클래스.부모메서드로 접근할 수 있지만, 이는 유지 보수 측면에서 super()를 사용하는 것이 더 유연하다.
특히 다중 상속에서 MRO(Method Resolution Order)를 통해 안정적인 부모 메서드 호출이 가능하다.
class 부모:
def __init__(self):
print("부모 생성자")
def say_hello(self):
print("부모: 안녕하세요")
class 자식(부모):
def __init__(self):
# 부모 클래스의 __init__을 호출
super().__init__()
print("자식 생성자")
def say_hello(self):
# 부모 클래스의 say_hello를 호출
super().say_hello()
print("자식: 안녕!")
다중 상속
파이썬은 다중 상속을 지원한다.
2개 이상의 부모클래스를 상속하는 것을 의미하고, 모든 속성을 그대로 상속하고, 메서드 오버라이딩도 가능하다.
다중 상속은 단일 상속보다 더 신중하게 사용되어야한다. 그 이유는 다음과 같다.
1. 단일 상속 클래스는 항상 더 간단하고 안전하며, 이해하고 유지관리하기 쉽다.
2. 다중 상속으로 인해 메서드 오버라이딩이 까다로울 수 있다. 특히 super()함수를 사용하면 모호함이 발생할 수 있다.
3. 다중 상속을 구현하면 단일 책임 원칙을 위반할 가능성이 높다.
만약 다중 상속이 필요한 경우 implementing composition 을 고려하는 것도 좋은 방법이다.
메서드 결정 순서는 클래스 속성이 __mro__에서 정의한다 (Method Resolution Order)
python 3 부터는 모든 클래스의 선언부를 명시하지 않아도 object 클래스를 묵시적으로 상속한다.
즉 파이썬은 모든 객체가 만들어질 때 object 객체가 기본적으로 만들어졌다고 보면 된다.
class A:
def greeting(self):
print("A: 안녕")
class B:
def greeting(self):
print("B: 헬로우")
class C(A, B): # A, B 둘 다 상속
pass
c = C()
c.greeting()
# 결과
# A : 안녕
메서드 결정 짓는 순서를 보고 싶다면, 해당 자식클래스.__mro__이나 해당 자식클래스.mro()으로 출력해서 볼 수 있다.
부모클래스를 클래스의 파라미터형식으로 표기가 되는데 , mro는 앞에서 뒤로 진행되는 것을 볼 수 있다.
- 다이야몬드형 다중상속
class A:
def show(self):
print("A")
class B(A):
def show(self):
print("B")
super().show()
class C(A):
def show(self):
print("C")
super().show()
class D(B, C): # 다중 상속
def show(self):
print("D")
super().show()
d = D()
d.show()
print(D.__mro__)
이에 대한 결과는 어떻게 될까 ? 한 번 생각해봐도 좋을 것 같다.
결과는 다음과 같다.
D
B
C
A
이 결과는 __mro__를 찍어보면 확인해 볼 수 있다. 실행되는 순서가 다음과 같다.
print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
MRO의 불일치
class A:
def info(self):
print("Class A")
class B(A):
def info(self):
print("Class B")
class C(A):
def info(self):
print("Class C")
class D(A, C):
pass
print(D.__mro__)
A ㅡ B
ㄴ C
|
ㄴ D
현재 이런상황...
이렇게 될 경우 문제가 생긴다.
MRO에 따라 풀어서 생각해보면 , 우선 D는 A 를 상속한다. 이후 C를 바라보면서 A 와 C를 상속하게된다.
즉, A가 두번 상속되면서, 순서가 꼬이기 쉬운 구조가 발생한다.
D → A → C → A
A 가 두번 나타나게 되면서, 무엇이 우선되는지 순서를 결정할 수 없는 모순점이 생겨버리는 것이다.
A는 C의 부모인데, C 보다 우선순위가 높은것으로 취급된다는 모순이다.
자바에서는 다중 상속을 사용했던가 잠깐 생각해보게 된다.
생각해보면 사용하지 않은 것 같다. 계층적으로 확장해나갔지, 하위계층에서 여러개의 상위계층을 포함한 적이 있는가 생각해보게 된다.
class A {
void hello() { System.out.println("A"); }
}
class B extends A {
void hello() { System.out.println("B"); }
}
class C extends A {
void hello() { System.out.println("C"); }
}
// 자바에서는 아래처럼 B, C를 동시에 상속할 수 없음
class D extends B, C { // 컴파일 오류!
// 어떤 hello()를 쓸지 모호함
}
그렇다. 자바에서는 다중 상속을 지원하지 않고, 다중 구현은 지원하였다.
다이야몬드형 상속상 super 키워드의 문제가 발생하게 되기 때문이다.
interface A {
default void hello() { System.out.println("A"); }
}
interface B {
default void hello() { System.out.println("B"); }
}
class C implements A, B {
// 둘 다 hello() 가지고 있으니 오버라이딩 필요
public void hello() {
A.super.hello(); // 명시적으로 선택
}
}
다중 인터페이스를 구현하게 되면 명시적으로 어떤 인터페이스를 사용할지 명시를 해줘야 한다.
이쯤 다시 궁금해진다.
그렇다면 오버라이딩이 super()를 통해 된다면,
오버 로딩은 ?
파이썬의 오버로딩(overloading)
파이썬은 기본적으로 오버로딩을 지원하지 않는다.
똑같은 이름으로 두번 정의하면 마지막이 덮어씌워진다.
클래스에 __init__(self,name) , __init__(self) 를 정의하면, 경고등이 뜨면서
클래스 생성시에 인자 두개를 넣으면 마지막인 __init__(self)가 발동되면서
__init__() takes 1 positional argument but 2 were given 이라는 오류가 발생하게 된다.
아래에 다른 예시를 확인해보자.
def greet(name):
print("Hello", name)
def greet(name, age):
print(f"Hello {name}, you are {age} years old.")
greet("길동")
# TypeError! → 마지막에 정의된 greet()만 살아남고, 이전 greet는 덮어써짐
그렇다면 어떻게 오버로딩을 구현할까 ?
생각보다 간단한 문제였다.
파이썬의 디폴트 파라미터 또는 가변인자를 활용할 수 있다.
디폴트 파라미터 활용
def greet(name, age=None):
if age:
print(f"{name}, {age}살 반가워~")
else:
print(f"{name}, 안녕~")
greet("길동") # 길동, 안녕~
greet("길동", 30) # 길동, 30살 반가워~
*arg 활용
def greet(*args):
if len(args) == 1:
print(f"{args[0]}, 안녕~")
elif len(args) == 2:
print(f"{args[0]}, {args[1]}살 반가워~")
else :
print("이름 없음")
greet()
greet("길동")
greet("길동", 30)
*arg,**kwargs 활용
def greet(*args, **kwargs):
# 위치 인자가 1개만 들어오면: 이름만
if len(args) == 1 and not kwargs:
name = args[0]
print(f"안녕 {name}")
# 위치 인자가 2개면: 이름, 나이
elif len(args) == 2:
name, age = args
print(f"안녕 {name}, 너는 {age}살이구나")
# 키워드 인자로만 받았을 때
elif "name" in kwargs and "age" in kwargs:
print(f"안녕 {kwargs['name']}, 너는 {kwargs['age']}살이구나")
# 그 외는 에러 처리
else:
print("지원하지 않는 인자입니다")
# 호출 예시
greet("길동")
greet("길동", 30)
greet(name="길동", age=30)
greet(age=30) # 지원하지 않는 인자입니다
다른 구현 형식으로는 데코레이터가 있다고 하지만 아직 공부하기 전이니 추후에 기억해서 만들어보자.
'Developer > Python' 카테고리의 다른 글
[python] 동적 함수 인수(*args, **kwargs) (0) | 2025.06.19 |
---|---|
[python] 파이썬의 다형성(Polymorphism) (0) | 2025.06.19 |
[python] 매직 메서드(Magic Methods) (0) | 2025.06.18 |
[python] 클래스 변수와 인스턴스 변수 그리고 __dict__ , hasattr() (0) | 2025.06.12 |
[python] isinstance()와 issubclass()의 차이점 (0) | 2025.05.22 |