클래스와 객체
지금까지 다뤄온 모든 타입(문자열, 리스트, 딕셔너리)은 사실 클래스입니다. "hello".upper()를 호출하면 문자열 객체의 메서드를 호출하는 것입니다. 클래스를 사용하면 자신만의 데이터와 동작을 가진 자신만의 타입을 정의할 수 있습니다. Player 클래스는 이름, 점수, 레벨을 가지고 있고 스스로를 표시하는 방법도 알 수 있습니다.
청사진과 인스턴스
클래스는 청사진입니다. 인스턴스는 그 청사진으로부터 만들어진 구체적인 것입니다. 필요한 만큼 많은 인스턴스를 만들 수 있으며, 각각은 자신의 데이터를 가지지만 클래스에 정의된 동일한 메서드를 공유합니다.
class Dog:
def bark(self):
print("Woof!")
rex = Dog()
luna = Dog()
rex.bark() # "Woof!"
luna.bark() # "Woof!"Dog는 클래스입니다. rex와 luna는 인스턴스입니다: 두 마리의 다른 강아지이며, 각각 클래스에 정의된 동일한 동작을 공유합니다.
__init__과 self
__init__은 새로운 인스턴스를 생성할 때 Python이 자동으로 호출하는 메서드입니다. 객체의 초기 데이터를 설정하는 곳입니다. self는 메서드가 작동 중인 특정 인스턴스를 참조하는 방법이며, 항상 첫 번째 매개변수입니다.
class Player:
def __init__(self, name, score=0):
self.name = name
self.score = score
def add_points(self, points):
self.score += points
def display(self):
print(f"{self.name}: {self.score} points")
alice = Player("민준")
bob = Player("서연", score=50)
alice.add_points(30)
alice.display() # "민준: 30 points"
bob.display() # "서연: 50 points"self.name과 self.score는 인스턴스 속성입니다: 클래스 자체가 아니라 특정 객체에 속합니다. 각 Player 인스턴스는 자신만의 name과 score를 가집니다.
메서드
클래스 내부에 정의된 모든 함수는 메서드입니다. 인스턴스 메서드는 항상 self를 첫 번째 매개변수로 가지며, Python이 자동으로 전달합니다. 메서드는 self를 통해 인스턴스의 데이터를 읽고 변경할 수 있습니다.
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def scale(self, factor):
self.radius *= factor
return self # self를 반환하면 체이닝이 가능합니다: c.scale(2).scale(0.5)
c = Circle(5)
print(c.area()) # 78.53975
c.scale(2)
print(c.area()) # 314.159클래스 변수 vs 인스턴스 변수
클래스에 직접 정의된 변수(__init__ 내부가 아닌)는 클래스 변수입니다. 모든 인스턴스는 동일한 클래스 변수를 공유합니다. __init__ 내부에서 self에 설정된 변수는 인스턴스 변수이며, 각 객체에 고유합니다.
class Player:
max_lives = 3 # 클래스 변수, 모든 Player에 대해 동일
def __init__(self, name):
self.name = name # 인스턴스 변수, 각 Player에 고유
self.lives = Player.max_lives
def die(self):
self.lives -= 1
alice = Player("민준")
bob = Player("서연")
Player.max_lives = 5 # 현재 및 미래의 모든 인스턴스에 대해 변경모든 인스턴스에서 공유되는 값(상수, 카운터, 기본값)에는 클래스 변수를 사용하세요. 객체마다 다른 데이터에는 인스턴스 변수를 사용하세요.
__str__과 __repr__
__str__은 print()와 f-string이 객체에 대해 보여주는 내용을 제어합니다. __repr__은 콘솔에 표시되고 디버깅에 사용되는 개발자용 뷰를 제어합니다. 항상 __repr__을 정의하세요. 디버그 뷰와 별도로 깔끔한 사용자 대상 표시를 원할 때 __str__을 정의하세요.
class Player:
def __init__(self, name, score):
self.name = name
self.score = score
def __str__(self):
return f"{self.name} ({self.score} pts)"
def __repr__(self):
return f"Player(name={self.name!r}, score={self.score})"
alice = Player("민준", 87)
print(alice) # "민준 (87 pts)" (__str__ 사용)
repr(alice) # "Player(name='민준', score=87)" (__repr__ 사용)항상 __repr__을 정의하세요. 디버그 뷰와 별도로 깔끔한 사용자 대상 표현을 원할 때 __str__을 정의하세요. __repr__만 정의되어 있으면 Python은 둘 다에 그것을 사용합니다.
비공개 관례
Python에는 진정으로 비공개인 변수는 없지만, 이름 시작 부분의 단일 밑줄(_balance)은 "이것은 내부용이며, 클래스 외부에서 직접 사용하지 마세요"를 알리는 관례입니다. 언어에 의해 강제되지 않으며, 다른 개발자에게 보내는 의사소통입니다.
class BankAccount:
def __init__(self, balance):
self._balance = balance # _는 "건드리지 마세요"를 의미합니다
def deposit(self, amount):
if amount > 0:
self._balance += amount
def balance(self):
return self._balance이중 밑줄(__name)은 이름 맹글링을 트리거합니다; Python은 서브클래스에서의 충돌을 피하기 위해 속성을 _ClassName__name으로 이름을 변경합니다. 거의 필요하지 않습니다. 단일 밑줄이 대부분의 코드에서 사용되는 관례입니다.
상속
클래스는 다른 클래스로부터 상속받을 수 있으며, 그 클래스의 모든 속성과 메서드를 자동으로 얻습니다. 그런 다음 서브클래스에서 특정 메서드를 오버라이드하여 동작을 변경할 수 있습니다. 이를 통해 공통 기반을 재사용하고 필요한 곳에서 특수화할 수 있습니다.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "..."
class Dog(Animal):
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
pets = [Dog("Rex"), Cat("Luna"), Dog("Max")]
for pet in pets:
print(pet.speak())Dog와 Cat은 Animal로부터 __init__을 상속받으므로 자신만의 것이 필요하지 않습니다. 자신만의 특정 동작으로 speak()를 오버라이드합니다.
super()
super()는 부모 클래스의 메서드를 호출합니다. 부모의 동작을 완전히 대체하기보다는 확장하고 싶을 때 사용하세요: 부모의 설정을 실행하기 위해 부모의 __init__을 호출한 다음, 서브클래스에 필요한 것을 그 위에 추가합니다.
class Animal:
def __init__(self, name, sound):
self.name = name
self.sound = sound
class Dog(Animal):
def __init__(self, name):
super().__init__(name, "Woof") # Animal.__init__ 호출
self.tricks = [] # 추가 기능 더하기
def learn(self, trick):
self.tricks.append(trick)
rex = Dog("Rex")
rex.learn("sit")
print(rex.tricks) # ["sit"]서브클래스가 자신만의 __init__을 가지고 있고 부모도 가지고 있을 때는 항상 super().__init__()을 호출하세요.
클래스 메서드와 정적 메서드
@classmethod는 인스턴스 대신 클래스 자체를 받는 메서드를 만듭니다. 대체 생성자에 유용합니다: 문자열, 파일, 또는 다른 형식으로부터 인스턴스를 생성하는 경우입니다. @staticmethod는 조직화를 위해 클래스 안에 있는 평범한 함수입니다; 인스턴스도 클래스도 받지 않습니다.
class Player:
def __init__(self, name, score):
self.name = name
self.score = score
@classmethod
def from_string(cls, data):
name, score = data.split(",")
return cls(name, int(score))
alice = Player.from_string("민준,87")class Player:
@staticmethod
def is_valid_name(name):
return name.isalpha() and len(name) >= 2
Player.is_valid_name("민준") # True
Player.is_valid_name("A1") # False대체 생성자에는 @classmethod를 사용하세요. 클래스와 논리적으로 연관되지만 인스턴스나 클래스 데이터가 필요하지 않은 유틸리티 함수에는 @staticmethod를 사용하세요.
@property
@property를 사용하면 괄호 없이 속성처럼 메서드에 접근할 수 있습니다. 다른 속성으로부터 계산되어 단순 속성 접근으로 읽는 것이 자연스러운 값에 사용하세요.
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return 3.14159 * self.radius ** 2
@property
def diameter(self):
return self.radius * 2
c = Circle(5)
print(c.area) # 78.53975 (속성처럼 보이지만 메서드처럼 실행됨)
print(c.diameter) # 10프로퍼티는 계산 값에 유용합니다: () 없이 접근하는 것이 자연스럽게 느껴지는 다른 속성으로부터 파생된 것들입니다.
실전에서
인스턴스 속성, 메서드, @property, 그리고 __str__을 가진 Player 클래스:
class Player:
max_lives = 3
def __init__(self, name: str):
self.name = name
self.score = 0
self.lives = Player.max_lives
def earn_points(self, amount: int) -> None:
self.score += amount
def take_hit(self) -> bool:
self.lives -= 1
return self.lives > 0
@property
def is_alive(self) -> bool:
return self.lives > 0
def __str__(self) -> str:
return f"{self.name} | Score: {self.score} | Lives: {self.lives}"
alice = Player("민준")
alice.earn_points(50)
alice.take_hit()
print(alice) # "민준 | Score: 50 | Lives: 2"
print(alice.is_alive) # True
