
클래스(Class)
지금까지 배운 프로그래밍은 함수 중심 혹은 절차 중심이었습니다.
이것은 동작 중심으로써, 실세계를 표현하는데 한계가 있습니다.
사람이 인지하는 실세계는 객체 중심이므로, 객체 중심 프로그래밍은 클래스를 사용한 프로그래밍입니다.
즉, 객체는 클래스를 통해 정의하고, 클래스 통해 객체를 생성하며, 이것을 객체 인스턴스라고 부릅니다.
클래스를 정의할 때는 class 키워드를 사용하여 안에 속성(변수)과 행동(메소드)으로 구성됩니다.
클래스를 정의하고, 생성자를 사용하여 객체를 생성하고 객체 인스턴스를 만듭니다.
생성된 객체를 통해 안의 속성(변수)와 행동(메소드)를 사용합니다.
클래스가 가져야 하는 변수를 객체 변수 혹은 필드라고 하고, 클래스에서 정의된 함수는 메소드라고 합니다.
생성자는 __init__(self) 함수로 정의하고, 객체가 생성될 때 자동으로 호출되는 특별한 메소드입니다.
참고로 객체 변수 및 필드는 생성자에서 초기화될 때 생성됩니다.
위 개념을 토대로 사각형 클래스를 정의해보겠습니다.
class Rectangle:
def __init__(self):
print("Rectangle 객체가 생성되었습니다.")
self.width = 0
self.height = 0
def get_area(self):
area = self.width * self.height
return area
def get_perimeter(self):
return (self.width + self.height) * 2이것을 토대로, 다른 2개의 객체 인스턴스를 호출하며, 객체 생성될 때 __init__ 메소드가 자동으로 호출됩니다.
각각의 객체 인스턴스에 서로 다른 인스턴스 변수를 가지고 있습니다.
r1 = Rectangle()
r2 = Rectangle()인스턴스 변수는 인스턴스 객체를 통해 인스턴스 변수에 접근하여 수정할 수 있습니다.
r1.width, r2.width = 100, 50
r2.width, r2.width = 50, 30또한, 새로운 인스턴스를 만들어 초기화할 수 있습니다.
r1.area = 19
print(r1.area) # 19생성자에서 매개변수인 self는 객체 자신을 가리키며, 다음과 같이 동작합니다.
r1.get_area()
Rectangle.get_area(r1)생성자에서 초기화할 인스턴스 변수를 매개변수로 받아, 초기화합니다.
class Rectangle:
def __init__(self, width=1, height=1):
print(f"Rectangle({width}, {height}) 생성자 생성")
self.width = width
self.height = height
# 사각형의 넓이 구하기
def get_area(self):
area = self.width * self.height
return area
# 사각형의 둘레 구하기
def get_perimeter(self):
return (self.width + self.height) * 2객체 인스턴스의 __dict__ 속성은 인스턴스 변수를 key-value 형태로 표현합니다.
r1 = Rectangle(30, 20)
r1.__dict__ # { 'width': 30, 'height': 20 }메소드의 종류에는 인스턴스 메소드, 클래스 메소드, 정적 메소드가 있습니다.
인스턴스 메소드는 첫번째 인자로 인스턴스 객체를 가리키는 self를 받는 메소드입니다.
클래스 메소드는 첫번째 인자로 cls를 받고 이것은 클래스 자체를 가리킵니다.
정적 메소드는 클래스 안에 넣은 일반 메소드처럼 동작합니다.
class Foo:
@classmethod
def func1(cls):
print("func1", cls)
def func2(self):
print('func2')
@staticmethod
def func3:
print('func3')클래스 메소드나 정적 메소드는 객체 인스턴스에서 사용 불가하고
인스턴스 메소드만 객체 인스턴스에서 사용 가능합니다.
클래스 메소드나 정적 메소드는 클래스를 통해서만 접근이 가능합니다.
f1 = Foo()
f1.func2()
Foo.func1() # func1 <class '__main__.Foo'>
Foo.func3()변수도 클래스 변수, 인스턴스 변수로 나뉩니다.
인스턴스 변수는 인스턴스마다 생성되며, self.변수이름 형태로 생성자에서 접근 가능한 형태입니다.
클래스 변수는 클래스에 딱 하나 생성되며, 클래스이름.변수이름 형태로 사용해야 합니다.
class Account:
num_accounts = 0 # 클래스 변수
def __init__(self, name):
print(f'Account({name}) 생성')
self.name = name # 인스턴스 변수
Account.num_accounts += 1상속(Inheritance)
기존의 만들어진 클래스를 상속받아, 새로운 클래스를 정의할 수 있습니다.
상속받아 만들어진 클래스는 기존 클래스의 메소드, 객체 변수를 그대로 상속받습니다.
상속받은 뒤, 새로운 객체 변수와 메소드를 추가할 수도 있고, 상속받은 메소드를 재정의하여 사용할 수도 있습니다.
상속은 기존의 코드를 다시 재작성할 필요 없이 새로 변경 추가되는 코드에만 집중할 수 있어 생산성이 향상됩니다.
다중 상속까지 허용하며 다음과 같이 상속 구문을 선언합니다.
class NewClass(Account, Foo):
pass상속이 필요한 이유를 다음과 같은 예시를 통해 알아보겠습니다. BasicTV 클래스를 작성합니다.
class BasicTV:
def __init__(self):
self.power = False
self.channel = 0
self.volume = 0
def display_info(self):
print("전원: ", self.power)
print("채널: ", self.channel)
print("볼륨: ", self.volume)시간이 지나고, 새로운 스마트 TV가 나와서 클래스를 수정해야 한다고 가정합니다.
이 때 기존 BasicTV 구조를 유지한 채 필요한 부분만 추가할 것입니다.
class SmartTV(BasicTV):
def __init__(self):
super().__init__ # 부모 클래스의 생성자 호출
self.IP = '192.168.0.1'
def display_info(self): # 오버라이딩
super().display_info()
print("IP: ", self.IP)그렇다면, 위와 같은 경우처럼 언제 상속이 필요할까요?
is-a 관계일 때 상속 관계로 만들며, has-a 관계일 때 클래스의 속성으로 만듭니다.
Vehicle과 Car 클래스가 있다고 가정하고, 이들간의 관계를 살펴봅시다.
Vehicle은 is a Car가 아니므로, Vehicle은 Car를 상속받으면 안됩니다.
반대로 Car is a Vehicle은 맞으므로, Car은 Vehicle을 상속받습니다.
매직 메소드 (Magic Method)
매직 메소드는 클래스 안에 정의할 수 있는 특별한 메소드이며
클래스를 int, str, list 등의 빌트인 타입과 같은 작동을 하게 해주는데, 연산자(오퍼레이터)에 대해서
각각의 데이터 타입에 맞는 메소드로 오버로딩하여 백그라운드에서 연산을 합니다.
__init__나 __str__ 같이 메소드 앞뒤에 언더스코어를 붙입니다.
예를 들어 다음과 같은 2개의 문자열이 있다고 가정하겠습니다.
s1 = "Hello"
s2 = "Python"__add__ 메소드는 문자열간의 + 연산과 같은 결과로, 각 문자열을 합쳐줍니다.
서로 다른 객체 안의 변수 연산도 가능합니다.
s1.__add__(s2) # 'HelloPython'__mul__ 메소드는 반복횟수(int)를 매개변수로 받아 그만큼 문자열을 반복시킵니다.
s1.__mul__(2) # 'HelloHello'__getitem__ 메소드는 인덱싱과 같은 효과로 인덱스 번호를 매개변수로 받아 그 요소를 반환합니다.
s1.__getitem__(0) # 'H'__contains__ 메소드는 in 연산자와 같은 의미로 해당 요소가 str 혹은 list에 포함되어 있는지 검사합니다.
s1.__contains__('e') # True__repr__와 __str__ 메소드는 객체를 문자열로 리턴합니다.
둘다 클래스에서 오버라이딩하여 사용하며 __str__ 오버라이딩 되어 있을 경우 __str__을 호출하고
__str__ 오버라이딩이 안되어 있을 경우 __repr__을 호출합니다.
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade
def __repr__(self):
return f'이름: {self.name}, 성적: {self.grade}'
def __str__(self):
return f'Student] {self.name}:{self.grade}'
s1 = Student('노진구', 4)
print(s1) # __str__ 호출__call__ 메소드는 객체를 호출 가능하게 해주는 매직 메소드입니다.
기본적으로 객체는 호출 불가능하지만, __call__ 메소드를 통해 객체를 호출할 수 있습니다.
class Foo:
def __init__(self):
print('hello Foo')
def __call__(self, postfix= 'Python'):
print(f'hello, {postfix}')
f1 = Foo()
Foo()() # hello, Python
f1('Java') # hello, Java