12장. 객체 지향 프로그래밍
12장. 객체 지향 프로그래밍
지금까지 프로그램을 작성할 때, 우리는 데이터를 다루는 명령들의 블록인 함수들의 조합으로 프로그램을 구성하였습니다. 이러한 설계 방식을 절차 지향 프로그래밍 기법이라고 부릅니다. 이와 달리 데이터와 기능을 객체라고 불리우는 것으로 묶어서 프로그램을 구성하는 또 다른 기법이 있습니다. 이것을 객체 지향 프로그래밍 기법이라고 부릅니다. 아마도 여러분의 대부분의 시간 동안 절차 지향 프로그래밍 기법을 통해 프로그램을 작성하게 되겠지만, 큰 프로그램을 작성할 때나 이 기법을 이용하는 것이 더 편리한 문제를 해결해야 할 경우 객체 지향 프로그래밍 기법을 활용할 수 있습니다.
객체 지향 프로그래밍에서는 클래스와 객체라는 두 가지 주인공이 있습니다. 클래스*는 새로운 형식을 정의하는 것이며, *객체*는 클래스의 *인스턴스 를 의미하는 것입니다. 이것을 다시 표현하면 여러분이 int 라는 형식의 변수를 만들 수 있다는 것으로, 이것은 곧 정수형을 저장하는 변수는 int 클래스의 인스턴스(객체)를 변수에 할당하는 것이라고도 말할 수 있습니다.
정적 언어 프로그래머들을 위한 주석
파이썬에서는 정수형조차도 객체로 다루어집니다 ( int 클래스의 객체입니다). C++이나 Java (버전 1.5 미만)처럼 정수형이 자체 기본 형식들 중 하나로 다루어지는 것과는 다릅니다.
help(int) 를 입력하여 정수형 클래스에 대해 좀 더 자세히 알아보시기 바랍니다.
C# 이나 Java 1.5 프로그래머들은 아마 이것이 boxing 과 unboxing 과 비슷하다는 것을 눈치채셨을 것입니다.
C# 이나 Java 1.5 프로그래머들은 아마 이것이 boxing 과 unboxing 과 비슷하다는 것을 눈치채셨을 것입니다.
객체는 그 객체에 내장된 일반적인 변수들을 사용하여 데이터를 저장할 수 있습니다. 이 때 객체 혹은 클래스에 소속된 변수들을 필드(field) 라고 부릅니다. 객체는 또한 내장된 함수를 이용하여 어떤 기능을 갖도록 할 수 있는데 이것을 클래스의 메소드(method) 라고 부릅니다. 이러한 명칭을 구별하여 부르는 것은 중요한데, 이는 일반적인 변수와 함수와 달리 이들은 클래스나 객체에 소속되어 있는 대상들이기 때문입니다. 또, 이러한 필드와 메소드들을 통틀어 클래스의 속성(attribute) 이라 부릅니다.
필드는 두 가지 종류가 있습니다. 하나는 클래스의 인스턴스/객체에 내장되어 있는 것이고, 또 하나는 클래스 자체에 내장되어 있는 것입니다. 각각을 인스턴스 변수 와 클래스 변수 라 부릅니다.
클래스는 class 키워드를 통해 생성됩니다. 클래스의 필드와 메소드는 그 아래 들여쓰기 된 블록에 차례로 정의됩니다.
12.1. self 에 대하여
클래스 메소드는 일반적인 함수와 딱 한 가지 다른 점이 있는데, 그것은 메소드의 경우 매개 변수의 목록에 항상 추가로 한 개의 변수가 맨 앞에 추가되어야 한다는 점입니다. 또한 메소드를 호출할 때 이 변수에는 우리가 직접 값을 넘겨주지 않으며, 대신 파이썬이 자동으로 값을 할당합니다.이 변수에는 현재 객체 자신의 참조가 할당되며, 일반적으로 self 라 이름을 짓습니다.
이 변수의 이름은 마음대로 지을 수 있지만, self 라는 이름을 사용할 것을 강력히 권합니다. 이것은 일종의 약속이며, 다른 이름을 사용하는 것은 다른 프로그래머들에게 눈살을 찌푸려지게 하는일이 될 수 있기 때문입니다. `self`라는 표준적인 이름을 사용하면 여러분의 프로그램을 읽는 사람들로부터 이것이 바로 그 변수를 의미함을 쉽게 알아보게 할 수 있고, 특별한 IDE (Integrated Development Environment)를 사용하는 사람들도 이를 쉽게 알아볼 수 있는 등 여러 장점이 있습니다.
C++/Java/C# 프로그래머를 위한 주석
파이썬의 self 는 C++ 의 this 포인터와 같은 것이며, Java와 C# 의 this 참조와 같습니다
아마 여러분은 파이썬이 self 에 어떻게 값을 할당하는 것인지 그리고 정말 값을 직접 할당할 필요가 없는지 궁금할 것입니다. 이해를 돕기 위해 예를 하나 들어 보겠습니다. 여러분이 MyClass라는 클래스를 생성했고, 이 클래스의 객체를 myobject 라는 이름으로 생성했다고 해 봅시다.이제 이 객체의 메소드를 호출할 때는 yobject.method(arg1, arg2) 와 같이 하며, 이것은 파이썬에 의해 자동적으로 MyClass.method(myobject, arg1, arg2) 의 형태로 바뀌게 됩니다. 이것이 self 에 대한 모든 것입니다.
또한 이것은 아무런 인수도 넘겨받지 않는 메소드를 정의할 때에도, self 라는 하나의 인수를 추가해 주어야 한다는 것을 의미합니다.
12.2. 클래스
가장 단순한 클래스의 예시가 아래 예제에 나타나 있습니다( oop_simplestclass.py 로 저장하세요).
1 2 3 4 5 | class Person: pass # An empty block p = Person() print(p) | cs |
실행 결과:
1 2 | $ python oop_simplestclass.py <__main__.Person instance at 0x10171f518> | cs |
동작 원리 먼저 class 문을 사용하여 새로운 클래스를 생성하였고 적당한 이름을 지어 주었습니다. 그 아래로는 들여쓰기 된 새로운 블록이 시작되며 이 블록은 클래스의 몸체를 구성합니다. 위 예제의 경우에는 pass 문으로 해당 블록이 빈 블록임을 나타내 주었습니다.
다음으로, 이 클래스의 이름 뒤에 괄호를 열고 닫아 주어 클래스의 객체/인스턴스를 만들었습니다 (다음 섹션에서 객체 초기화 에 대해 좀 더 자세히 배울 것입니다). 객체가 잘 생성되었는지 확인해 보기 위해, 정의한 변수명을 입력하여 결과를 확인해 봅니다. 그러면 이 객체는 main 모듈의 Person 클래스의 인스턴스임을 알 수 있습니다.
또 객체가 실제로 저장된 컴퓨터 메모리의 위치가 함께 반환되는 것을 확인하시기 바랍니다. 컴퓨터마다 그 객체를 저장하기 위한 빈 공간이 위치한 곳이 다를 것이므로 컴퓨터마다 이 값은 다르게 출력될 것입니다.
12.3. 메소드
앞서 클래스/객체는 메소드를 가질 수 있으며, 메소드는 추가된 self 변수를 제외하고 함수와 똑같다는 것에 대해 이야기했습니다. 아래는 예제입니다( oop_method.py 로 저장하세요).
1 2 3 4 5 6 7 8 | class Person: def say_hi(self): print('Hello, how are you?') p = Person() p.say_hi() # The previous 2 lines can also be written as # Person().say_hi() | cs |
실행 결과:
1 2 | $ python oop_method.py Hello, how are you? | cs |
동작 원리위 예제는 self 가 어떻게 동작하는지 보여줍니다. 여기서 say_hi 메소드는 아무 매개 변수도 넘겨받지 않지만 함수 정의에 self 를 가지고 있음을 확인하시기 바랍니다.
12.4. init 메소드
파이썬의 클래스에는 여러가지 특별한 메소드 이름이 존재합니다. 우선 그 중 init 메소드의 중요성에 대해 알아보겠습니다.
init 메소드는 클래스가 인스턴스화 될 때 호출됩니다. 따라서 이 메소드는 객체가 생성될 때 여러가지 초기화 명령들이 필요할 때 유용하게 사용됩니다. 여기서 init의 앞과 뒤에 있는 밑줄은 두번씩 입력해야 한다는 점을 기억하시기 바랍니다.
예제 ( oop_init.py 로 저장하세요):
1 2 3 4 5 6 7 8 9 10 | class Person: def __init__(self, name): self.name = name def say_hi(self): print ('Hello, my name is', self.name) p = Person('Swaroop') p.say_hi() # The previous 2 lines can also be written as # Person('Swaroop').say_hi() | cs |
실행 결과:
1 2 | $ python oop_init.py Hello, my name is Swaroop | cs |
동작 원리먼저 매개 변수 name 을 넘겨 받는 init 메소드를 정의합니다(물론 self`를 포함하여 정의합니다). 그리고, `name 이라는 필드를 생성합니다. 이 때 두 다른 변수의 이름으로 'name' 이라는 동일한 이름을 지정해 주었다는 점에 주목하시기 바랍니다. 이것이 문제가 되지않는 이유는 하나는 "self" 라 칭해지는 객체에 내장된 것으로써 self.name 의 형태로 사용되며 또 하나인 name 은 지역 변수를 의미하는 것으로 사용되기 때문입니다. 프로그램 상에서 각각을 완전하게 구분할 수 있으므로, 혼란이 일어나지 않습니다.
위 예제에서 가장 중요한 것은, 우리가 init 메소드를 직접 호출해 주지 않고 클래스로부터 인스턴스를 생성할 때 괄호 안에 인수를 함께 넘겨 주었다는 점입니다. 이 점이 이 메소드가 좀 특별하게 다뤄지는 이유입니다.
이제, sayHi 메소드에서처럼 객체 내부에서 self.name 필드를 사용할 수 있습니다.
12.5. 클래스 변수와 객체 변수
앞서 클래스와 객체가 어떤 기능을 갖도록 하는 방법, 즉 메소드에 대해 설명했습니다. 이제 데이터의 경우 어떻게 하는지 배워봅시다. 데이터, 즉 필드는 일반적인 변수와 다를 것이 없으나 딱 한가지, 그 클래스 혹은 객체의 네임스페이스 에 묶여 있다는 점이 다릅니다. 이것은 필드의 이름은 그 클래스 혹은 객체 내부에서만 의미가 있음을 의미합니다. 그래서 이것을 이름이 통용되는 공간이라고 하여 네임스페이스 라고 부릅니다.
필드 에는 두 종류가 있는데, 클래스 변수와 객체 변수입니다. 각각은 그것을 소유하고 있는 대상이 클래스인지 객체인지에 따라 구분됩니다.
클래스 변수 는 공유됩니다. 즉, 그 클래스로부터 생성된 모든 인스턴스들이 접근할 수 있습니다. 클래스 변수는 한 개만 존재하며 어떤 객체가 클래스 변수를 변경하면 모든 다른 인스턴스들에 변경 사항이 반영됩니다.
객체 변수 는 클래스로부터 생성된 각각의 객체/인스턴스에 속해 있는 변수입니다. 이 경우에는, 각각의 객체별로 객체 변수를 하나씩 따로 가지고 있으며, 서로 공유되지 않고 각 인스턴스에 존재하는 같은 이름의 필드끼리 서로 어떤 방식으로든 간섭되지 않습니다. 아래 예제를 통해 좀 더 자세히 알아봅시다. ( oop_objvar.py 로 저장하세요):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | class Robot: """Represents a robot, with a name.""" # A class variable, counting the number of robots population = 0 def __init__(self, name): """Initializes the data.""" self.name = name print ("(Initializing {})".format(self.name)) # When this person is created, the robot # adds to the population Robot.population += 1 def die(self): """I am dying.""" print ("{} is being destroyed!".format(self.name)) Robot.population -= 1 if Robot.population == 0: print ("{} was the last one.".format(self.name)) else: print("There are still {:d} robots working.".format(Robot.population)) def say_hi(self): """Greeting by the robot. Yeah, they can do that.""" print("Greetings, my masters call me {}.".format(self.name)) @classmethod def how_many(cls): """Prints the current population.""" print ("We have {:d} robots.".format(cls.population)) droid1 = Robot("R2-D2") droid1.say_hi() Robot.how_many() droid2 = Robot("C-3PO") droid2.say_hi() Robot.how_many() print( "\nRobots can do some work here.\n") print( "Robots have finished their work. So let's destroy them.") droid1.die() droid2.die() Robot.how_many() |
1 2 3 4 5 6 7 8 9 | $ python oop_objvar.py (Initializing R2-D2) Greetings, my masters call me R2-D2. We have 1 robots. (Initializing C-3PO) Greetings, my masters call me C-3PO. We have 2 robots. Robots can do some work here. Robots have finished their work. So let's destroy them. R2-D2 is being destroyed!
There are still 1 robots working.
C-3PO is being destroyed!
C-3PO was the last one.
We have 0 robots. |
1 | how_many = classmethod(how_many) | cs |
C++/Java/C# 프로그래머를 위한 주석
모든 클래스 멤버는 (데이터 멤버를 포함하여) public 이며 따라서 파이썬의 모든 메소드는 virtual 입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | class SchoolMember: '''Represents any school member.''' def __init__(self, name, age): self.name = name self.age = age print ('(Initialized SchoolMember: {})'.format(self.name)) def tell(self): '''Tell my details.''' print ('Name:"{}" Age:"{}"'.format(self.name, self.age),) class Teacher(SchoolMember): '''Represents a teacher.''' def __init__(self, name, age, salary): SchoolMember.__init__(self, name, age) self.salary = salary print ('(Initialized Teacher: {})'.format(self.name)) def tell(self): SchoolMember.tell(self) print ('Salary: "{:d}"'.format(self.salary)) class Student(SchoolMember): '''Represents a student.''' def __init__(self, name, age, marks): SchoolMember.__init__(self, name, age) self.marks = marks print ('(Initialized Student: {})'.format(self.name)) def tell(self): SchoolMember.tell(self) print ('Marks: "{:d}"'.format(self.marks)) t = Teacher('Mrs. Shrividya', 40, 30000) s = Student('Swaroop', 25, 75) # prints a blank line print members = [t, s] for member in members: # Works for both Teachers and Students member.tell() |
1 2 3 4 5 6 7 | $ python oop_subclass.py (Initialized SchoolMember: Mrs. Shrividya) (Initialized Teacher: Mrs. Shrividya) (Initialized SchoolMember: Swaroop) (Initialized Student: Swaroop) Name:"Mrs. Shrividya" Age:"40" Salary: "30000" Name:"Swaroop" Age:"25" Marks: "75" | cs |