함수형, 선언형, 명령형 스타일로 프로그래밍해 왔다면, 객체 지향 프로그래밍(OOP)으로 초점을 옮기는 것이 조금 낯설게 느껴질 수 있어요.
OOP 접근 방식은 문제를 서로 상호작용하고, 상태를 유지하며, 데이터에 작용하는 하나 이상의 objects로 모델링하는 것을 생각해 보라고 프로그래머에게 요청해요.
객체는 현실 세계의 개체(예를 들어 자동차나 고양이)를 나타낼 수도 있고, 더 추상적인 개념(예를 들어 정수, 탈것, 포유류)을 나타낼 수도 있어요.
각 객체는 컴퓨터 메모리에서 고유한 인스턴스가 되고, 전체 모델의 일부를 나타내요.
Classes는 새로운 객체 타입의 정의이고, 여기서 새로운 객체 instances가 만들어져요.
클래스는 흔히 데이터를, 그 데이터를 다루는 코드나 함수와 함께 묶어요.
이런 의미에서 클래스는 비슷한 타입의 객체를 많이 만들어 사용할 수 있는 청사진 또는 일련의 지침이에요.
복잡한 프로그램은 클래스를 많이 가질 수 있고, 각 클래스는 여러 가지 서로 다른 객체를 만들어요.
클래스로부터 객체를 만드는 과정을 instantiation(또는 클래스의 인스턴스를 만드는 것)이라고 해요.
Python에서 클래스 정의는 간단해요:
class MyClass:
# Class body goes here
클래스 필드(달리 properties, attributes, data members, variables라고도 해요)는 클래스 본문에 추가할 수 있어요:
class MyClass:
number = 5
string = "Hello!"
MyClass의 인스턴스(객체)는 클래스를 호출해서 만들고 이름에 묶을 수 있어요(함수를 호출하는 것과 같은 방식이에요):
>>> new_object = MyClass()
# Class is instantiated and resulting object is bound to the "new_object" name.
# Note: the object address 'at 0x15adc55b0' will vary by computer.
>>> new_object
<__main__.MyClass at 0x15adc55b0>
Class attributes는 클래스에서 만들어진 모든 객체(또는 인스턴스)가 공유하고, dot notation, 즉 객체 이름 뒤에 속성 이름 앞에 놓는 .을 통해 접근할 수 있어요:
>>> new_object = MyClass()
# Accessing the class attribute "number" via dot-notation.
>>> new_object.number
5
# Accessing the class attribute "string" via dot-notation.
>>> new_object.string
'Hello!'
# Instantiating an additional object and binding it to the "second_new_object" name.
>>> second_new_object = MyClass()
>>> second_new_object
# Note: the object address "at 0x15ad99970" will vary by computer.
<__main__.MyClass at 0x15ad99970>
# Second_new_object shares the same class attributes as new_object.
>>> new_object.number == second_new_object.number
True
클래스 속성은 다른 어떤 메서드보다도 먼저, 클래스 자체의 본문에 정의해요. 클래스 속성은 클래스가 소유하기 때문에, 클래스의 인스턴스들 사이에서 공유될 수 있어요. 이 속성들은 공유되기 때문에, 클래스에서 직접 값에 접근하고 조작할 수 있어요. 클래스 속성의 값을 바꾸면 클래스에서 만들어진 모든 객체의 값이 바뀌어요:
>>> obj_one = MyClass()
>>> obj_two = MyClass()
# Accessing a class attribute from an object.
>>> obj_two.number
5
# Accessing the class attribute from the class itself.
>>> MyClass.number
5
# Modifying the value of the "number" class attribute.
>>> MyClass.number = 27
# Modifying the "number" class attribute changes the "number" attribute for all objects.
>>> obj_one.number
27
>>> obj_two.number
27
항상 데이터가 동기화된 객체 여러 개를 갖는 것은 그다지 유용하지 않아요.
다행히, 클래스에서 만든 객체는 고유한 instance attributes(또는 인스턴스 속성, 변수, 필드)로 커스터마이즈할 수 있어요.
이름에서 알 수 있듯이, 인스턴스 속성은 각 객체에 고유하고, 독립적으로 수정할 수 있어요.
__init__()로 객체 생성 커스터마이즈하기특별한 "던더 메서드"("double underscore method"의 줄임말)인 __init__()는 클래스 인스턴스를 커스터마이즈하는 데 쓰이고, 객체의 인스턴스 속성이나 프로퍼티를 초기화하는 데 사용할 수 있어요.
인스턴스 속성을 초기화하는 역할 때문에, __init__()은 class constructor 또는 initializer라고도 해요.
__init__()는 self라는 필수 매개변수 하나를 받는데, 이는 새로 초기화되거나 만들어진 객체를 가리켜요.
그런 다음 인스턴스 속성이나 프로퍼티를 위한 데이터를 self 매개변수 뒤에 __init__()의 인자로 전달할 수 있어요.
아래에서 MyClass는 location_x와 location_y라는 인스턴스 속성을 가져요.
보다시피, 두 속성은 __init__()에 전달된 location(튜플) 인자의 첫 번째와 두 번째 인덱스에 할당됐어요.
이제 클래스를 인스턴스화해서 객체를 만들면 클래스 인스턴스의 location_x와 location_y 속성이 초기화돼요:
class MyClass:
# These are class attributes, variables, or fields.
number = 5
string = "Hello!"
# This is the class "constructor", with a "location" parameter that is a tuple.
def __init__(self, location):
# This is an instance or object property, attribute, or variable.
# Note that we are unpacking the tuple argument into two separate instance variables.
self.location_x = location[0]
self.location_y = location[1]
# Create a new object "new_object_one", with object property (1, 2).
>>> new_object_one = MyClass((1, 2))
# Create a second new object "new_object_two" with object property (-8, -9).
>>> new_object_two = MyClass((-8, -9))
# Note that new_object_one.location_x and new_object_two.location_x two different values.
>>> new_object_one.location_x
1
>>> new_object_two.location_x
-8
위에서 MyClass를 초기화할 때는 인자 하나만 전달하면 된다는 점에 주의해요. Python이 클래스를 호출할 때 self를 전달하는 것을 알아서 처리해 줘요.
method는 클래스 자체(클래스 메서드라고 하며, 나중 연습 문제에서 다뤄요)나 클래스의 인스턴스(객체)에 묶인 function이에요.
객체(인스턴스)를 다루는 메서드는 self를 첫 번째 매개변수로 해서 정의해야 해요.
그러고 나서 나머지 매개변수는 "일반" 함수, 즉 묶이지 않은 함수에서처럼 정의하면 돼요:
class MyClass:
number = 5
string = "Hello!"
# Class constructor.
def __init__(self, location):
# Instance properties
self.location_x = location[0]
self.location_y = location[1]
# Instance method. Note "self" as first parameter.
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
속성 접근과 마찬가지로, 메서드 호출은 객체 이름 뒤에, 메서드 이름 앞에 .을 놓기만 하면 돼요.
호출된 메서드는 첫 번째 매개변수로 객체의 복사본을 필요로 하지 않아요. Python이 self를 자동으로 채워 줘요:
class MyClass:
number = 5
string = "Hello!"
def __init__(self, location):
self.location_x = location[0]
self.location_y = location[1]
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
# Make a new test_object with location (3,7)
>>> test_object = MyClass((3,7))
>>> (test_object.location_x, test_object.location_y)
(3,7)
# Call change_location to increase location_x and location_y by 7.
>>> test_object.change_location(7)
(10, 14)
클래스 속성은 클래스 밖에서 접근하는 것과 같은 방식으로 인스턴스 메서드 안에서 접근할 수 있어요:
class MyClass:
number = 5
string = "Hello!"
def __init__(self, location):
self.location_x = location[0]
self.location_y = location[1]
# Alter instance variable location_x and location_y
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
# Alter class variable number for all instances from within an instance.
def increment_number(self):
# Increment the 'number' class variable by 1.
MyClass.number += 1
>>> test_object_one = MyClass((0,0))
>>> test_object_one.number
5
>>> test_object_two = MyClass((13, -3))
>>> test_object_two.increment_number()
>>> test_object_one.number
6
이전 개념 연습 문제와 실습 연습 문제 스텁에서, 실제 코드 대신 함수 본문 안에 pass 키워드가 사용된 것을 봤을 거예요.
pass 키워드는 문법적으로 유효한 자리 표시자예요. 빈 함수나 클래스 정의에 대해 Python이 문법 오류를 던지는 것을 막아 줘요.
본질적으로, 이는 Python 인터프리터에게 '걱정 마세요! 언젠가는 분명히 여기에 코드를 넣을 거예요, 아직 안 했을 뿐이에요.'라고 말하는 방법이에요.
class MyClass:
number = 5
string = "Hello!"
def __init__(self, location):
self.location_x = location[0]
self.location_y = location[1]
# Alter instance variable location_x and location_y
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
# Alter class variable number for all instances
def increment_number(self):
# Increment the 'number' class variable by 1.
MyClass.number += 1
# This will compile and run without error, but has no current functionality.
def pending_functionality(self):
# Stubbing or placholding the body of this method.
pass
Ellen은 플레이어가 외계인과 싸워야 하는 게임을 만들고 있어요.
막 객체 지향 프로그래밍(OOP)을 배운 참이라, classes를 사용하면 프로그램에 어떤 도움이 될지 기대에 부풀어 있어요.
Ellen은 기뻐하며 여러분에게 도움을 청했고, 여러분은 플레이어가 싸워야 할 외계인을 프로그래밍하는 일을 맡게 되었어요.
두 매개변수 <x_coordinate>, <y_coordinate>를 받는 생성자를 가진 Alien 클래스를 정의하고, 이 값들을 x_coordinate와 y_coordinate 인스턴스 변수에 넣어요.
또한 모든 외계인은 체력 3으로 시작하므로, health 변수도 함께 초기화해야 해요.
>>> alien = Alien(2, 0)
>>> alien.x_coordinate
2
>>> alien.y_coordinate
0
>>> alien.health
3
이제 각 외계인은 자신의 위치와 체력을 내부적으로 추적할 수 있어야 해요.
hit 메서드Ellen은 Alien class에, 호출되면 외계인 객체의 체력을 1만큼 줄여 주는 hit 메서드가 있으면 좋겠다고 해요.
이렇게 하면 외계인의 체력을 직접 바꾸지 않고도 <alien>.hit()을 호출하기만 하면 돼요.
hit()이 체력을 0까지 낮출지, 0 아래로도 내려가게 할지는 여러분이 정하면 돼요.
>>> alien = Alien(0, 0)
# Initialized health value.
>>> alien.health
3
# Decrements health by 1 point.
>>> alien.hit()
>>> alien.health
2
is_alive 메서드체력이 계속 줄어들다 보면 언젠가는 0이 되거나, 심지어 그보다 더 낮아질 수도 있다는 걸 알게 될 거예요.
그럴 때 Ellen이 외계인이... 음... 살아 있는지 빠르게 확인할 수 있도록 is_alive 메서드를 추가하면 좋겠어요. 😉
<alien>.is_alive()는 불리언을 반환해야 해요.
>>> alien.health
1
>>> alien.is_alive()
True
>>> alien.hit()
>>> alien.health
0
>>> alien.is_alive()
False
teleport 메서드Ellen의 게임에서 외계인은 순간이동 능력을 가지고 있어요!
새로운 x_coordinate와 y_coordinate 값을 받아 그에 맞게 외계인의 좌표를 바꾸는 teleport 메서드를 작성해야 해요.
>>> alien.teleport(5, -4)
>>> alien.x_coordinate
5
>>> alien.y_coordinate
-4
collision_detection 메서드외계인이 무언가에 맞을 수 있다면, 당연히 그런 충돌이 일어났을 때 이를 감지할 수 있어야 해요.
하지만 충돌 감지 알고리즘은 까다로울 수 있고, 아직 어떻게 구현해야 할지 모르죠.
Ellen은 나중에 직접 하겠다고 했지만, 기능을 구현해야 한다는 걸 잊지 않도록 collision_detection 메서드를 클래스에 넣어 두고 싶어 해요.
이 메서드는 어떤 종류의 변수(아마도 다른 객체)를 받아야 하지만, 아직 아는 건 그것뿐이에요.
메서드 정의를 클래스에 넣었을 때 호출해도 오류가 나지 않도록 해야 해요:
>>> alien.collision_detection(other_object)
>>>
Ellen이 새로운 부탁을 가지고 돌아왔어요. 게임이 진행되는 동안 외계인이 몇 개 만들어졌는지 추적하고 싶대요. 점수 시스템과 관련이 있다고 말해요.
예를 들어:
>>> alien_one = Alien(5, 1)
>>> alien_one.total_aliens_created
1
>>> alien_two = Alien(3, 0)
>>> alien_two.total_aliens_created
2
>>> alien_one.total_aliens_created
2
# Accessing the variable from the class directly
>>> Alien.total_aliens_created
2
Ellen은 지금까지 해준 게 마음에 쏙 들지만, 부탁이 하나 더 있어요.
Alien() 클래스 바깥에 있는 독립적인 함수를 원하는데, 이 함수는 위치들의 배열(tuples 형태)을 받아 Alien() 객체들의 list를 만들어요.
예를 들어:
>>> alien_start_positions = [(4, 7), (-1, 0)]
>>> aliens = new_aliens_collection(alien_start_positions)
...
>>> for alien in aliens:
... print(alien.x_coordinate, alien.y_coordinate)
(4, 7)
(-1, 0)
Exercism에 가입하고 Python 트랙을 개념 17개연습 문제 146개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.