본문 바로가기
코딩 연습/코딩배우기

파이썬 실천 기술 #04 - 클래스와 인스턴스

by good4me 2021. 5. 26.

goodthings4me.tistory.com

 

효율적 개발로 이끄는 파이썬 실천 기술 - 클래스와 인스턴스

(참고용으로 사용할 부분만 간략하게 정리)

파이썬의 클래스 구조

  • 파이썬에서는 클래스도 객체이고, 클래스를 정의하면 그 이름의 클래스 객체가 만들어진다.
  • class 키워드 사용하여 실행 시 객체를 만들어낸다.
  • 같은 클래스의 여러 인스턴스(즉, 객체)는 같은 특성을 가지면서 서로 각각 독립된 상태를 유지한다.
# 클래스 정의 - Page라는 이름을 가진 객체(즉, 클래스 객체) 생성
# 이 클래스 객체는 그 자체로 새로운 인스턴스(객체)를 만들 수 있고, 
# 인스턴스는 클래스 정의의 내용에 기술된 메서드나 변수를 가지는데, 
# self.num 과 self.content 는 인스터스 변수이고, __init__() 와 output()는 인스턴스 메서드임
# 인스턴스 메서드의 첫 번째 인수에는 반드시 인스턴스 자신의 객체를 전달함(self)

class Page:
    def __init__(self, num, content):
        self.num = num  # 페이지 번호
        self.content = content  # 페이지 내용

    def output(self):
        return f'(self.conttent)'

print(Page)  # 클래스 객체 Page가 정의됨
print(type(Page))  # <class 'type'>
# type()은 인수로 전달받은 객체의 타입을 반환하는 기능과 클래스의 클래스 즉, 메타클레스(클래스 생성)임
# 예로,
print(type(int))  # <class 'type'>
a = 1
print(type(a))  # <class 'int'>
print(a.__class__)  # <class 'int'>
print(a.__class__.__class__)  # <class 'type'>

print(Page(1, 'my content'))  # 인스터스 생성 및 초기화
page = Page(10, 'my content')  # # 또 다른 인스터스 생성 및 초기화 후 변수 page에 대입(여기서 우리는 page를 보통 Page 클래스의 객체라 함)
print(page)  # page가 가리키는 인스턴스 출력
print(type(page))  # # page가 가리키는 인스턴스의 클래스 타입 확인 => type(Page(2, 'my content'))
print(isinstance(page, Page))  # 클래스의 인스턴스인지 확인


[결과]
<class '__main__.Page'>
<class 'type'>
<class 'type'>
<class 'int'>
<class 'int'>
<class 'type'>
<__main__.Page object at 0x7f8d3408ae50>
<__main__.Page object at 0x7f8d3408afd0>
<class '__main__.Page'>
True

 

# 인스턴스화 연습
title_page = Page(0, 'Python Practice Book')
print(type(title_page))  # 인스턴스의 클래스를 확인함
print(isinstance(title_page, Page))

count = 1
for i in dir(title_page):  # 인스턴스가 가진 속성 확인
    print(i, end=', ')
    count += 1
    if count % 7 == 0:
        print()

print()
print(title_page.__class__)  # == print(type(title_page))
print(type(title_page.__class__))  # == print(type(Page))


[결과]
<class '__main__.Page'>
True
__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__, content, 
num, output, 
<class '__main__.Page'>
<class 'type'>

 

# 메서드 객체와 함수 객체 
# 인스턴스 메서드와 함수는 기능상 차이 없고, 모두 function 클래스의 인스턴스로 구현될 수 있다

class Klass:
    def some_method(self):
        print('method')

print(type(Klass.some_method))  # <class 'function'>

def some_function(self):
    print('function')

print(type(some_function))  # <class 'function'>

# 인스턴스 속성으로 클래스 객체에 연결된 함수를 참조(인스턴스를 통해 메서드에 접근)하면 그 함수는 인스턴스 메서드로 변환(method 클래스)
kls = Klass()
print(type(kls.some_method))  # <class 'method'>

# 파이썬은 실행 시 속성 검색을 수행하므로 인스턴스 메서드를 동적으로 추가할 수 있으나 가독성 또는 유지보수성이 떨어지므로 권장하지 않는다
Klass.some_function = some_function 
print(kls.some_function())


[결과]
<class 'function'>
<class 'function'>
<class 'method'>
function
None

 

# 인스턴스 변수는 각 인스턴스가 독립적으로 가진다
title_page.section = 0
print(title_page.section)

first_page = Page(1, 'Python')
print(first_page.num)
#print(first_page.section)  # AttributeError: 'Page' object has no attribute 'section'


[결과]
0
1

good4me.co.kr

 

프로퍼티

  • 프로퍼티(property) : 인스턴스 메서드를 인스턴스 변수처럼 다루는 기능
  • @property 가 붙은 메서드는 값을 얻을 때 호출되기 때문에 getter 라고 하고,
  • @메서드.setter 가 붙은 것은 값을 대입할 때 호출되기 때문에 setter 라고 함
  • @property 가 붙은 인스턴스 메서드는 ()를 붙이지 않고도 호출할 수 있음
# 프로퍼티(property) 연습

class Book:
    def __init__(self, raw_price):
        if raw_price < 0:
            raise ValueError('price must be positive')
        self.raw_price = raw_price
        self._discounts = 0  # _discounts는 private 변수
    
    @property
    def discounts(self):  # getter
        return self._discounts
    
    @discounts.setter
    def discounts(self, value):  # setter
        if value < 0 or 100 < value:
            raise ValueError('discounts must be between 0 and 100')
        self._discounts = value
    
    @property
    def price(self):
        multi = 100 - self._discounts
        return int(self.raw_price * multi / 100)

book = Book(2000)
print(book.discounts)  # 초기 할인율 0
print(book.price)  # 초기 가격
book.discounts = 20  # 할인율 설정, setter 호출
print(book.price)  # 할인 후의 가격
#book.discounts = 120  # ValueError: discounts must be between 0 and 100


[결과]
0
2000
1600

 

# 언더스코어 두 개(__) 붙이면 name mangling 또는 name decoration

class Klass:
    def __init__(self, x):
        self.__x = x

kls = Klass(10)
#print(kls.__x)  # AttributeError: 'Klass' object has no attribute '__x'

# name mangling __x 속성 변환 규칙 : __x => _Klass__x 변환을 통해 참조할 수 있으나 지양함
print(kls._Klass__x)


[결과]
10

 

클래스 변수

  • 클래스에서 정의하는 것 : 인스턴스 변수, 인스턴스 메서드, 클래스 변수, 클래스 메서드
  • 클래스 변수나 클래스 메서드는 클래스 객체에서 참조할 수 있고, 인스턴스에서도 참조 가능(해당 클래스의 모든 인스턴스에서 공유)
  • 클래스 변수의 변경은 반드시 클래스 객체를 통해 대입해야 함 (인스턴스 통해 대입 시 클래스 변수는 변경되지 않고 그 인스턴스에만 존재하는 새로운 인스턴스 변수로 정의됨)
class Page:
    book_title = 'Python Practice Book'

print(Page.book_title)  # 클래스 객체로 접근
first_page = Page()
second_page = Page()
print(first_page.book_title)  # 인스턴스로 클래스 변수 참조
print(second_page.book_title)  # 클래스 변수는 모든 인스턴스에서 공유됨

Page.book_title = 'No title'  # 클래스 변수 업데이트
print(Page.book_title)
print(first_page.book_title)

first_page.book_title = 'Python Book'
print(Page.book_title)  # 클래스 변수 업데이트X
print(first_page.book_title)  # 인스턴스 변수 book_title 의 값(클래스 변수와 같을 경우, 인스턴스 객체의 속성이 먼저 검색됨)
print(second_page.book_title)  # 공유된 클래스 변수 계속 참조


[결과]
Python Practice Book
Python Practice Book
Python Practice Book
No title
No title
No title
Python Book
No title

 

클래스 메서드

  • 클래스에 속한 메서드로, 첫 번째 인수(cls)에 클래스 객체를 전달함
  • 데코레이터(@classmethod) 붙임
from operator import attrgetter

class Page:
    book_title = 'Python Practice Book'

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

    def output(self):
        return f'{self.content}'
    
    @classmethod
    def print_pages(cls, *pages):
        print(cls.book_title)  # 클래스 객체 이용
        pages = list(pages)
        for page in sorted(pages, key=attrgetter('num')):  # 페이지순으로 정렬해서 출력
            print(page.output())

first = Page(1, 'first page')
second = Page(2, 'second_page')
third = Page(3, 'third_page')

Page.print_pages(first, third, second)
print('=' * 10)
first.print_pages(first, third, second)


[결과]
Python Practice Book
first page
second_page
third_page
==========
Python Practice Book
first page
second_page
third_page

 

클래스 상속

  • class 클래스명(베이스 클래스) * 베이스 클래스 == 부모 클래스 또는 슈퍼 클래스
  • 서브 클래스(또는 자식 클래스)에서는 베이스 클래스가 가진 메서드를 그대로 이용하면서, 새로운 메서드나 변수를 추가할 수 있다
  • 필요에 따라 베이스 클래스가 가진 메서드를 덮어쓸 수 있다(메서드 오버라이드)
# 메서드 오버라이드와 super()를 사용한 베이스 클래스로의 접근 (베이스 클래스의 메서드 호출 시 내장함수 super() 이용)
# super()의 반환 객체는 프록시 객체(proxy object)이며, 그 클래스 자체는 아니다

class Page:
    def __init__(self, num, content):
        self.num = num
        self.content = content

    def output(self):
        print('Page_output')
        return f'{self.content}'

class TitlePage(Page):
    def output(self):  # 메서드 오버라이드
        print('TitlePage_output')
        title = super().output()  # 명시적으로 호출해야 함
        return title.upper()

title = TitlePage(0, 'Python Book')
title.output()  # 호출되는 순서는 출력 결과를 보고 확인!!


[결과]
TitlePage_output
Page_output
PYTHON BOOK

 

[참고/출처] 효율적 개발로 이끄는 파이썬 실천 기술 - 파이썬에 숨겨진 힘을 이용해 개발 효율을 높이자!

 

댓글