클래스 속성 타입 힌트
from typing import List, Dict, Optional
class Sample:
def __init__(self, value_a: int, value_b: str, items: List[int], mapping: Dict[str, str], opt_value: Optional[float] = None):
self.val_a = value_a
self.val_b = value_b
self.items = items
self.mapping = mapping
self.opt_val = opt_value
def display(self) -> None:
print(self.mapping)
sample = Sample(10, "hello", [1, 2], {'name': 'test'})
sample.display()
메서드 유형
파이썬에서 메서드는 데코레이터에 따라 호출 방식과 첫 번째 매개변수의 의미가 달라집니다.
| 특성 | 인스턴스 메서드 | 클래스 메서드 | 정적 메서드 |
|---|---|---|---|
| 기본 매개변수 | self (인스턴스) |
cls (클래스) |
없음 |
| 접근 가능 | 인스턴스 및 클래스 속성 | 클래스 속성만 | 클래스/인스턴스 속성 직접 접근 불가 |
| 주 용도 | 인스턴스 데이터 처리 | 팩토리 메서드, 클래스 상태 조작 | 클래스와 관련된 독립적인 유틸리티 |
class Human:
category = "Homo sapiens"
def __init__(self, name):
self.name = name
def greet(self):
print(f"저는 {self.name}입니다.")
@classmethod
def get_category(cls):
print(f"종은 {cls.category}입니다.")
@staticmethod
def is_adult(age):
return age >= 18
p = Human("Alice")
p.greet()
Human.get_category()
print(Human.is_adult(20))
프로퍼티와 데이터 검증
@property 데코레이터를 사용하면 메서드를 속성처럼 호출할 수 있어 데이터 검증이나 계산된 필드에 유용합니다.
class PageController:
def __init__(self, current_page: str, total_items: int, per_page=10):
self.per_page = per_page
self.total = total_items
if not current_page.isdecimal():
self.current = 1
else:
page_num = int(current_page)
self.current = max(1, page_num)
@property
def start_index(self):
return (self.current - 1) * self.per_page
@property
def end_index(self):
return self.current * self.per_page
all_users = [f"User-{i}" for i in range(1, 3000)]
ctrl = PageController("2", len(all_users), 20)
page_data = all_users[ctrl.start_index:ctrl.end_index]
print(page_data[:3])
접근 제어 (Public & Private)
파이썬에서는 이름 앞에 __ (더블 언더스코어)를 붙여서 비공개(private) 멤버를 만듭니다. 이는 완전한 차단이 아닌 이름 변환(name mangling)을 통한 우회적 접근 제한입니다.
특징 1: 직접 접근 불가
class SampleClass:
@property
def __hidden(self):
print("비공개 getter")
@property
def proxy_prop(self):
print("공개 프록시")
self.__hidden
return 1
obj = SampleClass()
v = obj.proxy_prop
print(v)
# obj.__hidden() # 오류 발생
특징 2: 이름 변환을 통한 접근
class Parent:
def __hidden(self):
print("부모의 비공개 메서드")
def public_method(self):
print("부모의 공개 메서드")
def call_hidden(self):
self.__hidden() # 내부 호출은 가능
class Child(Parent):
def test(self):
self.public_method()
# self.__hidden() # 오류: 자식에서 부모의 비공개 메서드 호출 불가
obj = Parent()
# obj.__hidden() # 오류
obj._Parent__hidden() # 가능하지만 권장되지 않음
메타클래스
클래스 생성 방식을 제어합니다. 기본적으로 type 메타클래스를 사용합니다.
기본 클래스 생성
# 방법 1: class 키워드
class MyClass:
attr1 = "value1"
def method1(self):
print("실행")
return 123
def method2(self):
return lambda self: 4444
# 방법 2: type() 직접 호출
def my_function():
print("실행")
return 123
MyClassV2 = type("MyClassV2", (object,), {"attr1": "value1", "method1": my_function, "method2": lambda self: 4444})
커스텀 메타클래스
class CustomMeta(type):
def __new__(cls, *args, **kwargs):
# args[2]에 새 속성 추가
modified_attrs = dict(args[2])
modified_attrs["_added_by_meta"] = 1
print(f"메타클래스가 클래스 생성: 이름={args[0]}, 베이스={args[1]}, 속성={args[2]}")
new_class = super().__new__(cls, args[0], args[1], modified_attrs)
return new_class
class Base(metaclass=CustomMeta):
age = 25
class Sub(Base):
pass
객체 관계와 포함
객체는 서로를 포함하거나 참조할 수 있습니다.
시나리오 1: 리스트 포함
class Member:
def __init__(self, name, years):
self.name = name
self.years = years
def info(self):
msg = "회원 이름: {}, 나이: {}".format(self.name, self.years)
print(msg)
m1 = Member("Kim", 25)
m2 = Member("Lee", 30)
m3 = Member("Park", 22)
class Group:
def __init__(self, name):
self.name = name
self.members = []
def add_member(self, member):
self.members.append(member)
def add_multiple(self, member_list):
for m in member_list:
self.add_member(m)
def show_all(self):
for m in self.members:
m.info()
g = Group("Alpha Team")
g.add_member(m1)
g.add_multiple([m2, m3])
print(g.name)
시나리오 2: 객체 직접 참조
class StudentV2:
def __init__(self, name, years, group):
self.name = name
self.years = years
self.group = group
def details(self):
msg = "{}반 학생 {}, 나이 {}".format(self.group.title, self.name, self.years)
print(msg)
class Course:
def __init__(self, title):
self.title = title
course1 = Course("Python 고급")
course2 = Course("데이터 과학")
students = [
StudentV2("Alice", 22, course1),
StudentV2("Bob", 23, course1),
StudentV2("Charlie", 21, course2)
]
for s in students:
print(s.name, s.group.title)
시나리오 3: 다중 참조
class StudentV3:
def __init__(self, name, years, group):
self.name = name
self.years = years
self.group = group
class CourseV2:
def __init__(self, title, campus):
self.title = title
self.campus = campus
class Campus:
def __init__(self, name):
self.name = name
s1 = Campus("서울")
s2 = Campus("부산")
c1 = CourseV2("풀스택", s1)
c2 = CourseV2("클라우드", s2)
members = [
StudentV3("Kim", 27, c1),
StudentV3("Park", 28, c1),
StudentV3("Choi", 26, c2)
]
for m in members:
print(m.name, m.group.title, m.group.campus.name)
특수 메서드 심층 분석
__str__ vs __repr__
__str__:print()또는str()호출 시 사용. 사용자 가독성 목적.__repr__: 대화형 인터프리터에서 객체 출력 시, 또는repr()호출 시 사용. 개발자 정보 제공 목적.
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"사용자: {self.name} ({self.age}세)"
def __repr__(self):
return f"User(name='{self.name}', age={self.age})"
u = User("Alex", 30)
print(u)
print(repr(u))
print([u])
__call__ : 객체를 함수처럼 호출
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, x, y=None):
if y is None:
return x * self.factor
return (x + y) * self.factor
m = Multiplier(10)
print(m(5))
print(m(2, 3))
print(callable(m))
__new__ 와 __init__
__new__: 인스턴스 생성 (정적 메서드, 인스턴스 반환 필수)__init__: 생성된 인스턴스 초기화
import threading
class Singleton:
_instance = None
_lock = threading.Lock()
def __init__(self, name):
self.name = name
def __new__(cls, *args, **kwargs):
if not cls._instance:
with cls._lock:
if not cls._instance:
print("새 인스턴스 생성...")
cls._instance = super().__new__(cls)
return cls._instance
s1 = Singleton("A")
s2 = Singleton("B")
print(s1 is s2)
print(s1.name)
__dict__ 와 __doc__
class Instructor:
"""강사 클래스"""
role = "강사"
def __init__(self, name):
self.name = name
t = Instructor("Kim")
print(t.__dict__)
print(Instructor.__doc__)
시퀀스/컨테이너 메서드 (__getitem__, __setitem__, __delitem__)
class DataStore:
def __init__(self):
self._data = {}
def __getitem__(self, key):
return self._data.get(key, "키 없음")
def __setitem__(self, key, value):
self._data[key] = value
def __delitem__(self, key):
if key in self._data:
del self._data[key]
ds = DataStore()
ds["name"] = "Alice"
print(ds["name"])
del ds["name"]
속성 가로채기 (__getattr__, __setattr__, __delattr__)
class UserProxy:
def __init__(self, name):
# __setattr__ 트리거 주의: object.__setattr__ 사용 권장
object.__setattr__(self, 'name', name)
def __getattr__(self, item):
return f"속성 '{item}'이 존재하지 않습니다."
def __setattr__(self, key, value):
print(f"할당 가로챔: {key} = {value}")
object.__setattr__(self, key, value)
def __delattr__(self, item):
print(f"삭제 가로챔: {item}")
object.__delattr__(self, item)
u = UserProxy("Bob")
print(u.name)
print(u.age)
u.age = 30
del u.age
연산자 오버로딩 (__add__)
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
return NotImplemented
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)
컨텍스트 매니저 (__enter__, __exit__)
class DBConnection:
def __init__(self, db_name):
self.db_name = db_name
self.conn = None
def __enter__(self):
print(f"데이터베이스 연결 중: {self.db_name}...")
self.conn = f"Conn_{self.db_name}"
return self.conn
def __exit__(self, exc_type, exc_val, exc_tb):
print("데이터베이스 연결 종료...")
self.conn = None
if exc_type:
print(f"예외 발생: {exc_val}")
return False
with DBConnection("PostgreSQL") as conn:
print(f"사용 중인 연결: {conn}")
추상 기본 클래스 (ABC)
추상 기본 클래스는 하위 클래스가 특정 메서드를 구현하도록 강제합니다. abc 모듈을 사용합니다.
import abc
class Shape(metaclass=abc.ABCMeta):
@abc.abstractmethod
def area(self):
pass
@abc.abstractmethod
def perimeter(self):
pass
def description(self):
return "도형"
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius ** 2
def perimeter(self):
return 2 * 3.14 * self.radius
c = Circle(5)
print(c.area())
속성 및 메서드 숨기기
앞서 언급한 대로 __는 이름을 _ClassName__attribute 형식으로 변환합니다.
class Person:
__type = "인간"
home = "지구"
def __init__(self, name):
self.name = name
def __hobby(self):
print(f"{self.name}은/는 자유를 좋아합니다")
def show_hobby(self):
self.__hobby()
class Korean(Person):
__type = "한국인"
def __init__(self, name, home):
super().__init__(name)
self.home = home
p = Person("Kim")
# p.__hobby() # 오류
# print(p.__type) # 오류
print(p._Person__type) # 가능
p.show_hobby()
k = Korean("Lee", "서울")
print(k._Person__type) # 부모 클래스의 __type
print(k._Korean__type) # 자식 클래스의 __type
리플렉션 (Reflection)
리플렉션을 사용하면 문자열 기반으로 객체의 속성과 메서드에 동적으로 접근할 수 있습니다.
내장 함수
getattr(obj, name)setattr(obj, name, value)hasattr(obj, name)delattr(obj, name)
class Data:
def __init__(self):
self.value = 42
self.label = "original"
d = Data()
print(getattr(d, 'value'))
setattr(d, 'label', 'modified')
print(d.label)
print(hasattr(d, 'missing'))
delattr(d, 'label')
import_module + 리플렉션
from importlib import import_module
# 모듈 동적 임포트
rand_module = import_module("random")
random_value = rand_module.randint(1, 100)
# requests.exceptions 모듈 임포트
req_exceptions = import_module("requests.exceptions")
invalid_url_class = getattr(req_exceptions, "InvalidURL")
# 패키지 내부 모듈
# mod = import_module("b.c.c")
# sub_mod = import_module(".c.c", package="b")
메서드 결정 순서 (MRO) - C3 선형화
다이아몬드 상속 문제를 해결하기 위해 C3 선형화 알고리즘을 사용합니다.
기본 예시
class D:
pass
class C(D):
pass
class B:
pass
class A(B, C):
pass
print(A.mro())
# [A, B, C, D, object]
복잡한 예시
# 가상의 복잡한 상속 구조
class H:
pass
class G(H):
pass
class F(G):
pass
class E(F):
pass
class D(E):
pass
class C(D):
pass
class B:
pass
class A(B, C):
pass
print(A.mro())
MRO 규칙: 왼쪽에서 오른쪽, 깊이 우선, 작은 다이아몬드는 위쪽 유지, 큰 다이아몬드는 위쪽 유지 (C3 알고리즘)
예외 처리
기본 구조
try:
# 실행 코드
result = 10 / 0
except ZeroDivisionError as e:
print(f"0으로 나누기 오류: {e}")
except Exception as e:
print(f"일반 오류: {e}")
finally:
print("항상 실행 (리소스 정리 등)")
커스텀 예외
class ValidationError(Exception):
def __init__(self, message, *args, **kwargs):
super().__init__(*args, **kwargs)
self.message = message
def validate_email(email):
if "@" not in email:
raise ValidationError("유효하지 않은 이메일 형식")
try:
validate_email("invalid")
except ValidationError as e:
print(f"검증 오류: {e.message}")
except Exception as e:
print(f"기타 오류: {e}")
finally 와 return
def test():
try:
print("try 블록")
return "try 반환"
except Exception:
return "except 반환"
finally:
print("finally 블록 (return 이후에도 실행됨)")
result = test()
print(f"결과: {result}")
내장 함수 보충
callable()
def func():
pass
class WithCall:
def __call__(self):
pass
class WithoutCall:
pass
print(callable(func))
print(callable(WithCall))
print(callable(WithCall()))
print(callable(WithoutCall()))
super()
class Parent:
def action(self, num):
print("Parent.action", num)
class Child(Parent):
def action(self, num):
print("Child.action", num)
super().action(num + 50)
c = Child()
c.action(10)
isinstance() 와 issubclass()
class Animal:
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
d = Dog()
print(isinstance(d, Dog))
print(isinstance(d, Animal))
print(issubclass(Dog, Animal))
print(issubclass(Cat, Animal))
실전 예제: 간단한 TCP 서버
import socket
import threading
class BaseHandler:
def __init__(self, request, client_addr, server):
self.request = request
self.client_addr = client_addr
self.server = server
self.setup()
def __call__(self):
try:
self.handle()
finally:
self.finish()
def setup(self):
pass
def handle(self):
pass
def finish(self):
pass
class EchoHandler(BaseHandler):
def handle(self):
data = self.request.recv(1024)
print(f"수신: {data}")
self.request.sendall(b"Echo: " + data)
class ThreadedTCPServer:
def __init__(self, addr, handler_class):
self.addr = addr
self.handler_class = handler_class
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind(addr)
self.sock.listen(5)
def serve_forever(self):
while True:
client, addr = self.sock.accept()
t = threading.Thread(target=self._process, args=(client, addr))
t.start()
def _process(self, request, addr):
handler = self.handler_class(request, addr, self)
handler()
if __name__ == "__main__":
server = ThreadedTCPServer(("127.0.0.1", 8888), EchoHandler)
server.serve_forever()