Byeonguk Kim

안녕하세요. 29살의 조금은 늦은 나이로 새롭게 개발자로 시작하는 신입 개발자입니다. 포트폴리오 [https://deaguowl.github.io]

파이썬 14. 코드를 통해 보는 class

21 Mar 2019 » Python

2019.03.21 TIL

(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)

# 질문에 답하기.

  1. 아래의 코드를 따라가면서 #에 따라 답해보기
  2. 3.

코드를 통해 보는 class

>>>
>>>	def sum(a,b):			#1
>>>		return(a+b)
>>>
>>>	class Account:
>>>		
>>>		interest_rate = 0.08		#2		 
>>>		num_of_account = 0					
>>>		
>>>
>>>		@staticmethod				#3
>>>		def func(a,b):
>>>			return(a+b)
>>>
>>>		@classmethod					#4
>>>		def string_constructor(cls, string):
>>>			data = string.split('_')
>>>			clnt_name = data[0]
>>>			balance = int(data[1])
>>>			return cls(clnt_name, balance)
>>>
>>>		@classmethod						#5
>>>		def get_num_of_account(cls):
>>>			"""
>>>			Account.get_num_of_account() ---> interger
>>>			return : 개설된 계좌 수
>>>			"""
>>>			return cls.num_of_account
>>>
>>>		def __init__(self, clnt_name, balance):      #6
>>>			self.clnt_name = clnt_name              #7
>>>			self.balance = balance
>>>			Account.num_of_account += 1             
>>>
>>>		def deposit(self, money):                 #8
>>>			"""
>>>			a.deposit(money) --> boolean
>>>			만약에 money > 0 입금성공
>>>			아니면 에러 메시지 출력 후 실패!
>>>			"""
>>>			if money < 0:
>>>				print("입금은 0원 초과부터 가능합니다.")
>>>				return False
>>>			self.balance += money
>>>			return True
>>>
>>>		def withdraw(self, money):
>>>			"""
>>>			a.withdraw(money) --> integer
>>>			return : 인출된 돈
>>>			만약 잔고가 모자라면 None
>>>			"""
>>>			if money > self.balance:
>>>				print("인출할 돈이 잔액보다 많습니다.")
>>>				return None
>>>			self.balance -= money
>>>			return money
>>>
>>>		def transfer(self, other, money):           #9
>>>			self.balance -= money
>>>			other.deposit(money)
>>>
>>>		def __str__(self):                             #10
>>>			return f"{self.clnt_name} : {self.balance}"
>>>
>>>	if __name__ == "__main__"":
>>>		my_acnt = Account("greg", 5000)            #11
>>>		your_acnt = Account("john", 2000)
>>>	
>>>		print(Account.interest_rate)               #12
>>>		print(Account.get_num_of_account())        #13
>>>		print(my_acnt.interest_rate)               #14
>>>		print(my_acnt.get_num_of_account())        #15
>>>		print(Account.func(4,5))                   #16
>>>		print(type(Account.interest_rate))         #17
>>>		print(type(Account.get_num_of_account()))  #18
>>>		print(type(Account.get_num_of_account))    #19
>>>		print(type(my_acnt.deposit))               #20
>>>		print(type(Account.deposit))               #21
>>>		print(type(Account.func))                  #22
>>>
>>>		s = 'james_6000'                           #23
>>>		his_acnt = Account.string_constructor(s)
>>>		print(his_acnt)
>>>
>>>		my_acnt.deposit(7000)                      #24
>>>		print(my_acnt)
>>>
>>>		result1 = my_acnt.withdraw(3000)           #25
>>>		result2 = your_acnt.withdraw(3000)       
>>>		print(result1)
>>>		print(result2)
>>>
>>>		print(my_acnt)
>>>		print(your_acnt)
>>>
>>>
0.08
2
0.08
2
9
<class 'float'>
<class 'int'>
<class 'method'>
<class 'method'>
<class 'function'>
james : 6000
greg : 12000
인출할 돈이 잔액보다 많습니다.
3000
None
greg : 9000
john : 2000
>>>
>>>
>>>

#에 따라 정리하기

  1. #1 전역 함수
    • 어느 클래스에서도 속하지 않는다.
    • oop에서 제일 싫어하는 행동이다.
    • 최대한 관련있는 class에게 넣어주자.
  2. #2 클래스 멤버(class member)
    • 모든 객체가 공유한다.
    • 전역 변수(global variable)를 대체한다.
    • 클래스 바로 밑에 아무것도 적지 않고 적어주면 된다.
    • 모든 객체가 가지고 있을 필요는 없으나 모든 객체가 함께 공유해야 하는 것
      • ex: 이자율
    • 객체를 통해서도 클래스 멤버에 접근 가능하다.
    • 접근 방법 :
      • my_acnt.interest_rate : 객체를 통한 접근 방법
      • Account.interest_rate : 클래스를 통한 접근 방법
  3. #3 static method
    • 매서드처럼 보이지만 대놓고 함수이다. 전역함수
    • 전역 함수를 대체할 때 클래스메서드와 하나의 해결책이 될 수 있다.
    • 인자로 클래스나 객체를 받지 않는다. 함수의 정의만 클래스 A의 네임 스페이스에 있을 뿐 일반 함수와 같다.
  4. #4 클래스메서드(class method == @데코레이터)
    • 객체가 하나도 없는 상태에서도 호출이 가능하다.
    • cls가 self 대신 메모리 주소를 받는다.
    • 전역함수를 대체한다.
      • 전역함수를 대체할 때는 static method를 쓸 수 있다.
        • 아무것도 받지 않는다. 순수하게 함수다.
        • 사용방법(Account.func(5,4))
    • 관용적으로 cls를 받는다.
    • cls는 class를 받으므로 __init__ 와 같다.
  5. #5 클래스메서드(class method) + 대체 생성자
    • 객체가 하나도 없는 상태에서도 호출이 가능하다.
    • 전역 함수를 대체할 수 있다.
    • 원래 파이썬은 하나의 생성자만을 허용하지만 클래스메서드를 통해 해결 가능하다.
      • 대체 생성자(alternative constructor)가 될 수 있다.
      • 파이썬에서는 생성자를 하나만 가능하기때문에 여러가지 형태의 입력에 대응 할수 없다.
      • ex) 단체가입 “이름”_“초기값” —> s = ‘james_6000’
    • 접근방법
      • Account.get_num_of_account()
      • 객체를 통해서도 클래스 메서드에 접근이 가능하다.
  6. #6 생성자(constructor)
    • 파이썬에서는 오직 하나 존재한다.
    • __init__ : 객체(object)가 생성될 때 반듯이!! 한번 호출된다.
    • __ __ 은 파이썬에서 미리 쓰기로 해놓은 것이다.
  7. #7 인스턴스 멤버(instance member)
    • 인스턴스 멤버 == 상태정보 관리이다.
    • 각자의 객체가 가지는 값(값 = 상태정보)
    • __init__에 보면 변수가 나와 있다.
    • 생성된 object들은 같은 속성을 가지고 있으나 속성 값이 다르다.
    • Account.num_of_account —> class멤버에 접근하는 방법
  8. #8 인스턴스 매서드(instance method)
    • object의 메서드로 봐도 된다.
    • class 입장에서는 function/ object입장에서는 메서드이다.
    • 객체가 할 수 있는 행동, 기능을 이야기 한다.
    • 인스턴스 메서드는 self를 꼭 써줘야 한다.
    • self는 해당 객체가 저장된 첫번째 주소를 가르킨다.
      • 즉 self는 생성된 객체 자기 자신을 가르킨다.
      • my_account.deposit(5000)이 실행될 떄 my_account를 self로 던져준다. self는 객체의 메모리 주소를 참조한다. 즉 자체참조이다.
  9. #9 message passing(메시지 패싱)
    • 나의 상태 정보와 함께 다른 객체의 상태 정보를 함께 변경해야 할 때
    • 다른 객체의 상태 정보(인스턴스 멤버)를 변경할 때는 반드시 상대 객체가 가진 메서드를 이용해야 한다.
    • 절대 하면 안되는 예)
      • self.balance -= money
      • other.balance += money
      • 다른 객체가 가진 메서드를 이용하지 않았다.
  10. #10 __str__(self)
    • my_acnt의 반환 포맷을 적어준다.
    • 이게 없으면 __main__.Account object at 0x10f512438처럼 반환
  11. #11 객체 생성
    • my_acnt(변수) = Account(“greg”, 5000) ==> class(매개변수)
    • 변수 = class(매개변수) 형태로 생성
    • 해당 객체는 해당 class의 인스턴스이다.
  12. #12 클래스 멤버 접근 방법

  13. #13 클래스 메서드 접근 방법
    • 클래스 메서드는 클래스 입장에서는 메서드지만(클래스의 행동 특정)
    • class입장에서 deposit과 같은 것은 함수이다.(클래스에 영상 x)
    • 하지만 객체 입장에서 deposit은 메서드이다.
    • print(type(my_acnt.deposit)) — class ‘method’
    • print(type(Account.deposit)) — class ‘function’
  14. #14 객체를 활용한 클래스 멤버 접근 방법

  15. #15 객체를 활용한 클래스 메서드 접근 방법

  16. #16 static method 접근 방법

  17. #17 클래스 멤버의 type

  18. #18 클래스 메서드 실행 했을 때의 type —> int

  19. #19 클래스 메서드의 type

  20. #20 객체에서 메서드의 type

  21. #21 클래스에서 인스턴스 메서드의 type

  22. #22 static method의 타입

  23. #23 class method를 통해 대체 생성자를 만든 이유
    • 상황에 따라 전혀 예상하지 못한 형태의 값이 주어질 수도 있음
  24. #24 instance method 호출 방법(객체.method())
    • 객체에 self 인자는 넣어 줄 필요 없다.
    • class를 만들 때는 꼭 self를 써줘야 하지만 method를 호출하거나 객체를 생성할 때는 쓸 필요가 없다.
  25. #25 함수와 method의 차이점을 보여주는 예
    • 원래 withdraw라는 함수라면 입력이 3000원으로 같으므로 똑같이 나와야 한다.
    • 하지만 메소드는 내부에 가지고 있는 상태 정보가 다르기 때문에는 다르게 나온다.
    • 클로저에는 매개변수는 free variable이라고 부르는데
    • OOP에서는 인스턴스 멤버라고 부르는 것을 통해 상태정보를 관리한다.
    • 메서드 : 입력 + 인스턴스 멤버(상태정보, 데이터)에 의해 결과 값이 결정
      • 인스턴스 멤버(상태정보)를 바꾸는 역할을 하기도 한다.
    • 함수 : 입력에 의해 출력이 결정
      • 메서드처럼 사용하기 위해서는 call by reference 혹은 다른 변수에 할당
    • 메시지 패싱
      • 객체 간의 상호 작용 –> 객체가 가지고 있는 멤버 값(데이터, 상태 정보)의 변화
      • 내가 가진 건 1000이 적어지면 너에게 1000이 늘어난다.
      • interaction이 반드시 메서드에 의해 이루어져야 한다.
      • .은 속성을 가지고 온다는 뜻이다. 속성에는 인스턴스 멤버와 인스턴스 메서드가 있다.

절차지향 vs 객체지향

###procedural vs object-oriented

2개다 추상화를 하는 기법이다.

procedural : 함수를 이용해 추상화를 했다. 추상화의 도구로 함수를 이용 (절차지향)

  1. 이 프로그램은 어떤 일을 하는가?
  2. 기계와 비슷한 사고 방식
  3. 함수를 이용

object-oriented : 객체를 통해 추상화를 했다.

  1. 현실 세계(에 존재하는 객체(object)를 어떻게 모델링(modeling)할 것인가?
    1. 객체(object) : 사람, 동물, 자동차 등을 어떻게 프로그램으로 모델링 할 것인가?
  2. 사람과 비슷한 사고 방식
  3. 객체를 이용

객체 vs 인스턴스

메모리 상으로는 완벽히 같다. 하나를 볼 때는 객체로 보고 이 객체는 특정 클래스의 인스턴스라고 부른다.
why? 어떤 클래스에서 나왔는지 알고 싶을 때 - 클래스에 집중해서 말할 때

object란?

  1. 관련 있는 변수와 함수를 한곳에 모아(bundling) 놓은 곳(메모리의 영역)
    1. 변수 - 상태정보
    2. “관련 있는” 변수, 함수” “한곳에 모아” 가 중요하다.
  2. class를 통해 생성되므로 class의 속성을 가지고 있어야 한다.
  3. 속성
    1. instance member(변수)
      1. 각자의 객체가 가지는 값(값 = 상태정보)
      2. __init__에 보면 변수가 나와 있음
      3. 같은 속성을 가지고 있으나 속성 값이 다르다.
    2. instance method(함수)
      1. 객체가 할 수 있는 행동, 기능

—> 절차지향에서는 전체 프로그램을 다 봐야하지만.
—> 객체지향에서는 단위로 프로그램을 유추해볼 수 있다.

중요한 이해

클래스의 메모리와 객체의 메모리는 모두 다르다.
객체마다 모두 다른 메모리 공간을 가지고 있다.
각 객체가 인스턴스 멤버를 따로 가지고 있고 메서드 역시 각자 가지고 있다고 생각하는게 좋다.
my_acnt
balance / clnt_name / deposit / withdraw / transfer
your_acnt
balance / clnt_name / deposit / withdraw / transfer

미션 : 클래스를 쓰지 않고 OOP 구현

클래스를 쓰지 않고.
(클래스 멤버, 메소드 제외).
어떤 객체를 만들 수 있어야 하고
그 객체에 대해 함수를 호출하여 돈을 입금
init / deposit / withdraw / transfer.
하나의 unit을 딕셔너리로 고정한다.
class라는 유닛 대신에 딕셔너리로 묶어라.

def person_init(name, money):
    obj = {'name': name, 'money': money}
    obj['give_money'] = Person[1]
    obj['get_money'] = Person[2]
    obj['show'] = Person[3]
    print(obj)
    # --->    obj = {'name' : name, 'money' : money , 'give_money' : Person[1], 'get_money' : Person[2], 'show' : Person[3]}
    return obj


def give_money(self, other, money):  # 3
    self['money'] -= money
    other['get_money'](other, money)  # 4


def get_money(self, money):
    self['money'] += money


def show(self):
    print('{} : {}'.format(self['name'], self['money']))


Person = person_init, give_money, get_money, show

if __name__ == "__main__":
    # 객체 생성
    g = Person[0]('greg', 5000)  # 5
    j = Person[0]('john', 2000)

    g['show'](g)
    j['show'](j)
    print('')

    # 메시지 패싱
    g['give_money'](g, j, 2000)  # 6

    g['show'](g)
    j['show'](j)