파이썬 중급 - Python Class 예제 및 사용법
in Development on Python
- Python Class 예제 및 Python Class 사용법에 대해 작성한 글입니다
- 키워드 : Python Class, Python Class Example, Python Class Use Case
- 목차
Python Class
- 파이썬을 처음 공부하면 함수나 라이브러리 import 등은 나름 수월하게 진행함(훈련을 통해서)
- 단, Class 부분부터 약간 어려움이 존재할 수 있음
- 붕어빵 이야기, 붕어빵 틀 이야기 등으로 쉽게 설명을 들어도 이해가 안되었던 경험이 있음
- 이번에 하나씩 정리하려는 목적으로 글을 작성
- 쥬피터 노트북 등을 사용해서 처음 파이썬을 익히면 일종의 흐름 단위로 프로그래밍을 함
객체 지향 프로그래밍
- Object-Oriented Programming
- 프로그램을 단순히 데이터와 처리 방법으로 나누지 않고, 프로그램을 객체 단위로 생각하고 객체끼리 상호작용을 표현하는 방식
- 코드의 재사용, 코드 중복 방지, 유지보수 등을 위해 사용함
- 처음엔 함수 중심으로 개발할 수 있지만, 점점 구조가 복잡해지면 개선이 어려움
- 항상 객체 지향이 최선은 아니며 프로젝트의 규모 등에 따라 다름
- 객체 지향 프로그래밍 외에 절차 지향 프로그래밍도 존재
절차 지향 프로그래밍 예시
- 절차 지향 프로그래밍
- 순차적으로 진행되는 프로그래밍
- Jupyter Notebook에서 라인 바이 라인으로 실행하는 경우 절차 지향으로 볼 수 있음
- 먼저 절차 지향 방식으로 구현하고, 점점 객체 지향으로 구현해보기
- 일반적인 코딩
- 객체 없이 그냥 변수를 생성
- 추가 요소를 만들 때는 숫자와 값만 변경하고 계속 증가시킴
- 스마트폰과 스마트폰 정보를 출력하려면 두 변수를 같이 프린트해야 함
smartphone_1 = 'Iphone' smartphone_information_1 = [ {'color' : 'White'}, {'price': 10000} ] print(smartphone_1) print(smartphone_information_1)
- Dictionary를 사용해 스마트폰과 스마트폰 정보를 저장하는 방법
- 딕셔너리의 Key는 중복 불가능한 문제가 존재
- 정렬 문제 => OrderedDict을 사용하면 해결
- Key가 만약 없을 경우 예외 처리를 해야 함 => DefaultDict을 사용하면 해결
- 스마트폰, 스마트폰 정보를 출력할 경우엔 딕셔너리만 출력하면 됨
smartphone_dicts = [ {'brand': 'Iphone', 'smartphone_information': {'color' : 'White', 'price': 10000}}, {'brand': 'Galaxy', 'smartphone_information': {'color' : 'Blue', 'price': 8000}}, ] print(smartphone_dicts)
객체 지향 프로그래밍 살펴보기
- 코드의 중복 사용이 최소화되서 재사용성이 증가
- 클래스의 매직 메소드를 사용할 수 있음
- 네임스페이스
- 프로그래밍 언어에서 객체를 구분할 수 있는 공간
- 변수를 객체에 할당하면, 해당 객체는 딕셔너리 형태로 연결되서 네임스페이스에 저장됨
- 파이썬의 클래스는 새로운 타입(객체)을 정의하기 위해 사용되고, 모듈과 마찬가지로 하나의 네임스페이스를 가짐
globals()
를 사용하면 전역 네임스페이스를 확인할 수 있음- 함수는 전역과 별개의 네임스페이스를 가짐
temp_variable = a print(globals())
__init__
: 클래스 인스턴스 생성시 초기화하며 실행되는 부분- class의 값을 보고 싶으면
__dict__
을 사용 - python class magic method - str
__str__
매직 메소드가 구현되어 있지 않은 상태에서 인스턴스를 print하면 object가 나옴- print() 또는 str() 함수가 호출될 때 사용 - 기본적으로 str 메소드가 먼저 실행되며, str 메소드가 없으면 repr 메소드를 실행함
- python class magic method - repr
__repr__
: str과 비슷- 개발, 엔지니어 레벨에서 객체의 엄격한 타입을 표현시 사용
- 객체 표현을 반환함
- repr() 함수가 호출될 때 사용
- python class magic method - dir
- 해당 인스턴스가 가진 모든 attribute를 list 형태로 보여줌(값을 보여주진 않음)
- python class magic method - dict
__dict__
- 특정 네임스페이스만 보고 싶다면,
__dict__
를 사용한 후, 특정 네임스페이스에 접근하면 됨
- python class magic method - doc
__doc__
- docstring을 출력
class Smartphone:
"""
Smartphone class
"""
def __init__(self, brand, informations):
self._brand = brand
self._informations = informations
def __str__(self):
return f'str : {self._brand} - {self._informations}'
def __repr__(self):
return f'repr : {self._brand} - {self._informations}'
Smartphone1 = Smartphone('Iphone', {'color' : 'White', 'price': 10000})
Smartphone2 = Smartphone('Galaxy', {'color' : 'Black', 'price': 8000})
print(Smartphone1)
print(Smartphone1.__dict__)
print(Smartphone1._brand == Smartphone2._brand)
print(Smartphone1 is Smartphone2)
print(Smartphone.__doc__)
- self란?
- self는 인스턴스 자기 자신을 뜻함
- 인스턴스 : 클래스에 의해 만들어진 객체
- self가 있는 것이 인스턴스 변수
- 인자에 self를 받는 것은 인스턴스 메소드 : get_information 함수
- self는 인스턴스 자기 자신을 뜻함
- 인스턴스의
__class__
는 사용된 클래스를 출력함
class Smartphone:
"""
Smartphone Class
"""
def __init__(self, brand, details):
self._brand = brand
self._infomations = infomations
def __str__(self):
return f'str : {self._brand} - {self._ infomations}'
def __repr__(self):
return f'repr : {self._brand} - {self._ infomations}'
def get_infomation(self):
print(f'Current Id : {id(self)}')
print(f'Smartphone Detail Info : {self._brand} {self._infomations.get("price")}')
Smartphone1 = Smartphone('Iphone', {'color': 'White', 'price': 10000})
Smartphone2 = Smartphone('Galaxy', {'color': 'Black', 'price': 8000})
Smartphone1.detail_info()
print(Smartphone1.__class__, Smartphone2.__class__)
# 부모는 같음
print(id(Smartphone1.__class__) == id(Smartphone2.__class__))
- 클래스 변수
- 클래스 내부에 선언된 변수
- 클래스 변수는 클래스의 네임스페이스에 위치함
- 모든 인스턴스가 공유하는 변수
Smartphone1.__dict__
를 출력하면 클래스 변수는 보이지 않음dir(Smartphone1)
를 출력할 때는 클래스 변수가 보임
- 인스턴스 변수
- self.name 같이 self가 붙은 변수
- 인스턴스 변수는 인스턴스의 네임스페이스에 위치함
- 인스턴스 네임스페이스에서 없으면 상위에서 검색
- 즉, 동일한 이름으로 변수 생성 가능(인스턴스 검색 후 => 상위 클래스, 부모 클래스 변수)
class Smartphone:
"""
Smartphone Class
"""
# 클래스 변수
smartphone_count = 0
def __init__(self, brand, infomations):
self._brand = brand
self._infomations = infomations
Smartphone.smartphone_count += 1
def __str__(self):
return f'str : {self._brand} - {self._infomations}'
def __repr__(self):
return f'repr : {self._brand} - {self._infomations}'
def infomation(self):
print(f'Current Id : {id(self)}')
print(f'Smartphone Detail Info : {self._brand} {self._infomations.get('price'))}'
def __del__(self):
Smartphone.smartphone_count -= 1
Smartphone1 = Smartphone('Iphone', {'color' : 'White', 'price': 10000})
Smartphone2 = Smartphone('Galaxy', {'color' : 'Black', 'price': 8000})
print(Smartphone.__dict__)
print(Smartphone1.__dict__)
print(Smartphone2.__dict__)
print(dir(Smartphone1))
print(Smartphone1.smartphone_count)
print(Smartphone.smartphone_count)
메소드의 종류
- 1) 클래스 메소드(Python Class Method)
- @classmethod 데코레이터를 사용
- cls 인자를 받음
- cls는 Smartphone를 뜻함(인스턴스 말고 클래스)
- 클래스 변수 컨트롤할 때 사용
- 2) 인스턴스 메소드(Python Instance Method)
- Self가 들어간 경우
- 객체의 고유한 속성 값을 사용
- 3) 스태틱 메소드(Python Static Method)
- 아무것도 인자를 받지 않음(self, cls 등)
- Meaning of @classmethod and @staticmethod for beginner? 참고
class Smartphone:
"""
Smartphone Class
"""
Smartphone_count = 0
def __init__(self, brand, informations):
self._brand = brand
self._informations = informations
Smartphone.smartphone_count += 1
def __str__(self):
return f'str : {self._brand} - {self._informations}'
def __repr__(self):
return f'repr : {self._brand} - {self._informations}'
def detail_info(self):
print(f'Current Id : {id(self)}')
print(f'Smartphone Detail Info : {self._brand} {self._informations.get('price'))}'
def get_price(self):
return f'Before Smartphone Price -> brand : {self._brand}, price : {self._informations.get('price')}'
def get_price_culc(self):
return f'After Smartphone Price -> brand : {self._brand}, price : {self._informations.get('price') * Smartphone.price_per_raise}'
@classmethod
def raise_price(cls, per):
if per <= 1:
print('Please Enter 1 or More')
return
cls.price_per_raise = per
return 'Succeed! price increased.'
@staticmethod
def is_iphone(inst):
if inst._brand == 'Iphone':
return f'OK! This Smartphone is {inst._brand}.'
return 'Sorry. This Smartphone is not Iphone.'
Smartphone1 = Smartphone('Iphone', {'color' : 'White', 'price': 10000})
Smartphone2 = Smartphone('Galaxy', {'color' : 'Black', 'price': 8000})
# 기본 정보
print(Smartphone1)
print(Smartphone2)
Smartphone1.information()
Smartphone2.information()
print(Smartphone1.get_price())
print(Smartphone2.get_price())
# 가격 인상(클래스 메소드 미사용)
# 이렇게 직접 접근은 좋지 않아요
Smartphone.price_per_raise = 1.2
# 가격 정보
print(Smartphone1.get_price_culc())
print(Smartphone2.get_price_culc())
# 가격 인상
Smartphone.raise_price(1.6)
# 가격 정보
print(Smartphone1.get_price_culc())
print(Smartphone2.get_price_culc())
def is_iphone(inst):
if inst._brand == 'Iphone':
return f'OK! This Smartphone is {inst._brand}.'
return 'Sorry. This Smartphone is not Iphone.'
print(is_iphone(Smartphone2))
print('Static : ', Smartphone.is_iphone(Smartphone1))
print('Static : ', Smartphone.is_iphone(Smartphone2))
상속
- Class는 상속을 통해 자식 클래스에게 부모 클래스의 속성과 메소드를 물려줌
- 예를 들어 Smartphone Class가 있고, Iphone Class, Galaxy Class 등이 있는 상황
- Iphone과 Galaxy가 가치는 속성(attribute)는 다를 수 있음
- 다중 상속도 가능함
class Smartphone:
def __init__(self, brand, price):
self._brand = brand
self._price = price
def __str__(self):
return f'str : {self._brand} - {self._price}'
class Galaxy(Smartphone):
def __init__(self, brand, price, country):
self._brand = brand
self._price = price
self._country = country
def __str__(self):
return f'str : {self.__class__.__name__} 스마트폰은 {self._brand}에서 출시되었고, {self._country}에서 생산되었습니다. 가격은 {self._price}입니다'
iphone = Smartphone('IPhone', 7000)
print(iphone)
galaxy = Galaxy('Galaxy', 5000, 'South Korea')
print(galaxy)
Setter와 Getter, Property
- 객체의 속성(attribute)를 다룰 때 사용
- getter, setter는 객체의 속성을 읽고 변경할 때 사용함
- 참고로 객체(Object)는 속성(Attribute)와 Method로 구현
- 자바 같은 객체 지향 언어에서 외부에서 바로 접근할 수 없는 private 객체 속성을 지원함
- 이런 언어에선 private 속성의 값을 읽고(get) 변경(set)하기 위해 getter와 setter를 사용함
- 파이썬은 모든 메서드가 public이기 때문에 getter와 setter 메소드가 없지만, 사용할 수는 있음
class Smartphone:
def __init__(self, brand, price):
self._brand = brand
self._price = price
def get_price(self):
return self._price
def set_price(self, price):
self._price = price
- property
- 파이썬은 속성에 직접 접근을 막기 위해 property를 사용함
- 데코레이터로 감싸서 사용함
class Smartphone:
def __init__(self, brand, price):
self._brand = brand
self._price = price
@property
def price(self):
return self._price
@price.setter
def price(self, price):
print(f"변경 전 가격 : {self._price}")
self._price = price
print(f"변경 후 가격 : {self._price}")
Smartphone1 = Smartphone("Iphone", 1000)
Smartphone1.price = 10000
- property를 사용하면 value의 제한을 들 수 있음
- 예를 들면, Smartphone class에서 가격이 0원 미만일 경우 에러를 발생시킬 수 있음(property가 아니고 get, set으로 구현하면 에러가 발생하진 않음)
- 사용하는 목적
- 변수를 변경할 때 제한사항을 두고 싶은 경우
- getter, setter 함수를 만들지 않고 간단히 접근하기 위함
class Smartphone:
def __init__(self, brand, price):
self._brand = brand
self._price = price
@property
def price(self):
return self._price
@price.setter
def price(self, price):
if price < 0:
raise ValueError("Price below 0 is not possible")
print(f"변경 전 가격 : {self._price}")
self._price = price
print(f"변경 후 가격 : {self._price}")
Smartphone1 = Smartphone("Iphone", 1000)
Smartphone1.price = 10000
Smartphone1.price = -1000
추상 메소드
- Class를 만들었다면 Class에서 정의된 메소드를 그대로 사용하지 않을 수 있음
- 상속받고, 어떤 메소드는 추가하고 어떤 메소드는 오버라이드할 수 있음
- 통일된 Class 체계를 구축하며 확장 기능을 가능하게 만드는 것이 Class
- 이런 개념을 토대로, Class를 만들 때 반드시 구현해야 할 메소드를 명시할 수 있음
- 아래 코드는 Sonata에서 func1 함수를 구현하지 않아 오류가 발생함
class Smartphone:
def func1(cls):
raise NotImplementedError()
class Iphone(Smartphone):
pass
iphone = Iphone()
iphone.func1() # Error 발생
- 조금 더 Strict하고 세련된 방법은
@abc.abstractmethod
를 사용하는 방법- abc는 abstract base class의 약자
- Class의 인자로 metaclass=abc.ABCMeta를 지정하고,
- Class에 데코레이터로 abc.abstractmethod를 사용하면, class가 호출되는 순간 구현해야 하는 메소드가 구현되었는지를 확인함
- 참고 : metaclass는 단순히 클래스를 만들 때 사용됨. 클래스의 클래스
- 바로 에러가 발생하기 때문에 인스턴스화시키지 않고, 추상화에 이점이 존재
import abc
class Smartphone(metaclass=abc.ABCMeta):
@abc.abstractmethod
def func1(cls):
raise NotImplementedError()
class Iphone(Smartphone):
def func2(self):
pass
iphone = Iphone() # Error 발생
slots
- 파이썬의 클래스들은 인스턴스 속성을 저장함
- 파이썬에서 객체의 인스턴스 속성을 저장하기 위해 dictionary를 사용함
- 이런 방식은 속성을 추가하거나 삭제할 때 유용함
obj.__dict__
으로 접근하는 경우
- 그러나 dictonary 자료 구조는 메모리를 많이 낭비함
- 파이썬 객체 생성시 모든 속성을 메모리에 할당하려고 함
- 메모리 할당량이 많으면 많은 RAM을 낭비하게 됨
- 이런 문제를 해결하기 위해
__slots__
를 사용해 특정 속성에만 메모리를 할당하도록 함 ipython memory usage를 사용하면
__slots__
를 사용해서 메모리 사용량이 얼마나 개선되는지 확인할 수 있음__slots__
없이 짠 코드
class Smartphone:
def __init__(self, brand, price):
self._brand = brand
self._price = price
self.set_up()
# 코드 생략
# set_up은 init시 실행하는 함수라고 생각
__slots__
를 사용해 짠 코드
class Smartphone:
__slots__ = ['_brand', '_price']
def __init__(self, brand, price):
self._brand = brand
self._price = price
self.set_up()
# 코드 생략
# set_up은 init시 실행하는 함수라고 생각
- 추후 추가적으로 다룰 이야기
- MetaClass
카일스쿨 유튜브 채널을 만들었습니다. 데이터 사이언스, 성장, 리더십, BigQuery 등을 이야기할 예정이니, 관심 있으시면 구독 부탁드립니다 :)
PM을 위한 데이터 리터러시 강의를 만들었습니다. 문제 정의, 지표, 실험 설계, 문화 만들기, 로그 설계, 회고 등을 담은 강의입니다
이 글이 도움이 되셨거나 다양한 의견이 있다면 댓글 부탁드립니다 :)