본문 바로가기
Study/Python

[Python #5] Class, Module, Package

by YOONAYEON 2021. 10. 31.
Class

 

: 클래스란 관련된 '속성'과 '동작'을 하나의 범주로 묶어 실세계의 사물을 흉내낸 것

 

- 객체지향의 가장 기본적인 개념이 클래스

- 스크립트 언어는 원래 가볍게 쓰는 것이 주목적이라 객체지향을 지원하는 경우가 드물지만 파이썬은 스크립트 언어임에도 클래스 정의, 연산자 오버로딩, 다중상속을 지원한다. (하지만 진짜 객체지향언어에 비해 형식성이 떨이지고 기능도 많이 부족)

- 사물을 분석하여 필요한 속성과 동작을 추출 -> 모델링

- 모델링된 결과를 클래스로 포장하는 것 -> 캡슐화

 

- 아래 코드예제는 계좌라는 실세계의 사물(Object)를 표현하는 하나의 묶음 : 연관된 정보는 한 곳에 모여있어야 관리가 용이

 

balance = 8000		# 잔액

def deposit(money):	# 입출금 동작
	global balance
    balance += money
    
def inquire():
	print("잔액은 %d " % balance)
    
deposit(1000)
inquire()		# 잔액은 9000

 

- 클래스로 정의

class Account:				#Account 클래스 정의, 필요한 동작의 함수 구현
	def __init__(self, balance):	# 잔액 속성
    	self.balance = balance
    def deposit(self, money):		# 입금
    	self.balance += money
    def inquie(self):			# 조회
        print('잔액은 %d' % self.balance)
  
obj = Account(8000)
obj.deposit(1000)
obj.inquire()			# 잔액은 9000

 

* 필요한 속성과 동작이 Account클래스로 캡슐화 됨 (클래스는 재사용 가능)

* 모든 것이 클래스 안에 포함되어 있으므로 Account클래스로부터 얼마든지 계좌 생성 가능

* 각 계좌 객체는 속성인 'balance'가 각자 따로 가지며, 'deposit', 'inquire' 메소드는 공유됨

* 기억장소가 분리되어 있어 고유의 값을 저장 가능

 

 

클래스 선언  & 생성자

 

class 이름:
    def __init__(self, 초기값):
        멤버변수 초기화
    메소드 정의

 

* 클래스 이름은 첫자를 대문자로 하는 것이 관행

* 클래스 선언문 안에 필요한 속성 변수와 동작 메소드를 나열함

* 첫번째 메소드는 객체를 초기화해주는 __init__ 생성자

* __init__의 첫 번째 인수인 self는 객체 자기자신을 의미하여 파이썬 인터프리터가 객체 생성시 자동으로 넣어줌

 

class Human:
	def __init__(self, age, name):
    	self.age = age
        self.name = name
	def intro(self):
    	print(str(self.age) + '세' + self.name + '입니다.')
  
kim = Human(23, '홍길동')	# 객체 생성
kim.intro()			# 23세 홍길동입니다.

 

* 객체를 생성한 후 객체를 생성자(__init__)의 첫번째 인수인 self로 전달

* 생성자는 객체 생성 직후에 호출되어 멤버 변수를 초기화하는 중요한 역할

 

 

 

상속

 

: 상속은 기존 클래스를 확장하여 멤버를 추가하거나 동작을 변경하는 방법

 

class 이름(부모):
    ....

 

* 비슷한 클래스가 있다면 처음부터 다시 만들필요 없이 Base class를 상속받아 약간씩 확장 및 변형하여 사용

* 상속할 때는 클래스 이름 다음 괄호 안에 부모클래스의 이름을 지정

* 새로 정의되는 자식클래스는 부모클래스의 모든 멤버를 물려받음

* 물려받은 후에 추가로 멤버변수를 더 정의하거나 동작을 수정 가능

 

class Student(Human):				# Human 상속받음
	def __init__(self, age, name, stunum):
		super().__init__(age, name) 	# age,name의 초기화는 부모클래스의 초기화와 똑같으므로
		self.stunum = stunum 		# 멤버를 추가로 선언
        
	def intro(self): 			# 정의하지 않아도 부모메소드 사용가능(but 학번출력X)
		super().intro()
		print("학번 : " + str(self.stunum))
        
	def study(self): 			# 메서드 추가로 정의
		print("파이썬공부중")

 

* super() : 부모클래스의 object 리턴

* 한 클래스로부터 상속되는 자식클래스의 개수와 깊이에 제한없음 (Human으로부터 Student, Teacher 등 많은 클래스 파생가능)

* 파이썬은 다중상속(여러개의 부모클래스를 가지는 것)도 가능하지만 프로그램을 복잡하게 만드므로 가급적 사용안하는 것이 좋음

 

 

액세서

 

: 객체의 안정성을 확보하기 위해 정보 은폐 기능으로써 별도로 멤버에 access하는 방법

 

* Java/c++ 같은 객체지향언어에 비해 파이썬은 공식적으로 정보 은폐를 지원하지 않음

* 따라서 파이썬 클래스의 멤버는 모두 공개되어 있어 외부에서 누구나 액세스 할 수 있음

* 클래스의 멤버를 외부에서 마음대로 조작하게 내버려 두는 것은 위험하므로 일정한 규칙으로 안전하게 액세스해야 함

 

방법 1) Getter, Setter정의

 

class Date:
	def __init__(self, month):
    		self.month = month
	def getmonth(self):
		return self.month
	def setmonth(self, month):
		if 1 <= month <= 12: 	# 1 ~ 12 사이의 유효한 값만 받아들이고 그 외는 무시
			self.month = month
            
today = Date(11)
today.setmonth(15) 	# 이상한 호출
print(today.getmonth())	 # 11 (객체가 이상한 값을 가지지 않음)

 

 

방법 2) property(getter, setter) 형식으로 속성 정의

 

class Date:
	def __init__(self, month):
    		self.s_month = month	# 실제멤버는 밖에서 알기 어려운 이름으로 정의
	def getmonth(self):
		return self.s_month
	def setmonth(self, month):
		if 1 <= month <= 12: 	
			self.s_month = month
            
    # month property를 통해 내부 멤버를 액세스하는 getter, setter와 연결        
	month = property(getmonth, setmonth)            
            
today = Date(11)
today.setmonth(15) 	
print(today.getmonth())	 # 11

 

 

방법 3) 데코레이터로 property 정의

 

class Date:
	def __init__(self, month):
		self.s_month = month
	# getter는 @property
	@property 				
	def month(self): 	
		return self.s_month
        
        # setter는 @이름.setter    
	@month.setter 
		def month(self, month):		# 함수이름은 위와 동일
			if 1 <= month <= 12:
				self.s_month = month
                
today = Date(11)
today.month = 15
print(today.month)	#11

 

 

방법3) __로 시작하는 멤버변수 이름

 

class Date:
	def __init__(self, month):
		self.__month = month 	# 숨겨진 멤버의 이름을 __로 시작하면 바로 참조가 불가능
	def getmonth(self):
		return self.__month
	def setmonth(self, month):
		if 1 <= month <= 12:
			self.__month = month
            
month = property(getmonth, setmonth)
today = Date(10)
today.__month = 7 # 직접 대입으로 변경되지 않는다
print(today.month)

 

* __가 붙으면 내부적인 실제이름은 '__클래스명__멤버명' 이 됨

   ex) '__month' 로 쓰면 실제 이름은 '_Date__month' 가 된다 

* 사용자가 숨겨진 이름을 알아내 실수로 대입할 위험을 막을 수 있음

* 물론 이 숨겨진 이름도 직접 대입하면 변경은 가능하나 의도치 않은 실수는 막을 수 있음

 

-> 너무 복잡한 방법까지 쓸 필요는 없고 getter, setter정도만 잘 작성해도 어느 정도 안전!

 

 

클래스 메서드

 

: 특정 객체에 대한 작업을 처리하는 것이 아니라 클래스 전체가 공유

 

* 일반적인 메소드는 객체에 소속되는 인스턴스 메소드 (첫번째 인수 self가 해당 객체에 대한 작업 수행)

* 클래스 메소드는 클래스 자체를 나타내는 cls 인수를 받아들임

* 함수 앞에 '@classmethod' 데코레이터를 붙이고 첫번째 인수로 cls 인수 작성

 

class Car:
	count = 0 			# Car클래스 소속변수: 특정 객체에 소속되지 않고 모든 객체가 공유
	def __init__(self, name):
		self.name = name
		Car.count += 1 		# 차 생성시마다 1씩 증가
       
	@classmethod
	def outcount(cls):	 	# 클래스 전체의 공유 값인 count를 출력
		print(cls.count)
        
ferrari = Car("페라리")
lamborghini = Car("람보르기니")
Car.outcount()				# 2

 

* 클래스 멤버는 모든 객체에 의해 공유되므로 각 객체에서도 참조 가능, 그러나 가독성위해 클레스 메소드는 위처럼 작성 권장

   ex) print(ferrari.count) 해도 같은 결과

 

 

 

연산자 메소드

 

: 클래스에 연산자 메소드를 정의하면 객체에 대해서도 연산자를 사용 가능

 

* 클래스 별로 연산자의 동작을 고유하게 정의하는 기능을 연산자 오버로딩이라고 함

* 피연산자의 타입에 따라 적절한 동작을 정의해 두면 객체를 수식에 바로 활용가능해 편리

* 연산자는 기호로 되어있어 함수의 이름으로 쓸 수 없음 -> 연산자 별로 메소드 이름이 정해져 있음

 

연산자 메소드 우변일 때의 메소드
== __eq__  
!= __ne__  
< __It__  
> __gt__  
<= __le__  
>= __ge__  
+ __add__ __radd__
- __sub__ __rsub__
* __mul__ __rmul__
/ __div__ __rdiv__

 

* 보통 객체가 좌변에 오지만 우변에 올 때는 앞에 r이 붙은 함수명을 사용

* 교환법칙이 성립하는 연산자는 우변일 때의 연산자 메소드가 필요없음

 

- 예시

class Human:
	def __init__(self, age, name):
		self.age = age
		self.name = name
	def __eq__(self, other): # 두 객체가 같은지 비교. 비교 내용은 클래스별로 다름
		return self.age == other.age and self.name == other.name
        
hong = Human(23, "홍길동")
dong = Human(23, "홍길동")
lee = Human(22, "이몽룡")
print(hong == dong)		# True
print(hong == lee)		# False

 

* 연산자는 함수에 비해 호출구문이 간단하고 의미가 분명해 사용하기 쉬움

* 사람끼리 더하거나 곱하는 것도 가능하지만 논리적으로 합당한 의미가 있어야 함

 

특수 메소드

 

: 특정한 구문에 객체가 사용될 때 미리 약속된 작업을 수행. 메소드를 정의해두면 필요할때 자동으로 호출되어 원하는 작업 수행

 

메소드 설명
__str__ str(객체) 형식으로 객체를 문자열화한다
__repr__ repr(객체) 형식으로 객체의 표현식을 만든다
__len__ len(객체) 형식으로 객체의 길이를 조사한다

 

- 예시

class Human: 
	def __init__(self, age, name):
		self.age = age
		self.name = name
	def __str__(self):
		return "이름 : %s, 나이 : %s" % (self.name, self.age)
        
hong = Human(23, "홍길동")
print(hong)			# 이름 : 홍길동, 나이 : 23

 

 

Module

 

: 모듈은 파이썬 코드를 저장하는 기본 단위

 

* 해석기는 실행 중인 모든 변수의 값을 저장하고 변화를 추적하지만 해석기를 재시작하면 변수,함수의 정의가 사라짐

* 이 때문에 명령행보다 파일 형태로 작성하여 일괄 실행하는 스크립트(소스 코드)를 사용

* 그러나 스크립트도 너무 길면 편집이 귀찮고 공동작업이 힘드므로 기능 별로 여러개의 파일로 나누어 작성

* 이때 나누어진 스크립트 파일 하나를 모듈 이라고 함

* 파이썬은 자주 사용하는 기능을 표준 모듈로 제공 (standard library)

* 자주 사용하는 기능을 모듈화해두면 중복작업을 방지하고 재사용하기 편리

* 모듈을 import하여 마치 자기가 정의한 것처럼 사용가능

 

import util 		# 같은 디렉토리에 있는 util 모듈을 불러옴
# 변수나 함수를 참조할 때는 모듈명을 앞에 붙여 호출
print("1inch = ", util.INCH)
print("~ 10 = ", util.calcsum(10))

 

 

Package

 

: 모듈을 관리하기 위한 상위 개념

 

* 대규모의 프로젝트 수행 시 모듈의 수가 많아지고 관리에 어려움이 따름

* 그래서 모듈을 관리하기 위한 상위의 패키지라는 개념이 필요

* 모듈이 파이썬 코드를 담는 파일이라면, 패키지는 모듈을 담는 디렉토리

* 디렉토리로 계층을 구성해 놓으면 모듈을 기능에 따라, 개발 주체에 따라 체계적으로 관리할 수 있고 이름 충돌도 피할 수 있음

 

 

외부모듈 설치

 

pip 명령 패키지명

 

명령 설명
install 패키지를 설치한다
uninstall 설치한 패키지를 삭제한다
freeze 설치한 패키지의 목록을 보여준다
show 패키지의 정보를 보여준다
search pyPI에서 패키지를 검색한다

'Study > Python' 카테고리의 다른 글

[Python #7] Multiple Connections, Web Scrapping  (0) 2021.12.14
[Python #6] Network  (0) 2021.11.24
[Python #5] Dictionary, Set, Collection  (0) 2021.10.10
[Python #4] File  (0) 2021.10.07
[Python #3] String, List & Tuple  (0) 2021.10.05