-
객체 지향 프로그래밍python 2021. 3. 5. 16:13
'이 프로그램이 무슨 일을 하는가?'에 대한 답을 알려 준 절차 지향과 달리 객체 지향은 '현실 세계에 존재하는 객체(object)를 어떻게 모델링(modeling)할 것인가?'에 대한 물음에서 시작된다.
캡슐화
주변을 둘러보면 모든 사물이 저마다 특성이 있다는 것을 알 수 있다. 그 특성을 기준으로 객체들을 분류하거나 계층을 만들 수 있다. 필자는 '사람'이라는 계층(클래스)에 속한다.
모든 사람이 같은 특성을 갖고 있지만, 그 특성의 값은 다르다. 예를 들면 키, 나이, 몸무개, 성별, 인종은 모든 사람이 가지는 특성이지만, 그 값은 사람마다 다르다. 특성 값 하나하나가 모여 '나'라는 '객체'를 완성하는 것이다.
또한 사람은 '잠자기', '숨쉬기', '먹기' 같은 행동을 할 수 있다. 이처럼 객체는 고유의 특성 값과 행동 혹은 기능으로 표현할 수 있다. 객체가 지니는 특성 값은 변수로 나타낼 수 있고 행동 혹은 기능은 함수로 표현할 수 있다.
현실 세계의 객체를 나타내려면 변수와 함수만 있으면 된다. 현실 세계를 모델링하거나 프로그램을 구현하는 데 이처럼 변수와 함수를 가진 객체를 이용하는 패러다임을 '객체 지향 프로그래밍'이라고 하며, 변수(데이터)와 함수를 하나의 단위(대부분 언어에서 클래스)로 묶는 것을 캡슐화(encapsulation)라고 한다.
#인스턴스 멤버 초기화 def person_init(name, money): obj = {'name' : name, 'money' : money} obj['give_money'] = Person[1] obj['get_money'] = Person[2] obj['show'] = Person[3] return obj
클래스를 사용하지 않고 캡슐화를 구현해 봤다.
코드를 보면 person_init() 함수는 인자로 두 특성 값인 이름과 돈을 받는다. 딕셔너리에 두 변수(데이터)를 삽입하고 아직 나오지 않은 Person(튜플)의 1번 인덱스 값(함수)부터 차례대로 삽입한다. give_money, get_money, show는 아직 나오지 않았지만 함수다. person_init() 함수는 두 개의 변수와 세개의 함수를 가진 객체 딕셔너리를 반환한다. 이 객체가 가지는 세 개의 함수를 살펴보자
def give_money(self, money): self['money'] -= money other['get_money'](other, money) def get_money(self, money): self['money'] += money def show(self): print('{} : {}'.format(self['name'], self['money']))
세 함수는 person_init() 함수에서 객체 obj에 삽입하는 함수다. give_money() 함수는 한 사람 객체가 다른 사람 객체에게 돈을 주는 함수다. 전달받는 인자 중 other는 돈을 받는 사람 객체를 의미한다. 돈을 주는 사람의 돈은 줄어들 것이고 돈을 받는 사람의 돈은 늘어날 것이다. 다시 말해, 두 객체 간에 상호작용이 일어나 각자가 가지고 있는 데이터가 변경되는 것이다.
other 객체의 돈, 즉 변수를 변경할 때 돈을 받는 객체가 가지고 있는 특정 함수(get_money)를 호출하여 변경한다. 이처럼 서로 다른 객체가 함수 호출을 통해 상호작용하여 객체의 상태(데이터)가 변하는 것을 메시지 패싱(message passing)이라고 한다. 여기서 주목할 것은 서로 다른 객체가 상호작용할 때 함수를 호출했다는 것과 함수 안에서 상대의 변수를 바꾸려면 상대가 가진 특정 함수를 호출해야 한다는 점이다.
이제 소개한 함수들을 하나로 묶는 Person을 보자
Person = person_init, give_money, get_money, show
Person은 함수들을 퓨플로 묶고 있을 뿐이다. 클래스로 만들어지지 않았지만, 클래스처럼 동작한다.
if __name__ == "__main__": #객체 생성 g = Person[0]('greg', 5000) j = Person[0]('john', 2000) g['show'](g) j['show'](j) print('') #message passing g['give_money'](g, j, 2000) g['show'](g) j['show'](j)
5000원을 가진 greg 객체와 2000원을 가진 john 객체를 만들고 greg이 john에게 2000원을 give_money() 함수를 통해 준다. 돈을 주기 전과 후에 각 객체의 show() 함수를 호출해 보면 돈의 증감을 알 수 있다.
give_money(), show() 함수를 호출할 때 함수에 전달된 첫 번째 인자가 함수를 호출한 객체 자신이라는 점도 주의 깊게 봐야 한다. 이 부분을 이해하면 클래스를 작성할 때마다 보이는 self의 의미를 알 수 있다.
이어서 클래스를 이용해 객체를 만들어 보며 인스턴스, 멤버, 메서드, 속성의 개념을 알아보자.
컴퓨터는 객체가 현실 세계의 사물을 모델링한 것이라는 의미를 모른다. 컴퓨터에게 객체란 그저 메모리의 한 단위일 뿐이다. 객체라는 메모리 공간을 할당한 다음 객체 안에 묶인 변수를 초기화하고 함수를 호출하는 데 필요한 것이 클래스일 뿐이다. 클래스는 객체를 생성해 내는 템플릿이고, 객체는 클래스를 이용해 만들어진 변수와 함수를 가진 메모리 공간이다. 둘은 서로 다른 존재이며 메모리 공간도 다르다.
객체와 매우 유사한 개념으로 인스턴스(instance)가 있다. 객체는 객체 자체에 초점을 맞춘 용어고, 인스턴스는 이 객체가 어떤 클래스에서 만들어졌는지에 초점을 맞춘 용어다. "이 객체는 Person이라는 클래스의 인스턴스야"라고 말할 수도 있다.
class Person: def __init__(self, name, money): self.name = name self.money = money def give_money(self, other, money): self.money -= money other.get_money(money) def get_money(self, money): self.money += money def show(self): print("{0} : {1}".format(self.name, self.money))
Person이라는 클래스를 선언한다. 클래스 이름은 첫 글자를 대문자로 하는 것이 관용이다. 객체 지향 프로그래밍(OOP)에서는 클래스로 묶이는 변수를 프로퍼티(property) 또는 멤버 변수 혹은 멤버(member)라고 부른다. 파이썬에서는 멤버를 사용하므로 필자는 '멤버'라고 부르겠다. 특히 객체가 가지는 멤버를 인스턴스 멤버(instance member)라고 한다.
__init__() 함수는 생성자(constructor)라고 부르는 특별한 함수다. 앞뒤로 언더바가 두 개 있는 함수는 파이썬이 예약해 두었다는 의미다. 생성자의 역할은 인스턴스 멤버를 초기화하는 것이다.
self는 객체 자신을 의미한다. 생성 중인 객체에 name과 moeny라는 멤버를 만들고 전달받은 인자들로 할당한다.
OOP에서는 클래스에 묶이는 함수를 행동(behavioir), 멤버 함수, 메서드(method)라고 부른다. 파이썬에서는 메서드를 사용한다. 또한 멤버와 메서드를 합쳐 속성(attribute)이라고 부른다. give_money(), get_money(), show() 함수는 모두 객체가 갖게 될 메서드다.
if __name__ == "__main__": g = Person('greg', 5000) j = Person('john', 2000) g.show() j.show() g.give_money(j, 2000) print('') g.show() j.show()
#실행 결과 greg : 5000 john : 2000 greg : 3000 john : 4000
클래스 Person의 인스턴스를 만든 다음 Person() 안에 이름과 돈을 전달하면 클래스의 __init__() 함수가 호출되면서 객체의 멤버를 초기화한다. greg 객체가 john 객체에게 give_money() 메서드로 돈을 전달한다. 주목할 점은 이번 코드에서는 함수를 호출할 때 첫 번째로 인자로 함수를 호출하는데 객체를 전달하지 않았다는 점이다. 그 이유는 인스턴스 메서드를 호출하면 객체가 자동으로 첫 번째 인자인 self로 객체 자신을 전달하기 때문이다.
파이썬의 클래스
>>> type(Person.__init__) <class 'function'> >>> type(Person.give_money) <class 'function'>
>>> type(g.give_money) <class 'method'> >>> type(g.get_money) <class 'method'>
Person 클래스에 있는 메서드는 모두 함수다. 그러나 객체의 메서드는 함수가 아니라 메서드다. 객체의 메서드는 내부에 함수와 객체의 참조(__func__, __self__ 등등..)를 갖고 있으므로 직접 객체의 참조를 전달할 수 있기 때문에 메서드를 호출할 때 맨 처음 인자인 self를 전달하지 않아도 된다.
객체가 멤버와 메서드를 가질 수 있는 것처럼 클래스도 멤버와 메서드를 가질 수 있는데 메서드 위에 데코레이터(@classmethod) 처리를 하면 된다.
클래스 멤버와 클래스 메서드는 클래스가 가진 멤버와 메서드이므로 객체가 없어도 클래스를 통해 접근하거나 호출할 수 있다. 객체 지향 패러다임이 꺼리는 전역 변수와 전역 함수를 클래스 멤버와 클래스 메서드를 이요해 대체할 수 있다. 클래스 멤버와 클래스 메서드의 또 다른 특징은 객체에서도 접근하거나 호출할 수 있다는 점이다.
class A: @staticmethod def f(): print('static method') @classmethod def g(cls): print(cls.__name__) if __name__ == "__main__": a = A() a.f() a.g() 실행결과 static method A ----------------------------------------------------------- f()함수는 정적 메서드다. 정적 메서드는 인자로 클래스나 객체를 받지 않는다. 함수의 정의만 클래스A의 네임스페이스에 있을 뿐 일반 함수와 같으며 전역 함수를 대체하기에 가장 알맞다.
'python' 카테고리의 다른 글
파이썬 - 인수(argument) (0) 2021.03.16 클래스 - 메서드 오버라이딩과 다형성 (0) 2021.03.11 클래스 - 클래스 관계 (0) 2021.03.11 파이썬 - 절차지향 프로그래밍 (0) 2021.03.04 함수 (0) 2021.03.03