파이썬스러운 객체
"파이썬스러운 객체"라는 표현은 Python의 철학과 관례를 잘 따르는 객체를 의미한다. Python은 명확하고 간결하며 직관적인 코딩 스타일을 지향하며, 이러한 철학을 잘 반영한 객체를 "파이썬스럽다"고 표현할 수 있다.
Pythonic한 객체의 특징
- 명확하고 간결한 코드: Pythonic한 객체는 코드를 읽기 쉽고 이해하기 쉽게 작성한다. 복잡한 로직을 단순하고 직관적으로 표현하려고 노력한다.
- PEP 8 준수: Python의 스타일 가이드인 PEP 8을 준수한다. 이는 일관된 코드 스타일을 유지하고, 가독성을 높이는 데 도움이 된다.
- Duck Typing: Pythonic한 객체는 덕 타이핑을 따른다. 객체의 타입보다는 객체가 필요한 메서드나 속성을 가지고 있는지에 중점을 둔다.
- 공개와 비공개 속성: 객체의 속성과 메서드를 명확히 구분하여, 공개(public) 속성과 비공개(private) 속성을 적절히 사용한다. 비공개 속성은 두 개의 언더스코어(__)로 시작하도록 한다.
- Magic Methods: 특수 메서드(magic methods)를 적절히 활용한다. 예를 들어, __str__, __repr__, __eq__, __hash__ 등을 구현하여 객체가 표준 인터페이스를 따르도록 한다.
- 컨테이너와 이터레이터: 컨테이너 객체나 이터레이터 객체를 구현할 때는 __iter__, __next__ 메서드를 제공하여 Python의 이터레이션 프로토콜을 따른다.
- 예외 처리: 적절한 예외 처리를 통해 코드의 안정성을 높인다. 예외를 포착하고 처리하는 방식을 명확하게 정의한다.
__str__과 __repr__ 메서드 구현
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Point({self.x}, {self.y})"
def __repr__(self):
return f"Point({self.x}, {self.y})"
point = Point(1, 2)
print(point) # 출력: Point(1, 2)
print(repr(point)) # 출력: Point(1, 2)
덕 타이핑과 이터레이터
class IterableContainer:
def __init__(self, items):
self.items = items
def __iter__(self):
return iter(self.items)
container = IterableContainer([1, 2, 3])
for item in container:
print(item) # 출력: 1 2 3
예외 처리
class Calculator:
def divide(self, a, b):
try:
return a / b
except ZeroDivisionError:
return "Cannot divide by zero"
calc = Calculator()
print(calc.divide(10, 2)) # 출력: 5.0
print(calc.divide(10, 0)) # 출력: Cannot divide by zero
이러한 원칙들을 따르는 객체는 Python의 철학과 관례에 맞는, 즉 "파이썬스러운" 객체라고 할 수 있다.
객체 표현에 따른 repr()과 str()
Python에서는 객체를 문자열로 표현하는 두 가지 주요 방법인 repr()과 str() 함수를 제공한다. 이 두 함수는 객체를 문자열로 변환하는 데 사용되지만, 목적과 용도가 다르다. 아래에 각 함수의 역할과 차이점을 설명하겠다.
repr()
- 목적: repr() 함수는 객체의 "공식적인" 문자열 표현을 반환한다. 이 표현은 주로 디버깅과 개발을 위해 사용된다. 객체의 __repr__ 메서드가 호출된다.
- 요구사항: 반환되는 문자열은 가능한 한 객체를 재생성할 수 있는 형태여야 한다. 즉, eval(repr(obj)) == obj가 참이 되는 것이 이상적이다.
- 사용 사례: 디버깅 시 객체의 상태를 출력하거나 로그에 남길 때 사용한다.
class MyClass:
def __init__(self, value):
self.value = value
def __repr__(self):
return f"MyClass({self.value})"
obj = MyClass(42)
print(repr(obj)) # 출력: MyClass(42)
str()
- 목적: str() 함수는 객체의 "비공식적인" 문자열 표현을 반환한다. 이 표현은 주로 사용자에게 읽기 쉽고 의미 있는 정보를 제공하기 위해 사용된다. 객체의 __str__ 메서드가 호출된다.
- 요구사항: 반환되는 문자열은 사용자에게 읽기 쉽고 의미 있어야 한다. 반드시 객체를 재생성할 수 있는 형태일 필요는 없다.
- 사용 사례: 사용자 인터페이스, 보고서 생성, 출력문 등 사용자에게 정보를 제공할 때 사용한다.
class MyClass:
def __init__(self, value):
self.value = value
def __str__(self):
return f"Value is {self.value}"
obj = MyClass(42)
print(str(obj)) # 출력: Value is 42
주요 차이점
- 목적:
- repr(): 디버깅과 개발을 위한 객체의 공식적인 문자열 표현.
- str(): 사용자에게 읽기 쉽고 의미 있는 비공식적인 문자열 표현.
- 재생성 가능성:
- repr(): 가능한 한 객체를 재생성할 수 있는 문자열 반환.
- str(): 재생성 가능성보다는 가독성에 중점.
- 기본 구현:
- __repr__이 정의되지 않고 __str__만 정의된 경우, repr()은 기본적으로 __str__을 호출한다.
- __str__이 정의되지 않은 경우, str()은 기본적으로 __repr__을 호출한다.
두 메서드를 함께 사용:
class MyClass:
def __init__(self, value):
self.value = value
def __repr__(self):
return f"MyClass({self.value!r})"
def __str__(self):
return f"Value is {self.value}"
obj = MyClass(42)
print(repr(obj)) # 출력: MyClass(42)
print(str(obj)) # 출력: Value is 42
위 예제에서 repr(obj)는 객체를 MyClass(42)로 표현하여 객체를 재생성할 수 있는 문자열을 반환한다. 반면 str(obj)는 사용자에게 더 친숙하고 의미 있는 Value is 42를 반환한다.
이렇게 repr()과 str()을 적절히 사용하면 객체의 문자열 표현을 더 명확하게 관리하고, 디버깅과 사용자 상호작용을 보다 효율적으로 할 수 있다.
@classmethod와 @staticmethod
Python에서 @classmethod와 @staticmethod는 클래스 메서드를 정의하는 두 가지 데코레이터이다. 이 두 데코레이터는 클래스와 메서드 간의 관계를 제어하는 데 사용되며, 각기 다른 목적과 사용법을 가지고 있다. 아래에 각 데코레이터의 역할과 차이점을 설명하겠다.
@classmethod
- 정의: 클래스 메서드를 정의할 때 사용되는 데코레이터이다. 클래스 메서드는 클래스 자체를 첫 번째 인자로 받는다. 일반적으로 cls라는 이름을 사용하여 클래스 자체를 참조한다.
- 용도: 클래스 상태를 조작하거나 클래스 레벨에서 동작하는 메서드를 정의할 때 사용된다. 인스턴스 대신 클래스에 바인딩되므로, 클래스의 인스턴스 없이도 호출할 수 있다.
class MyClass:
count = 0
def __init__(self):
MyClass.count += 1
@classmethod
def get_count(cls):
return cls.count
obj1 = MyClass()
obj2 = MyClass()
print(MyClass.get_count()) # 출력: 2
위 예제에서 get_count 메서드는 클래스 메서드로 정의되었으며, 클래스 변수 count의 값을 반환한다. 이 메서드는 클래스 자체를 통해 호출된다.
@staticmethod
- 정의: 정적 메서드를 정의할 때 사용되는 데코레이터입니다. 정적 메서드는 클래스나 인스턴스를 첫 번째 인자로 받지 않는다. 이는 일반 함수와 동일하게 동작하지만 클래스의 네임스페이스 내에 정의된다.
- 용도: 클래스와 관련이 있지만 클래스나 인스턴스의 상태를 조작하지 않는 메서드를 정의할 때 사용된다. 보통 유틸리티 함수나 헬퍼 함수로 사용된다.
- 예제:위 예제에서 is_even 메서드는 정적 메서드로 정의되었으며, 특정 숫자가 짝수인지 여부를 확인한다. 이 메서드는 클래스나 인스턴스의 상태를 전혀 사용하지 않는다.
- class MyClass: @staticmethod def is_even(number): return number % 2 == 0 print(MyClass.is_even(4)) # 출력: True print(MyClass.is_even(5)) # 출력: False
주요 차이점
- 첫 번째 인자:
- @classmethod: 클래스 메서드는 클래스를 첫 번째 인자로 받는다 (cls).
- @staticmethod: 정적 메서드는 어떤 특별한 첫 번째 인자도 받지 않는다.
- 상태 조작:
- @classmethod: 클래스나 클래스 변수를 조작할 수 있다.
- @staticmethod: 클래스나 인스턴스의 상태를 조작하지 않으며, 보통 유틸리티 함수로 사용된다.
- 호출 방식:
- 두 메서드 모두 클래스 이름을 통해 직접 호출할 수 있다. 인스턴스를 통해서도 호출할 수 있지만, 클래스 메서드는 인스턴스를 통한 호출 시 클래스가 전달되고, 정적 메서드는 어떤 특별한 인자도 전달받지 않는다.
@classmethod와 @staticmethod비교:
class Example:
count = 0
def __init__(self):
Example.count += 1
@classmethod
def get_count(cls):
return cls.count
@staticmethod
def is_positive(number):
return number > 0
# 클래스 메서드 호출
print(Example.get_count()) # 출력: 0
# 인스턴스 생성
ex1 = Example()
ex2 = Example()
# 클래스 메서드 호출
print(Example.get_count()) # 출력: 2
# 정적 메서드 호출
print(Example.is_positive(10)) # 출력: True
print(Example.is_positive(-5)) # 출력: False
이 예제에서는 get_count 클래스 메서드가 클래스 변수 count를 반환하고, is_positive 정적 메서드가 주어진 숫자가 양수인지 확인한다. 두 메서드 모두 클래스 이름을 통해 직접 호출된다.
포맷된 출력
Python에서 포맷된 출력을 통해 문자열을 보다 읽기 쉽고 일관되게 만들 수 있다. 포맷된 출력을 수행하는 여러 가지 방법이 있으며, 각 방법은 사용 용도에 따라 적합하다. 아래에 대표적인 방법인 % 연산자, str.format() 메서드, 그리고 f-strings를 설명하겠다.
1. % 연산자
% 연산자는 C 스타일의 문자열 포맷팅을 제공한다. 문자열 내에서 % 문자를 사용하여 값을 삽입할 위치를 지정하고, 문자열 뒤에 %를 붙여 삽입할 값을 제공한다.
name = "Alice"
age = 30
print("Name: %s, Age: %d" % (name, age))
- %s: 문자열
- %d: 정수
- %f: 부동 소수점 수
이 방식은 오래된 스타일이지만 여전히 일부 코드에서 볼 수 있다.
2. str.format() 메서드
str.format() 메서드는 Python 3에서 도입된 보다 유연한 문자열 포맷팅 방법이다. 중괄호 {}를 사용하여 삽입할 값을 지정한다.
name = "Alice"
age = 30
print("Name: {}, Age: {}".format(name, age))
인덱스를 사용하여 중괄호 내에 위치를 지정할 수도 있다.
print("Name: {0}, Age: {1}".format(name, age))
또는 이름을 사용할 수도 있다.
print("Name: {name}, Age: {age}".format(name=name, age=age))
3. f-strings (Formatted String Literals)
f-strings는 Python 3.6에서 도입된 가장 최신의 포맷팅 방법이다. 문자열 앞에 f를 붙이고, 중괄호 {} 안에 변수를 직접 삽입할 수 있다.
name = "Alice"
age = 30
print(f"Name: {name}, Age: {age}")
f-strings는 표현식을 지원하여 더 강력한 기능을 제공한다.
width = 10
precision = 4
value = 12.34567
print(f"Value: {value:{width}.{precision}f}")
이 예제에서는 value를 width와 precision에 따라 포맷팅한다.
포맷팅의 주요 기능
1. 숫자 포맷팅
숫자를 특정 형식으로 출력할 수 있다.
value = 1234.56789
print(f"Value: {value:.2f}") # 소수점 둘째 자리까지
print(f"Value: {value:,.2f}") # 천 단위 구분 기호 추가
2. 정렬
문자열을 좌측, 우측, 가운데로 정렬할 수 있다.
name = "Alice"
print(f"Name: {name:<10}") # 좌측 정렬
print(f"Name: {name:>10}") # 우측 정렬
print(f"Name: {name:^10}") # 가운데 정렬
3. 채우기
정렬 시 공백을 다른 문자로 채울 수 있다.
name = "Alice"
print(f"Name: {name:_<10}") # 좌측 정렬, 공백을 언더바(_)로 채우기
각 포맷팅 방법은 서로 다른 상황에서 유용하게 사용할 수 있다. 최신 코드에서는 가독성과 편리성을 위해 f-strings를 사용하는 것이 권장되지만, 이전 스타일의 코드를 유지보수하거나 특정한 이유로 다른 포맷팅 방법을 사용할 수도 있다. Python의 다양한 포맷팅 방법을 이해하고 적절히 사용하는 것은 코드의 가독성과 유지보수성을 높이는 데 매우 중요하다.
format() 메소드 구현
Python에서는 __format__ 메서드를 통해 사용자 정의 객체에 대한 포맷팅 규칙을 정의할 수 있다. __format__ 메서드는 내장 함수 format() 및 f-string에 의해 호출된다. 이 메서드를 구현함으로써 사용자 정의 객체를 특정 형식으로 출력할 수 있다.
__format__ 메서드 정의
__format__ 메서드는 두 개의 인자를 받는다:
- self: 현재 인스턴스
- format_spec: 포맷 명세 문자열
format_spec은 포맷팅 규칙을 정의하는 문자열로, 이를 해석하여 객체를 특정 형식으로 변환해야 한다.
__format__ 메서드 구현
아래는 사용자 정의 객체에 대해 __format__ 메서드를 구현하는 예제이다. 이 예제에서는 Point 클래스를 정의하고, 이 클래스의 인스턴스를 포맷팅할 수 있도록 한다.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __format__(self, format_spec):
if format_spec == "":
format_spec = "({x}, {y})" # 기본 포맷
if format_spec == "repr":
return f"Point(x={self.x}, y={self.y})"
elif format_spec == "str":
return f"({self.x}, {self.y})"
else:
return format_spec.format(x=self.x, y=self.y)
# 예제 사용
p = Point(1, 2)
print(format(p)) # 기본 포맷: (1, 2)
print(format(p, "repr")) # repr 포맷: Point(x=1, y=2)
print(format(p, "str")) # str 포맷: (1, 2)
print(format(p, "{x} - {y}")) # 사용자 정의 포맷: 1 - 2
# f-string을 사용한 예제
print(f"{p}") # 기본 포맷: (1, 2)
print(f"{p:repr}") # repr 포맷: Point(x=1, y=2)
print(f"{p:str}") # str 포맷: (1, 2)
print(f"{p:{x}/{y}}") # 사용자 정의 포맷: 1/2
- 기본 포맷: format_spec이 빈 문자열인 경우, 기본 포맷으로 ({x}, {y})를 사용한다.
- 특정 포맷 처리:
- "repr" 포맷: Point(x={self.x}, y={self.y}) 형식으로 반환한다.
- "str" 포맷: 기본 형식인 ({self.x}, {self.y})로 반환한다.
- 사용자 정의 포맷 처리: 사용자가 지정한 포맷 명세 문자열에 따라 값을 삽입하여 반환한다.
이 예제에서는 Point 객체를 다양한 형식으로 출력할 수 있게 합니다. 기본 포맷, repr 포맷, str 포맷, 그리고 사용자 정의 포맷을 지원한다.
import math
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __format__(self, format_spec):
if format_spec.endswith("p"):
# 극좌표 포맷
r = math.sqrt(self.x ** 2 + self.y ** 2)
theta = math.degrees(math.atan2(self.y, self.x))
format_spec = format_spec[:-1] # 'p' 제거
return f"({r:{format_spec}}, {theta:{format_spec}}°)"
elif format_spec == "":
# 기본 포맷
format_spec = "({x}, {y})"
# 기본 및 사용자 정의 포맷
return format_spec.format(x=self.x, y=self.y)
# 예제 사용
p = Point(3, 4)
print(format(p)) # 기본 포맷: (3, 4)
print(format(p, ".2f")) # 기본 포맷, 2소수점 자리: (3.00, 4.00)
print(format(p, ".2fp")) # 극좌표 포맷, 2소수점 자리: (5.00, 53.13°)
# f-string을 사용한 예제
print(f"{p}") # 기본 포맷: (3, 4)
print(f"{p:.2f}") # 기본 포맷, 2소수점 자리: (3.00, 4.00)
print(f"{p:.2fp}") # 극좌표 포맷, 2소수점 자리: (5.00, 53.13°)
이 예제에서는 포맷 명세 문자열이 p로 끝나면, 좌표를 극좌표로 변환하여 출력한다. 다른 경우에는 기본 또는 사용자 정의 포맷으로 출력한다. 이 방식은 객체의 출력 형식을 유연하게 제어할 수 있도록 한다.
hash() 메소드
Python에서 객체를 해시 가능하게 만들기 위해서는 __hash__() 메서드를 구현해야 한다. 해시 가능 객체는 해시 테이블 (예: 딕셔너리의 키나 집합의 요소)에서 사용할 수 있다. 기본적으로, 사용자 정의 객체는 __eq__와 __hash__ 메서드를 구현해야만 해시 가능하게 된다. 여기서는 해시 가능 객체를 만드는 과정과 __hash__() 메서드에 대해 설명하겠다.
해시 가능 객체의 조건
- 불변성: 해시 가능 객체는 그 상태가 변경되지 않아야 한다. 그렇지 않으면 객체의 해시 값이 변경되어 해시 테이블의 무결성이 깨질 수 있다.
- __hash__() 메서드 구현: 객체의 해시 값을 반환하는 __hash__() 메서드를 구현해야 한다.
- __eq__() 메서드 구현: 두 객체가 같은지 비교하는 __eq__() 메서드를 구현해야 한다. __hash__() 메서드와 함께 사용되며, 동일한 해시 값을 가지는 두 객체는 반드시 __eq__()를 통해 동일한 것으로 판단되어야 한다.
__hash__() 메서드
__hash__() 메서드는 객체의 해시 값을 반환한다. 이 값은 정수이어야 하며, 객체의 불변 상태를 기반으로 계산되어야 한다.
해시 가능 객체 만들기
다음은 Point 클래스를 해시 가능하게 만드는 예제이다. 이 클래스는 x와 y 좌표를 가지며, 두 좌표를 기반으로 해시 값을 계산한다.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
if isinstance(other, Point):
return self.x == other.x and self.y == other.y
return False
def __hash__(self):
return hash((self.x, self.y))
# 예제 사용
p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(2, 3)
# 집합에 추가
points = {p1, p2, p3}
print(points) # 출력: {Point 객체의 집합}
# 딕셔너리 키로 사용
point_dict = {p1: "A", p3: "B"}
print(point_dict[p2]) # 출력: A (p1과 p2는 동일한 좌표를 가지므로 동일 키로 인식)
- __init__ 메서드: Point 클래스는 x와 y 좌표를 초기화한다.
- __eq__ 메서드: 두 Point 객체가 동일한지 비교한다. x와 y 값이 같으면 동일한 객체로 간주한다.
- __hash__ 메서드: 두 좌표 (self.x, self.y)의 튜플을 해시하여 객체의 해시 값을 계산한다. 튜플은 기본적으로 해시 가능하므로 hash() 함수를 사용할 수 있다.
왜 __eq__와 __hash__를 함께 구현해야 하는가
__hash__와 __eq__는 함께 사용되어야 한다. 해시 테이블에서 두 객체가 동일한 해시 값을 가지는 경우, 해시 충돌이 발생할 수 있다. 이때, __eq__ 메서드는 두 객체가 실제로 동일한지를 비교한다. 다음과 같은 규칙을 따라야 한다:
- 두 객체가 같다면 (a == b), hash(a) == hash(b)여야 한다.
- 두 객체가 다르더라도, 해시 값은 같을 수 있다. 하지만 해시 값이 같다고 해서 객체가 같다는 것을 의미하지는 않는다.
해시 불가능 객체를 해시 가능 객체로 만들기
일부 객체는 기본적으로 해시 불가능하다. 예를 들어, 리스트는 가변 객체이므로 기본적으로 해시 불가능하다. 이를 해시 가능하게 만들려면, 불변 객체로 변환해야 한다. 다음은 리스트를 해시 가능하게 만드는 예제이다.
class ImmutableList:
def __init__(self, items):
self._items = tuple(items) # 불변 튜플로 변환
def __eq__(self, other):
if isinstance(other, ImmutableList):
return self._items == other._items
return False
def __hash__(self):
return hash(self._items)
# 예제 사용
list1 = ImmutableList([1, 2, 3])
list2 = ImmutableList([1, 2, 3])
list3 = ImmutableList([4, 5, 6])
# 집합에 추가
lists = {list1, list2, list3}
print(lists) # 출력: {ImmutableList 객체의 집합}
# 딕셔너리 키로 사용
list_dict = {list1: "A", list3: "B"}
print(list_dict[list2]) # 출력: A (list1과 list2는 동일한 요소를 가지므로 동일 키로 인식)
해시 가능 객체를 만들기 위해서는 __hash__()와 __eq__() 메서드를 구현해야 한다. 불변 객체를 기반으로 해시 값을 계산해야 하며, 동일한 해시 값을 가지는 객체는 __eq__()를 통해 동일한 객체로 간주되어야 한다. 이를 통해 사용자 정의 객체를 해시 테이블에서 키로 사용할 수 있게 된다.
int()와 float() 구현
Python에서 사용자 정의 객체를 특정 자료형으로 변환할 수 있도록 __int__()와 __float__() 메서드를 구현할 수 있다. 이 메서드들은 각각 객체를 정수와 부동 소수점 숫자로 변환하는 역할을 한다. 이를 통해 사용자 정의 객체를 내장 함수 int()와 float()를 사용하여 변환할 수 있다.
__int__() 메서드 구현
__int__() 메서드는 객체를 정수로 변환할 때 호출된다. 이 메서드는 객체의 상태를 정수로 표현할 수 있도록 구현해야 한다.
__float__() 메서드 구현
__float__() 메서드는 객체를 부동 소수점 숫자로 변환할 때 호출된다. 이 메서드는 객체의 상태를 부동 소수점 숫자로 표현할 수 있도록 구현해야 한다.
__int__()와 __float__() 메서드 구현
다음은 사용자 정의 클래스 MyNumber에 대해 __int__()와 __float__() 메서드를 구현하는 예제이다.
class MyNumber:
def __init__(self, value):
self.value = value
def __int__(self):
return int(self.value)
def __float__(self):
return float(self.value)
# 예제 사용
num = MyNumber(42.7)
# 정수로 변환
print(int(num)) # 출력: 42
# 부동 소수점 숫자로 변환
print(float(num)) # 출력: 42.7
- __init__ 메서드: MyNumber 클래스는 value 값을 초기화한다.
- __int__ 메서드: self.value를 정수로 변환하여 반환한다.
- __float__ 메서드: self.value를 부동 소수점 숫자로 변환하여 반환한다.
이 예제에서는 MyNumber 객체를 int() 함수와 float() 함수를 사용하여 각각 정수와 부동 소수점 숫자로 변환할 수 있다.
날짜 클래스를 정수와 부동 소수점으로 변환
다음은 날짜를 나타내는 사용자 정의 클래스 Date에 대해 __int__()와 __float__() 메서드를 구현하는 예제이다. 여기서 날짜는 특정 기준 날짜(예: 1970년 1월 1일)로부터의 일수로 변환된다.
from datetime import date
class Date:
def __init__(self, year, month, day):
self.date = date(year, month, day)
def __int__(self):
epoch = date(1970, 1, 1)
delta = self.date - epoch
return delta.days
def __float__(self):
epoch = date(1970, 1, 1)
delta = self.date - epoch
return float(delta.days)
# 예제 사용
d = Date(2023, 5, 20)
# 정수로 변환
print(int(d)) # 출력: 특정 기준 날짜(1970-01-01)로부터의 일수
# 부동 소수점 숫자로 변환
print(float(d)) # 출력: 특정 기준 날짜(1970-01-01)로부터의 일수 (부동 소수점 형식)
- __init__ 메서드: Date 클래스는 year, month, day 값을 받아 date 객체를 초기화한다.
- __int__ 메서드: 기준 날짜(1970년 1월 1일)로부터의 일수를 계산하여 정수로 반환한다.
- __float__ 메서드: 기준 날짜(1970년 1월 1일)로부터의 일수를 부동 소수점 숫자로 변환하여 반환한다.
이 예제에서는 날짜를 기준 날짜로부터의 일수로 변환하여, Date 객체를 int()와 float() 함수를 사용해 정수와 부동 소수점 숫자로 변환할 수 있다.
__int__()와 __float__() 메서드를 구현하면 사용자 정의 객체를 int()와 float() 함수를 사용하여 각각 정수와 부동 소수점 숫자로 변환할 수 있다. 이를 통해 객체의 특정 속성이나 상태를 정수 또는 부동 소수점 숫자로 표현할 수 있게 되어 다양한 상황에서 유용하게 활용할 수 있다.
complex() 생성자
Python에서 complex() 생성자는 복소수를 생성하는 데 사용된다. 복소수는 실수부와 허수부로 구성되며, a + bj 형식으로 나타낼 수 있다. 여기서 a는 실수부, b는 허수부를 나타내며, j는 허수부를 나타내는 단위이다.
complex() 생성자
complex() 생성자는 두 가지 방식으로 호출할 수 있다:
- 두 개의 인자: 실수부와 허수부를 별도로 전달하여 복소수를 생성한다.
- 하나의 인자: 문자열 또는 다른 숫자 형식으로 복소수를 생성한다.
사용법
# 두 개의 인자로 생성
c1 = complex(2, 3)
print(c1) # 출력: (2+3j)
# 하나의 인자로 생성 (문자열)
c2 = complex("2+3j")
print(c2) # 출력: (2+3j)
# 하나의 인자로 생성 (다른 숫자 형식)
c3 = complex(2)
print(c3) # 출력: (2+0j)
- complex(real, imag): real은 실수부, imag는 허수부이다. 허수부는 생략할 수 있으며, 생략하면 기본값은 0.0이다.
- complex(string): 복소수를 나타내는 문자열을 인자로 받아서 복소수를 생성한다. 문자열 형식은 a+bj 또는 a-bj 형태여야 한다.
__complex__() 메서드
사용자 정의 클래스에서 __complex__() 메서드를 구현하면, 해당 클래스의 인스턴스를 complex() 생성자를 사용해 복소수로 변환할 수 있다. 이 메서드는 객체를 복소수로 변환하는 역할을 한다.
class MyNumber:
def __init__(self, real, imag):
self.real = real
self.imag = imag
def __complex__(self):
return complex(self.real, self.imag)
# 예제 사용
num = MyNumber(2, 3)
# complex() 함수를 사용하여 MyNumber 인스턴스를 복소수로 변환
c = complex(num)
print(c) # 출력: (2+3j)
- __init__ 메서드: MyNumber 클래스는 실수부와 허수부를 초기화한다.
- __complex__ 메서드: 객체를 복소수로 변환하는 메서드로, complex(self.real, self.imag)를 반환한다.
이 예제에서는 MyNumber 객체를 complex() 함수를 사용하여 복소수로 변환할 수 있다. __complex__() 메서드를 구현하면, complex() 함수가 호출될 때 자동으로 이 메서드가 호출되어 객체를 복소수로 변환한다.
이 기능들은 복소수를 다루는 계산이나 수학적 연산을 수행할 때 유용하게 사용할 수 있다.
비공개 속성과 보호된 속성
Python에서는 객체 지향 프로그래밍에서 속성의 접근 수준을 제어할 수 있는 몇 가지 규칙이 있다. 속성을 비공개(private)로 하거나 보호(protected)로 설정할 수 있다. 이러한 규칙은 클래스 내부에서만 속성에 접근할 수 있게 하거나, 서브클래스에서만 속성에 접근할 수 있도록 제한하는 데 사용된다.
보호된 속성 (Protected Attributes)
보호된 속성은 언더스코어 하나(_)로 시작하는 이름을 가지며, 이는 해당 속성이 클래스 내에서와 서브클래스에서 사용될 수 있음을 의미한다. 보호된 속성은 클래스 외부에서 접근하지 않는 것이 권장된다. 그러나 이는 완전한 제한을 의미하지는 않으며, 단지 접근을 피하도록 유도하는 관례이다.
class Base:
def __init__(self):
self._protected_attr = "This is a protected attribute"
class Derived(Base):
def access_protected(self):
return self._protected_attr
# 예제 사용
base = Base()
print(base._protected_attr) # 가능하지만 권장되지 않음
derived = Derived()
print(derived.access_protected()) # 서브클래스에서 접근 가능
비공개 속성 (Private Attributes)
비공개 속성은 더블 언더스코어(__)로 시작하는 이름을 가지며, 이는 해당 속성이 클래스 내부에서만 사용되어야 함을 의미한다. Python에서는 이러한 속성을 클래스 외부에서 직접 접근할 수 없도록 이름을 맹글링(name mangling)하여 속성을 보호한다. 실제로는 속성 이름을 _ClassName__attrname 형식으로 변환한다.
class MyClass:
def __init__(self):
self.__private_attr = "This is a private attribute"
def get_private_attr(self):
return self.__private_attr
# 예제 사용
obj = MyClass()
# print(obj.__private_attr) # AttributeError: 'MyClass' object has no attribute '__private_attr'
print(obj.get_private_attr()) # 공개 메서드를 통해 접근 가능
# 이름 맹글링을 사용하여 접근 (권장되지 않음)
print(obj._MyClass__private_attr) # 가능하지만 권장되지 않음
- 보호된 속성 (_attr)
- 클래스 내부와 서브클래스에서 접근 가능.
- 외부에서 접근하지 않도록 권장하는 관례.
- 완전한 접근 제한이 아니며, 단지 접근을 피하도록 유도.
- 비공개 속성 (__attr)
- 클래스 내부에서만 접근 가능.
- 이름 맹글링을 통해 외부 접근을 방지 (_ClassName__attrname 형식).
- 클래스 외부에서 접근하려면 공개 메서드를 제공하는 것이 일반적.
접근 방법
보호된 속성 접근
보호된 속성은 클래스 외부에서도 접근할 수 있지만, 이는 권장되지 않는다. 대신 클래스 내부나 서브클래스에서만 접근하도록 하는 것이 좋다.
class MyClass:
def __init__(self):
self._protected_attr = "This is a protected attribute"
def _protected_method(self):
return "This is a protected method"
# 외부 접근 (권장되지 않음)
obj = MyClass()
print(obj._protected_attr)
print(obj._protected_method())
비공개 속성 접근
비공개 속성은 직접 접근할 수 없으며, 이를 위해서는 공개 메서드를 제공하는 것이 좋다. 이름 맹글링을 통해 우회 접근할 수 있지만, 이는 일반적으로 권장되지 않는다.
class MyClass:
def __init__(self):
self.__private_attr = "This is a private attribute"
def get_private_attr(self):
return self.__private_attr
# 공개 메서드를 통해 접근
obj = MyClass()
print(obj.get_private_attr())
# 이름 맹글링을 통해 접근 (권장되지 않음)
print(obj._MyClass__private_attr)
이러한 관례를 잘 활용하면, 클래스 내부 구현을 숨기고 인터페이스를 통해서만 속성에 접근하도록 유도할 수 있다. 이는 코드의 캡슐화와 유지보수성을 높이는 데 도움이 된다.
이름 장식(name mangling)
이름 장식(name mangling)은 Python에서 클래스 속성의 이름을 변경하는 방식으로, 주로 비공개(private) 속성을 보호하기 위해 사용된다. 이름 장식은 속성 이름 앞에 더블 언더스코어(__)를 붙여서 구현된다. 이때, Python은 이러한 속성의 이름을 자동으로 변경하여 외부에서의 접근을 어렵게 만든다.
이름 장식의 작동 방식
이름 장식은 비공개 속성을 클래스 외부에서 접근할 수 없도록 하기 위해, 속성 이름을 _ClassName__attrname 형태로 변경한다. 이렇게 하면 의도하지 않은 접근을 방지할 수 있다.
class MyClass:
def __init__(self):
self.__private_attr = "This is a private attribute"
def get_private_attr(self):
return self.__private_attr
def __private_method(self):
return "This is a private method"
def call_private_method(self):
return self.__private_method()
# 예제 사용
obj = MyClass()
# 직접 접근 시도
# print(obj.__private_attr) # AttributeError: 'MyClass' object has no attribute '__private_attr'
# print(obj.__private_method()) # AttributeError: 'MyClass' object has no attribute '__private_method'
# 공개 메서드를 통해 접근
print(obj.get_private_attr()) # 출력: This is a private attribute
print(obj.call_private_method()) # 출력: This is a private method
# 이름 장식을 통해 접근 (권장되지 않음)
print(obj._MyClass__private_attr) # 출력: This is a private attribute
print(obj._MyClass__private_method()) # 출력: This is a private method
이름 장식의 필요성
이름 장식은 클래스의 내부 구현을 보호하고, 클래스 외부에서 무분별하게 속성에 접근하는 것을 방지하기 위해 사용된다. 이는 특히 대형 프로젝트에서 중요한 역할을 한다. 클래스 설계자는 외부에 공개하고 싶은 속성만을 선택적으로 공개할 수 있다.
주의사항
- 이름 장식은 보안 기능이 아니다: 이름 장식은 단지 속성의 이름을 변경하여 접근을 어렵게 만들 뿐이지, 완전한 접근 차단을 보장하지 않는다. 따라서 보안 목적으로 사용되기보다는, 코드의 안정성과 유지보수성을 높이기 위한 관례로 사용된다.
- 사용자 정의 클래스에서도 이름 장식 적용: 이름 장식은 모든 사용자 정의 클래스에 동일하게 적용된다. 이는 다른 클래스와 상속 관계에서도 동일하게 작동한다.
- 이름 장식의 한계: 이름 장식은 기본적으로 클래스를 정의할 때만 적용된다. 따라서 클래스 내부에서 동적으로 속성을 추가하거나 변경할 경우, 이름 장식이 적용되지 않는다.
class Example:
def __init__(self):
self.__private_var = "I am private"
self._protected_var = "I am protected"
self.public_var = "I am public"
def __private_method(self):
return "This is a private method"
def _protected_method(self):
return "This is a protected method"
def public_method(self):
return "This is a public method"
def access_methods(self):
return self.__private_method(), self._protected_method(), self.public_method()
# 예제 사용
e = Example()
# 직접 접근
# print(e.__private_var) # AttributeError: 'Example' object has no attribute '__private_var'
print(e._protected_var) # 출력: I am protected
print(e.public_var) # 출력: I am public
# 이름 장식을 통해 접근 (권장되지 않음)
print(e._Example__private_var) # 출력: I am private
# 메서드 접근
# print(e.__private_method()) # AttributeError: 'Example' object has no attribute '__private_method'
print(e._protected_method()) # 출력: This is a protected method
print(e.public_method()) # 출력: This is a public method
# 이름 장식을 통해 접근 (권장되지 않음)
print(e._Example__private_method()) # 출력: This is a private method
# 내부 메서드 접근
print(e.access_methods()) # 출력: ('This is a private method', 'This is a protected method', 'This is a public method')
- __private_var와 __private_method는 이름 장식이 적용되어 클래스 외부에서 직접 접근할 수 없다.
- _protected_var와 _protected_method는 관례적으로 보호된 속성이며, 클래스 외부에서 접근할 수 있지만 권장되지 않는다.
- public_var와 public_method는 공개 속성이며, 클래스 외부에서 자유롭게 접근할 수 있다.
이름 장식을 통해 클래스의 내부 구현을 보다 잘 보호하고, 외부와의 명확한 인터페이스를 정의할 수 있다.
slots 클래스
Python에서 __slots__ 클래스 속성은 객체가 가지는 속성의 이름을 고정하여 메모리 사용을 최적화하는 데 사용된다. 기본적으로 Python 객체는 동적으로 속성을 추가할 수 있으며, 이를 위해 내부적으로 __dict__라는 딕셔너리를 사용한다. 하지만 __slots__를 사용하면 이 딕셔너리가 생략되어 메모리 사용을 줄일 수 있다.
__slots__의 역할
- 메모리 절약: __slots__를 사용하면 객체의 속성을 고정된 크기의 배열로 저장하게 되어, 동적 속성 추가를 위한 메모리 할당이 필요 없게 된다.
- 속도 향상: 메모리 할당과 접근이 더 빠르게 이루어질 수 있다.
__slots__ 사용 방법
__slots__는 클래스 속성으로 정의되며, 클래스가 가질 수 있는 속성 이름들을 문자열의 튜플이나 리스트로 지정한다.
class MyClass:
__slots__ = ['x', 'y']
def __init__(self, x, y):
self.x = x
self.y = y
# 예제 사용
obj = MyClass(1, 2)
print(obj.x) # 출력: 1
print(obj.y) # 출력: 2
# 새로운 속성 추가 시도
# obj.z = 3 # AttributeError: 'MyClass' object has no attribute 'z'
이 예제에서 MyClass는 __slots__를 사용하여 x와 y 속성만을 가질 수 있게 된다. z와 같은 새로운 속성을 추가하려고 하면 AttributeError가 발생한다.
__slots__의 장단점
장점
- 메모리 사용 감소: __dict__가 없기 때문에 메모리 사용이 감소한다.
- 빠른 속성 접근: 고정 배열을 사용하므로 속성 접근이 더 빠르다.
- 버그 방지: 잘못된 속성 이름을 추가하는 것을 방지할 수 있다.
단점
- 유연성 감소: 동적으로 새로운 속성을 추가할 수 없다.
- 상속 제한: 자식 클래스에서 __slots__를 다시 정의하지 않으면 부모 클래스의 __slots__가 상속되지 않는다.
- 복잡성 증가: 코드를 이해하고 유지보수하는 데 더 많은 주의가 필요하다.
__slots__와 상속
상속 관계에서 __slots__를 사용하는 방법은 조금 복잡할 수 있다. 자식 클래스에서 부모 클래스의 __slots__을 포함하려면, 자식 클래스의 __slots__에 부모 클래스의 __slots__ 항목을 포함시켜야 한다.
class Parent:
__slots__ = ['a']
def __init__(self, a):
self.a = a
class Child(Parent):
__slots__ = ['b']
def __init__(self, a, b):
super().__init__(a)
self.b = b
# 예제 사용
child = Child(1, 2)
print(child.a) # 출력: 1
print(child.b) # 출력: 2
# 새로운 속성 추가 시도
# child.c = 3 # AttributeError: 'Child' object has no attribute 'c'
이 예제에서 Parent 클래스와 Child 클래스는 각각 __slots__을 정의하고 있으며, 자식 클래스는 부모 클래스의 속성 외에 추가적인 속성을 가질 수 있다. 단, 자식 클래스의 __slots__에는 부모 클래스의 __slots__ 항목을 포함하지 않기 때문에 a와 b 속성만 가질 수 있다.
__slots__를 사용하면 메모리 절약과 성능 향상을 기대할 수 있지만, 코드의 유연성이 줄어들고 복잡성이 증가할 수 있다. 따라서, 객체의 속성 수가 매우 많고, 많은 인스턴스가 생성되는 경우에 사용을 고려하는 것이 좋다. 일반적인 경우에는 유연성과 유지보수성을 고려하여 __dict__를 사용하는 것이 더 나을 수 있다.
__slots__ 추가 예제
Python에서 __slots__와 __dict__ 또는 __weakref__를 함께 사용하려면, 특별한 주의가 필요하다. __slots__는 객체의 메모리 사용을 최적화하기 위해 속성을 고정된 배열로 저장하도록 한다. 그러나 경우에 따라 __dict__를 포함하여 동적 속성 추가를 허용하거나, __weakref__를 포함하여 약한 참조(weak references)를 사용할 수 있도록 할 수 있다.
__slots__와 __dict__ 함께 사용하기
__slots__를 정의하면서도 동적 속성 추가를 허용하고 싶다면, __slots__에 __dict__를 추가할 수 있다. 이렇게 하면 기본적인 속성은 고정된 배열로 저장되지만, 추가적인 속성은 __dict__에 저장된다.
class MyClass:
__slots__ = ['x', 'y', '__dict__']
def __init__(self, x, y):
self.x = x
self.y = y
# 예제 사용
obj = MyClass(1, 2)
print(obj.x) # 출력: 1
print(obj.y) # 출력: 2
# 동적 속성 추가
obj.z = 3
print(obj.z) # 출력: 3
print(obj.__dict__) # 출력: {'z': 3}
__slots__와 __weakref__ 함께 사용하기
약한 참조는 객체의 참조 카운트를 증가시키지 않는 참조이다. 이를 통해 객체가 가비지 컬렉션(GC)될 수 있다. 약한 참조를 사용하려면 __slots__에 __weakref__를 포함시켜야 한다.
import weakref
class MyClass:
__slots__ = ['x', 'y', '__weakref__']
def __init__(self, x, y):
self.x = x
self.y = y
# 예제 사용
obj = MyClass(1, 2)
weak_ref = weakref.ref(obj)
print(weak_ref) # 출력: <weakref at ...; to 'MyClass' at ...>
print(weak_ref()) # 출력: <__main__.MyClass object at ...>
del obj # 원본 객체 삭제
print(weak_ref()) # 출력: None (원본 객체가 삭제되었기 때문에 약한 참조도 사라짐)
__slots__, __dict__, 그리고 __weakref__를 함께 사용하기
이 세 가지를 모두 사용하면, 기본적인 메모리 절약을 하면서도 동적 속성 추가와 약한 참조를 사용할 수 있다.
import weakref
class MyClass:
__slots__ = ['x', 'y', '__dict__', '__weakref__']
def __init__(self, x, y):
self.x = x
self.y = y
# 예제 사용
obj = MyClass(1, 2)
# 동적 속성 추가
obj.z = 3
print(obj.z) # 출력: 3
print(obj.__dict__) # 출력: {'z': 3}
# 약한 참조
weak_ref = weakref.ref(obj)
print(weak_ref) # 출력: <weakref at ...; to 'MyClass' at ...>
print(weak_ref()) # 출력: <__main__.MyClass object at ...>
del obj # 원본 객체 삭제
print(weak_ref()) # 출력: None (원본 객체가 삭제되었기 때문에 약한 참조도 사라짐)
__slots__의 특징, 장단점
특징
- 객체 속성의 고정 배열을 사용하여 메모리 사용을 최적화한다.
- 속성의 이름을 미리 정의하여 속성 추가/삭제를 제한한다.
장점
- 메모리 절약: __dict__를 사용하지 않으므로 메모리 사용량이 줄어든다.
- 속도 향상: 고정된 배열을 사용하여 속성 접근 속도가 빨라진다.
단점
- 유연성 감소: 동적 속성 추가가 불가능하다.
- 상속 시 복잡성 증가: 자식 클래스에서 __slots__를 다시 정의해야 한다.
__dict__와 __slots__ 함께 사용
특징
- 기본적인 속성은 고정 배열로 저장되며, 추가적인 속성은 __dict__에 저장된다.
장점
- 동적 속성 추가가 가능해진다.
- 메모리 절약과 유연성 사이의 균형을 제공한다.
단점
- 메모리 절약 효과가 __slots__만 사용할 때보다는 덜할 수 있다.
- 관리 복잡성이 증가한다.
__weakref__와 __slots__ 함께 사용
특징
- 약한 참조를 허용하여 객체가 가비지 컬렉션될 수 있게 한다.
장점
- 객체의 생명 주기를 보다 유연하게 관리할 수 있다.
- 메모리 누수를 방지할 수 있다.
단점
- 약한 참조의 관리가 필요하다.
- 코드 복잡성이 증가한다.
__slots__는 메모리 절약과 성능 최적화에 유용하지만, 유연성이 감소하고 코드 복잡성이 증가할 수 있다. __dict__와 __weakref__를 함께 사용하여 동적 속성 추가와 약한 참조를 허용할 수 있다. 이러한 기능들을 적절히 사용하면 메모리 관리와 성능 최적화에 큰 도움이 될 수 있다.
__slots__를 사용할 때 주의할 점
__slots__를 사용할 때 주의해야 할 점들은 다음과 같다. 이러한 점들을 유의하면 __slots__의 장점을 잘 활용할 수 있으며, 발생할 수 있는 문제를 피할 수 있다.
1. 상속 문제
__slots__를 사용한 클래스는 상속에 주의해야 한다. 부모 클래스와 자식 클래스 모두에서 __slots__를 사용할 경우, 각 클래스의 __slots__를 올바르게 정의해야 한다.
class Parent:
__slots__ = ['x']
def __init__(self, x):
self.x = x
class Child(Parent):
__slots__ = ['y']
def __init__(self, x, y):
super().__init__(x)
self.y = y
# 예제 사용
c = Child(1, 2)
print(c.x) # 출력: 1
print(c.y) # 출력: 2
이 예제에서는 Parent 클래스와 Child 클래스 모두 __slots__를 정의했으며, 자식 클래스에서 부모 클래스의 __slots__를 올바르게 상속받고 있다.
2. 동적 속성 추가 불가
__slots__를 정의하면 클래스에 미리 정의된 속성 외에 동적 속성을 추가할 수 없다. 이를 피하기 위해 __dict__를 __slots__에 추가할 수 있지만, 그러면 메모리 최적화 효과가 감소한다.
class MyClass:
__slots__ = ['x']
def __init__(self, x):
self.x = x
# 예제 사용
obj = MyClass(1)
obj.x = 10 # 허용됨
# obj.y = 20 # AttributeError: 'MyClass' object has no attribute 'y'
동적 속성 추가를 허용하려면 __dict__를 __slots__에 추가해야 한다.
class MyClass:
__slots__ = ['x', '__dict__']
def __init__(self, x):
self.x = x
# 예제 사용
obj = MyClass(1)
obj.y = 20 # 허용됨
print(obj.y) # 출력: 20
3. 메모리 절약의 한계
__slots__는 주로 메모리 절약을 위해 사용되지만, 모든 경우에 최적의 선택은 아니다. 예를 들어, 속성 수가 매우 적고 인스턴스 수가 많지 않다면, __slots__를 사용하지 않아도 메모리 절약 효과가 미미할 수 있다.
4. 서브클래스에서의 사용
__slots__를 사용한 클래스는 서브클래스에서 __slots__를 재정의하지 않으면 서브클래스가 부모 클래스의 속성을 가지지 못한다. 서브클래스에서도 __slots__를 사용하려면, 부모 클래스의 __slots__를 포함해야 한다.
class Parent:
__slots__ = ['x']
class Child(Parent):
__slots__ = ['y']
# 예제 사용
obj = Child()
obj.x = 1 # 허용됨
obj.y = 2 # 허용됨
5. __weakref__와 함께 사용
약한 참조를 사용하려면 __weakref__를 __slots__에 포함해야 한다. 그렇지 않으면 객체에 약한 참조를 사용할 수 없다.
import weakref
class MyClass:
__slots__ = ['x', '__weakref__']
def __init__(self, x):
self.x = x
# 예제 사용
obj = MyClass(1)
weak_ref = weakref.ref(obj)
print(weak_ref()) # 출력: <__main__.MyClass object at ...>
6. Pickle 지원 문제
__slots__를 사용하면 기본적으로 객체를 직렬화(pickling)할 수 없다. 이를 해결하려면 __getstate__와 __setstate__ 메서드를 정의해야 한다.
import pickle
class MyClass:
__slots__ = ['x']
def __init__(self, x):
self.x = x
def __getstate__(self):
return self.x
def __setstate__(self, state):
self.x = state
# 예제 사용
obj = MyClass(1)
data = pickle.dumps(obj) # 직렬화
obj2 = pickle.loads(data) # 역직렬화
print(obj2.x) # 출력: 1
__slots__를 사용하면 메모리 사용을 최적화하고 속성 접근 속도를 높일 수 있지만, 동적 속성 추가 불가, 상속 문제, 약한 참조 및 Pickle 지원 등의 여러 제한 사항을 고려해야 한다. 이러한 제한 사항을 이해하고 적절히 처리하면, __slots__를 효과적으로 활용할 수 있다.
클래스 속성 오버라이드
클래스 속성 오버라이딩은 파이썬에서 클래스 상속과 관련된 중요한 개념이다. 상속된 클래스에서 부모 클래스의 속성이나 메서드를 재정의하여 새롭게 구현할 수 있다. 이를 통해 클래스의 동작을 변경하거나 확장할 수 있다.
클래스 속성 오버라이딩의 기본 개념
- 클래스 속성: 클래스 자체에 정의된 속성으로, 클래스의 모든 인스턴스가 공유한다.
- 인스턴스 속성: 클래스의 특정 인스턴스에 속하는 속성으로, 각 인스턴스마다 별도로 존재한다.
클래스 속성을 오버라이드(재정의)하면, 자식 클래스에서 부모 클래스의 속성을 동일한 이름으로 새롭게 정의하여 그 속성의 값을 변경하거나 다른 동작을 수행하도록 할 수 있다.
부모 클래스 정의
class Parent:
class_attr = "I am a class attribute in Parent"
def __init__(self):
self.instance_attr = "I am an instance attribute in Parent"
자식 클래스 정의 및 클래스 속성 오버라이딩
class Child(Parent):
class_attr = "I am a class attribute in Child"
def __init__(self):
super().__init__()
self.instance_attr = "I am an instance attribute in Child"
parent = Parent()
child = Child()
# 클래스 속성 접근
print(Parent.class_attr) # 출력: I am a class attribute in Parent
print(Child.class_attr) # 출력: I am a class attribute in Child
# 인스턴스 속성 접근
print(parent.instance_attr) # 출력: I am an instance attribute in Parent
print(child.instance_attr) # 출력: I am an instance attribute in Child
주요 사항
- 클래스 속성 공유:
- 부모 클래스와 자식 클래스가 같은 이름의 클래스 속성을 가지면, 자식 클래스에서 해당 속성을 오버라이드할 수 있다. 이는 자식 클래스와 부모 클래스의 속성이 독립적으로 존재함을 의미한다.
- 인스턴스 속성:
- 인스턴스 속성은 각 인스턴스마다 별도로 존재하며, 클래스 속성과는 다르게 오버라이드가 아니라 단순히 다른 값을 가질 수 있다.
- super() 함수:
- super() 함수를 사용하여 부모 클래스의 초기화 메서드(__init__)를 호출할 수 있다. 이를 통해 부모 클래스의 초기화를 수행한 후에 자식 클래스의 초기화를 추가로 수행할 수 있다.
클래스 메서드 오버라이딩
클래스 속성뿐만 아니라 클래스 메서드도 오버라이딩할 수 있다. 부모 클래스의 메서드를 자식 클래스에서 재정의하여 다른 동작을 수행하도록 할 수 있다.
class Parent:
def greet(self):
print("Hello from Parent")
class Child(Parent):
def greet(self):
print("Hello from Child")
# 예제 사용
parent = Parent()
child = Child()
parent.greet() # 출력: Hello from Parent
child.greet() # 출력: Hello from Child
- 클래스 속성 오버라이딩: 부모 클래스의 클래스 속성을 자식 클래스에서 동일한 이름으로 재정의하여 다른 값을 가질 수 있다.
- 인스턴스 속성: 인스턴스 속성은 각 인스턴스마다 별도로 존재하며, 오버라이딩이 아니라 단순히 다른 값을 가질 수 있다.
- 클래스 메서드 오버라이딩: 부모 클래스의 메서드를 자식 클래스에서 재정의하여 다른 동작을 수행하도록 할 수 있다.
클래스 속성 오버라이딩은 객체 지향 프로그래밍의 중요한 개념 중 하나로, 클래스 계층 구조에서 동작을 유연하게 정의하고 재사용성을 높이는 데 도움이 된다.
모든 코드는 github에 저장되어 있습니다.
'Python study' 카테고리의 다른 글
인터페이스 (0) | 2024.05.26 |
---|---|
시퀀스 해킹, 해시, 슬라이스 (0) | 2024.05.22 |
객체 참조, 가변성, 재활용 (0) | 2024.05.13 |
함수 데커레이터(Decorator) & 클로저(Closure) (0) | 2024.05.10 |
일급 함수 디자인 패턴 (0) | 2024.05.08 |