인프런 - 프로그래밍 시작하기: 파이썬 입문(Inflearn Original) 강의를 수강하고 기억할 내용을 제 언어로 요약정리한 포스팅이라 표현 및 설명이 매끄럽지 않을 수 있습니다!

1. 강의정보

  • Chapter06-1
  • Python Class
  • OOP(객체 지향 프로그래밍), Self, Instance Method, Instance Variable


2. keywords

  • class(붕어빵기계) vs. instance(붕어빵) 차이 이해
  • namespace: 객체를 인스턴스화 할 때 저장된 공간 (각각 갖고 있는 자신만의 방)
  • 클래스 변수: 직접 접근 가능, 공유
  • 인스턴스 변수: 객체마다 별도 존재
  • Self: 인스턴스화 된 대상. 파이썬 코드에서 메모리에 올라가는 것
  • 클래스 메소드: 클래스로 직접 호출 (매개변수가 없기 때문)
  • 인스턴스 메소드: 인스턴스를 넘겨주거나 인스턴스로 호출 (self가 있기 때문)
  • __init__: 생성자
  • __del__: 소멸자


3. Examples

Example 1) 클래스 변수 vs. 인스턴스 변수 이해

class Dog:                  # object 상속
    # class 속성
    species = 'maltese'    # "클래스 변수"

    # 초기화/instance 속성
    def __init__(self, name, age):
        self.name = name    # self가 붙은 변수는 "인스턴스 변수"!
        self.age = age
  • 모든 class는 object를 상속받는다. 따라서 아래 세개의 표현 모두 가능. 어느 것을 써도 상관없다.
    • class Dog(object): object 상속받는다고 명시적으로 써준 것
    • class Dog()
    • class Dog
  • __init__
    • 모든 class가 초기화될 때 반드시 호출되는 함수
    • 즉, 모든 class는 초기화 메소드를 갖는다


클래스 정보 출력

print(Dog)
--------------------------------------------------
<class '__main__.Dog'>


인스턴스화

a = Dog('Mickey', 2)       # __init__ 메소드에서 넣어줬던 변수를 넣어준다.
b = Dog('Minnie', 3)
c = Dog('Mickey', 2)

# 비교
print(a == b, id(a), id(b), id(c))     
--------------------------------------------------
false 4409673088 4409672992 4409672848
  • 인스턴스 a와 인스턴스 c의 경우, 내용이 같아도 인스턴스끼리는 서로 다른 인스턴스!
    • 즉, 인스턴스화시킨 것은 서로 모두 다른 인스턴스!


namespace 확인

print('dog1', a.__dict__)
print('dog2', b.__dict__)
--------------------------------------------------
dog1 {'name': 'Mickey', 'age': 2}
dog2 {'name': 'Minnie', 'age': 3}
  • namespace: 객체를 인스턴스화할 때 저장되는 자신만의 공간
    • {인스턴스변수}.__dict__ 이용하여 namespace에 접근 가능
    • class가 갖고 있는 attribute 확인 가능
    • dictionary 형태로 가져다 쓸 수 있게끔 출력된다.


인스턴스 속성 확인

print('{} is {} and {} is {}'.format(a.name, a.age, b.name, b.age))
--------------------------------------------------
Mickey is 2 and Minnie is 3


if a.species == 'maltese':
    print('{0} is a {1}'.format(a.name, a.species))

print(Dog.species)  # 클래스로 직접 접근 가능
print(a.species)    # 인스턴스화된 변수로도 접근 가능
print(b.species)
--------------------------------------------------
Mickey is a maltese
maltese
maltese
maltese


Example 2) Self 이해

class  SelfTest:
    # 클래스 메소드
    def func1():
        print('Func1 called')

    # 인스턴스 메소드
    def func2(self):
        print('Func2 called')
  • Self: 인스턴스화 된 대상. 파이썬 코드에서 메모리에 올라가는 것
  • 위 예시 코드에서 __init__ 메소드가 없다!
    • 이렇게 __init__ 메소드가 없는 경우, 파이썬이 내부적으로 알아서 실행해준다
    • 위 코드에서는 전 예시의 name이나 age같은 속성이 필요없고 기본 클래스로 사용하기 위해 만들어주지 않은 것
  • 클래스 메소드
    • self가 없는 것은 클래스 메소드! 매개변수가 없기 때문에 클래스로 직접 호출한다
    • ex) SelfTest.func1()
  • 인스턴스 메소드
    • self가 붙은 것은 인스턴스 메소드! 인스턴스를 넘겨주거나 인스턴스로 호출해야 한다
    • ex) f.func2() or SelfTest.func2(f)


인스턴스화

f = SelfTest()

print(dir(f))
print(id(f))
--------------------------------------------------
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'func1', 'func2']
4343498496
  • dir(f) 출력시 맨 끝에 내가 만들어준 메소드인 func1(), func2()가 잘 들어가 있다!


func1(), func2()를 호출해보자.

f.func1()
--------------------------------------------------
TypeError: func1() takes 0 positional arguments but 1 was given
  • 에러 이유: self에 대한 인스턴스 값인 f을 넘겼는데 self가 없어서 f를 받을 수 없어서 예외 발생


f.func2()
--------------------------------------------------
Func2 called
  • self는 인스턴스를 요구한다. self에 인스턴스 f가 넘어간 것

      class  SelfTest:
    
      def func1():
          print('Func1 called')
    
      def func2(self):
          print(id(self))
          print('Func2 called')
        
      f = SelfTest()
    
      print(id(f))
      f.func2()
    
      --------------------------------------------------
      4343498496
      4343498496
      Func2 called
    
    • 클래스 내부 메소드 func2() 안에 넘어온 self의 id값과 클래스를 인스턴스화시킨 f의 id값이 같은 것을 볼 수 있다.


그럼 func1()은 어떻게 호출해야 할까? → 클래스로 바로 접근!

SelfTest.func1()
--------------------------------------------------
Func1 called
  • 클래스 메소드는 클래스로 바로 접근해서 호출할 수 있는 메소드
  • 인스턴스 메소드는 클래스로 인스턴스화 시킨 변수로 접근해서 호출할 수 있는 메소드


SelfTest.func2()
--------------------------------------------------
TypeError: func2() missing 1 required positional argument: 'self'
  • 에러 이유: 인스턴스화 시킨 변수를 넣어줘야 에러가 안 날 것!


SelfTest.func2(f)
--------------------------------------------------
Func2 called
  • 인스턴스화 시킨 변수를 넣어주니 에러가 나지 않았다.


Example 3) 생성자, 소멸자

class Warehouse:
    # 클래스 변수: 모두와 공유
    stock_num = 0   # 재고

    # 생성자
    def __init__(self, name):
        # 인스턴스 변수: 나만의 속성
        self.name = name
        Warehouse.stock_num += 1

    # 소멸자
    def __del__(self): 
        Warehouse.stock_num -= 1
  • __del__ 메소드: 객체가 소멸할 때 자동으로 호출되는 함수
    • Warehouse.stock_num += 1: 객체가 하나 만들어질 때 stock_num이 1 증가
    • Warehouse.stock_num -= 1: 객체가 하나 소멸할 때 stock_num이 1 감소


인스턴스화

user1 = Warehouse('Kim')
user2 = Warehouse('Lee')


이 시점에 어떠한 일이 일어났을까?

print(Warehouse.stock_num)
--------------------------------------------------
2
  • 두 번 인스턴스화 시켰으니까 2가 출력된다.


증명해보자

print(user1.name)
print(user2.name)

print(user1.__dict__)
print(user2.__dict__)

print(Warehouse.__dict__)
--------------------------------------------------
Kim
Lee
{'name': 'Kim'}
{'name': 'Lee'}
{'__module__': '__main__', 'stock_num': 2, '__init__': <function Warehouse.__init__ at 0x1069ad0d0>, '__del__': <function Warehouse.__del__ at 0x1069ad160>, '__dict__': <attribute '__dict__' of 'Warehouse' objects>, '__weakref__': <attribute '__weakref__' of 'Warehouse' objects>, '__doc__': None}
  • 'stock_num': 2로 바뀌어있는 것을 확인할 수 있다
  • stock_num은 모두와 공유한다는데 왜 print(user1.__dict__) 또는 print(user2.__dict__) 할 때는 'stock_num': 2가 나오지 않을까?
  • 그런데 아래와 같이 user1.stock_numuser2.stock_num을 출력해보면 2라고 답이 잘 나온다. stock_num 정보가 잘 저장되어 있는 것


print(user1.stock_num)
print(user2.stock_num)
--------------------------------------------------
2
2
  • 모두와 공유하는 정보이기 때문에 굳이 정보를 표시해주지 않은 것!
  • 인스턴스 namespace에 없는 정보라면, 공유하는 정보는 클래스의 namespaceWarehouse.__dict__에서 정보를 가져온다.


print('before:', Warehouse.stock_num) 

Warehouse.stock_num = 50
print('after:', Warehouse.stock_num)
--------------------------------------------------
before: 2
after: 50


클래스 변수에 직접 접근해서 수정도 가능. But, 좋은 방법은 아니다! 중요한 코드라면 더더욱 조심

print(Warehouse.__dict__)
--------------------------------------------------
{'__module__': '__main__', 'stock_num': 50, '__init__': <function Warehouse.__init__ at 0x1061314c0>, '__del__': <function Warehouse.__del__ at 0x106131550>, '__dict__': <attribute '__dict__' of 'Warehouse' objects>, '__weakref__': <attribute '__weakref__' of 'Warehouse' objects>, '__doc__': None}
  • 'stock_num': 50으로 바뀌어있는 것을 확인할 수 있다


__del__ 메소드 사용

del user1
print('after delete user1:', Warehouse.__dict__)
--------------------------------------------------
after delete user1: {'__module__': '__main__', 'stock_num': 49, '__init__': <function Warehouse.__init__ at 0x104c0e430>, '__del__': <function Warehouse.__del__ at 0x104c0e4c0>, '__dict__': <attribute '__dict__' of 'Warehouse' objects>, '__weakref__': <attribute '__weakref__' of 'Warehouse' objects>, '__doc__': None}
  • 'stock_num': 49로 바뀌어있는 것을 확인할 수 있다


Example 4) 연습

class Dog:
    species = 'maltese'

    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def info(self):
        return '{} is {} years old'.format(self.name, self.age)

    def bark(self, sound):
        return "{} barks {}!".format(self.name, sound)

# 인스턴스 생성
c = Dog('July', 4)
d = Dog('May', 10)

# 메소드 호출
print(c.info())
print(d.info())
print(c.bark('Woof Woof'))
print(d.bark('Woof!'))
--------------------------------------------------
July is 4 years old
May is 10 years old
July barks Woof Woof
May barks Woof!


Reference


Leave a comment