Post

[Python] 객체지향 프로그래밍

컴퓨터 사이언스 부트캠프 with 파이썬을 보고 정리한 내용입니다.

절차 지향 프로그래밍

  • 절차 = 프로시저(procedure)는 서브 루틴, 메서드, 함수라고도 불림 = 간단하게 함수라고 하자.
  • 함수는
    • 입력을 받아 일련의 연산과정을 거쳐 출력을 내보냄
    • 한번 정의해두면 어디서든 다시 사용할 수 있고 이름만 봐도 어떤 일을 하는지 알 수 있음
    • 사용하는 사람이 만든 사람처럼 내부 구현을 할 필요가 없음 (= 다른 프로그래머도 쉽게 프로그램을 이해하고 유지보수 할 수 있음)

객체 지향 프로그래밍 (OOP)

  • 객체(object) 지향 프로그래밍은 변수와 함수를 이용해 현실 세계에 존재하는 객체를 모델링함

캡슐화

  • 우리는 모두 ‘사람’이라는 계측(클래스)에 속함
  • 각자 키, 몸무게, 국적, 성별 등 같은 특성을 갖고 있지만 그 값은 다름 → 이 특성 값은 변수
  • 다들 잠자기, 숨쉬기, 말하기, 먹기 등의 행동을 할 수 있음 → 이 행동/기능은 함수
  • 이 변수(데이터)와 함수를 하나의 단위(클래스)로 묶는다 = 캡슐화 (encapsulation)
1
2
3
4
5
6
7
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이라는 클래스의 인스턴스야’
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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('{} : {}'.format(self.name, self.money))
  • OOP에서는
    • 클래스로 묶이는 변수를 프로퍼티 / 멤버 변수 / 멤버라고 함 (*파이썬에서는 멤버)
      • 객체가 가지는 멤버를 인스턴스 멤버라고 함
    • 클래스에 묶이는 함수를 행동 / 멤버 함수 / 메서드라고 함(*파이썬에서는 메서드)
    • 멤버 + 메서드를 합쳐서 속성(attribute)라고 함
  • __init__ 은 constructor 라고 부르는 특별한 함수로, 인스턴스 멤버를 초기화하는 역할
    • self 는 객체 자신을 의미함
    • 생성 중인 객체에 name과 money 라는 멤버를 만들고 전달받은 인자들로 할당한다.
  • give_money(), get_money(), show() 함수는 모두 객체가 갖게 될 메서드임
1
2
3
4
5
6
7
8
9
if __name__ = ="__main__":
	g = Person('greg', 5000)
	j = Person('john', 2000)

	g.show()
	j.show()

	g.give_money(j, 2000)
#인스턴스 메서드를 호출하면 객체가 자동으로 첫번째 인자인 self로 객체 자기 자신을 전달함

클래스 살펴보기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type(Person.__init__)
type(Person.give_money)
type(Person.get_money)

#Person 클래스에 있는 것은 모두 함수임

type(g.give_money)
#객체의 메서드는 함수가 아닌 메서드로 출력됨

dir(g.give_money)
#이 메서드의 attribute 확인, __func__, __self__가 보임
g.give_money.__self__  # 이 메서드를 가진 자기 자신을 참조하고 있음 
g.give_money.__self__ is g # True
#메서드 내부에 함수와 객체의 참조를 가지고 있으므로 함수에 직접 객체 참조를 전달할 수 있음
  • 객체가 멤버와 메서드를 가질 수 있는 것처럼 클래스도 멤버와 메서드를 가질 수 있음
    • 객체가 없어도 클래스를 통해 접근/호출할 수 있음
    • 객체를 통해서도 접근/호출 가능
1
2
3
4
5
6
7
8
9
10
11
12
class A:
	c_mem = 10 #클래스 멤버 <-> 인스턴스멤버와 다름
 
	@classmethod #데코레이터를 사용해서 클래스 메서드를 만듦
	def cls_f(cls)
		print(cls.c_mem)

	def __init__(self, num):
		self.i_mem = num

	def ins_f(self):
		print(self.i_mem)

객체 지향 - 입출금 프로그램 만들기

  • 은행에서 계좌 클래스를 관리하면서 총 몇개 개설되었는지를 클래스 멤버로 두어 인스턴스를 만들 때마다 하나씩 늘리자.
  • 단, 계좌를 이용하는 고객은 개설 계좌 수를 인스턴스 멤버로 가질 필요가 없음
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
class Account:
	num_act = 0

	@classmethod
	def get_num_acnt(clas):
		return cls.num_acnt

	def __init__(self, name, money):
		self.user = name
		self.balance = money
		Account.num_acnt += 1 #계좌 하나 개설할 때마다 1씩 증가

	def deposit(self, money)
		if money < 0:
			return
		self.balance += money

	def withdraw(self, money):
			if money >0 and money <= self.balance:
				self.balance -= money
				return money
			else:
				return None

	def transfer(self,other, moeny):
		mon = slef.withdraw(money)
		if mon:
			other.deposit(mon)
			return True
		else:
			return False

		def __str__(self):
			return 'user : {}, balance : {}'.format(self.user, self.balance)

정보 은닉

  • 캡슐화할 때 어떤 멤버와 메서드는 공개하여 유저 프로그래머가 사용할 수 있도록 하고 어떤 멤버와 메서드는 숨길지 정해야 하는데 이런 개념을 정보 은닉(information hiding)이라고 함
  • 숨길 경우, 메서드와 멤버가 클래스 안에서만 사용할 수 있고 객체를 통해서는 접근하거나 호출할 수 없게 됨
    • 숨기는 이유?
      • set_balance라는 메서드가 있고 이 메서드는 Account 클래스의 balance 값을 바꿀 수 있지만, balance가 음수인 것은 허용하지 않음 → 이를 액세스 함수라고 함
      • balance를 숨겨놓으면, 유저 프로그래머가 직접적으로 balance를 바꿀 수 없고 오직 set_balance를 이용해야만 하게 됨 (음수를 실수로 입력하는 것을 방지)
  • 단 파이썬은 기본적으로 정보 은닉을 지원하지 않음(완벽한 정보 은닉 불가)
  • 다만 유저 프로그래머의 실수를 막을 수 있는 방법을 2가지 제공함

1- 숨기려는 멤버 앞에 언더바 두개 붙이기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Account:
	def __init__(self, name, money):
		self.user = name
		self.__balance = money

	def get_balance(self):
		return self.__balance

	def set_balance(self, money):
			if money < 0:
				return
			slef.__balance = money

if __name__ == "__main__":
	my_acnt = Account('greg', 5000)
	my_acnt.__balance = -3000 #직접 입력(실수)

	print(my_acnt.get_balance())

#5000이 출력되고, 실수로 입력한 -3000은 반영되지 않았음
  • 만약 언더바 두개를 멤버 앞에 붙이면, 이 멤버는 객체가 만들어질 때 이름이 변함 ( _클래스 이름 이 멤버 앞에 붙게 됨)
  • __balance_Account__balance가 되는데, 다음과 같이 호출할 수 있음
1
2
my_acnt.__dict__
#{'user': 'greg', '_Account__balance':5000, '__balance':-3000}
  • 즉 바꿀려면 바꿀수도 있다..!

2- 프로퍼티 기법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Account:
	def __init__(self, name, money):
		self.user = name
		self.balance = money

	#멤버가 아닌 setter 메서드
	@property
	def balance(slef):
		return self._balance

	@balance.setter
	def balance(self, money):
			if money < 0:
				return
			slef._balance = money

if __name__ == "__main__":
	my_acnt = Account('greg', 5000)
	my_acnt.balance = -3000 #직접 입력(실수)

	print(my_acnt.balance())
  • 데코레이터 @property 를 붙이는 순간 balance()는 getter 함수가 되어 이전 버전의 get_balance() 메서드와 같은 역할을 함
  • 이름이 같은 다른 함수는 @balance.setter 를 붙여 setter 함수로 쓰고, set_balance() 메서드와 같은 역할을 함
  • 유저 프로그래머가 객체를 만들어 접근할 때는 getter, setter의 이름인 balance를 마치 멤버인 것처럼 사용하게 됨 → 멤버에 직접 접근하는 것 같지만 사실은 getter, setter 함수를 호출해 실제 멤버인 _balance 에 접근함
  • 단 이 방법의 경우에도 다음과 같이 호출하면,
1
2
3
my_acnt.__dict__
#{'user':'greg', '_balance':5000}
my_acnt._balance = -3000 #이렇게 직접 지정하면 바뀜..
This post is licensed under CC BY 4.0 by the author.