Python 클래스 개념

Python의 class 개념에 대한 정리

[참고] 파이썬 - 기본을 갈고 닦자! (https://wikidocs.net/book/1553)


Class, Instance, Method, Attribute

# 메소드 작성하기
class Flight:

	# 클래스 속성: class 안에서 바로 할당하는 속성 (__init__에서 할당했던 변수들은 모두 인스턴스 속성)
	# 클래스 속성에서의 직접 접근, 객체에서의 접근할 때 모두 똑같은 값을 공유함
	class_attr = []

	# 생성자(constructor)로 객체 생성을 호출받으면 먼저 __new__를 호출하여 객체를 생성할당하고, __new__ 메소드가 __init__ 메소드를 호출하여 객체에서 사용할 초기값 초기화
	# __init__(): 메소드만 오버라이딩하여 객체초기화에만 이용할 뿐 생성자는 아님
	def __init__(self, number):
		# super().__init__()

		# 초기화자(__init__)에 객체의 불변성 확립 (유효성 검증 수행)
		# 객체 생성시 들어올 값에 대해 __init__에서 validation 수행
		# e.g. 비행기의 번호는 앞 두글자는 영문이어야하고 대문자이며, 뒤 세번째 글자부터 마지막까지는 양의 정수
		# 객체 생성시 규칙에 맞지 않는 값이 들어오면 ValueError 발생시키기
		if not number[:2].isalpha():
			raise ValueError("첫 두글자가 알파벳이 아닙니다.")
		if not number[:2].isupper():
			raise ValueError("첫 두글자가 대문자가 아닙니다.")
		if not number[2:].isdigit():
			raise ValueError("세 번째 글자 이상이 양의 숫자가 아닙니다.")

		# `_` 네이밍 컨벤션 (_single_leading_underscore: 내부적으로 사용되는 변수)
		# [참고] Python은 기본적으로 다른 언어와 달리 접근 제어자(public, private, protected)가 없고, 모두 public (생성된 인스턴스에서 _number 변수 접근 가능)
		# self._number = number
		# [참고] 변수 왼쪽에 __(더블스코어)를 붙인 경우 (Mangling): 선언된 클래스 안에서만 해당 이름으로 사용 가능
		# 외부에서 접근이 불가능하지만, `_클래스명__변수명` 형식으로는 접근 가능
		# e.g. >>> f = Flight("AB001")
		#      >>> print(f._Flight__number)
		#      'AB001'
		self.__number = number

	# 객체 생성 시 __new__가 클래스 자체를 받으며 할당하게 되고, __init__가 self를 받으며 객체의 내부에서 사용할 속성 초기화
	# __new__ 메소드는 자동으로 실행되므로 굳이 작성하지 않아도 됨
	# def __new__(cls):
	# 	print('new')
	# 	return super().__new__(cls)

	# 인스턴스 메소드: 객체에서 호출되어질 수 있는 함수
	# 파이썬 메소드의 첫 번째 파라미터명은 관례적으로 self라는 이름 사용
	# 호출 시 호출한 객체 자신이 전달되기 때문에 self라는 이름 사용 (메소드의 첫 번째 인자로 항상 인스턴스(객체)가 전달)
	# 이를 이용하여 클래스에서 바로 메소드로 접근하면서 위에서 할당한 Flight의 객체 f를 파라미터로 전달해도 똑같은 결과값 얻음
	def number(self):
		return self.__number

	def add_class_attr(self, number):
		Flight.class_attr.append(number)
class Fly:

	# 비공개 클래스 속성: 언더바 두 개(__)로 시작 (비공개 속성으로 만들기)
	__private_attr = 5

	class_attr = []

	# 클래스 속성과 인스턴스 속성을 동일하게 만들면, 인스턴스 속성을 먼저 찾게됨
	# [참고] 속성과 메소드 이름 찾는 순서: 인스턴스 -> 클래스
	# [주의] 클래스 속성은 여러 객체가 공유함
	def __init__(self):
		self.class_attr = []

	def add_instance_attr(self, number):
		self.class_attr.append(number)

	def add_class_attr(self, number):
		Fly.class_attr.append(number)


Method Overloading (Python에서 불가능)

"""
파이썬은 메소드 오버로딩 불가능
(아래의 클래스의 show 메소드는 가장 밑에 있는 것으로 동작)

메소드 오버로딩: 하나의 클래스 내부에서 메소드 명칭은 똑같고, 인자를 다르게하는 형태 (Java에서는 가능)
"""
class Korea:

	def __init__(self, name, population, capital):
		self.name = name
		self.population = population
		self.capital = capital

	def show(self):
		print(
			"""
			국가의 이름은 {} 입니다.
			국가의 인구는 {} 입니다.
			국가의 수도는 {} 입니다.
			""".format(self.name, self.population, self.capital)
			)

	def show(self, abc):
		print('abc :', abc)


Inheritance

"""
상속(inheritance)
- 물려주는 클래스(Parent Class, Super Class)의 내용(속성과 메소드)을 물려받는 클래스(Child Class, Sub Class)가 가지게 되는 것

e.g. 국가 클래스 -> 한국, 일본, 중국, 미국 클래스
- 국가 클래스가 속성으로 인구를 만들었으면, 상속 받은 한국, 일본, 중국, 미국 클래스에서는 부모 클래스의 속성인 인구를 비롯하여 메소드 사용 가능

자식 클래스를 선언할 때 소괄호로 부모 클래스 포함하면, 자식 클래스에서는 부모 클래스의 속성과 메소드 사용 가능
"""

class Country:
	"""Super Class"""

	name = '국가명'
	population = '인구'
	capital = '수도'

	def show(self):
		print('국가 클래스의 메소드입니다.')

class Province:
	Province_list = []

	def show(self):
		print('province show method!')

# 상속받은 서브 클래스에서는 상속해준 슈퍼 클래스의 속성과 메소드 모두 사용 가능
# 파이썬은 다중 상속 가능 (C#, Java는 불가능, C++은 가능)
class Korea(Country, Province):
	"""Sub Class"""

	def __init__(self, name, population, capital):
		self.name = name
		self.population = population
		self.capital = capital

	# 메소드 오버라이딩 (Method Overriding): 부모 클래스의 메소드를 자식 클래스에서 재정의
	# 부모 클래스의 메소드는 무시되고 자식 클래스의 메소드가 수행
	def show(self):
		# super() 사용시 자식 클래스 내에서 부모 클래스 호출 가능
		super().show()
		print(
			"""
			국가의 이름은 {} 입니다.
			국가의 인구는 {} 입니다.
			국가의 수도는 {} 입니다.
			""".format(self.name, self.population, self.capital)
			)

	def show_name(self):
		print('국가 이름은 : ', self.name)


Static Method & Class Method

"""
정적 메소드 (@classmethod, @staticmethod)

- 정적 메소드: 클래스에서 직접 접근할 수 있는 메소드
- [주의] 파이썬에서는 다른 언어와 다르게 정적 메소드임에도 불구하고 인스턴스에서도 접근 가능
"""

class CustomClass:

	# instance method는 첫 번째 인자로 객체 자신 self를 입력
	def add_instance_method(self, a, b):
		return a + b

	# classmethod는 첫 번째 인자로 클래스를 입력
	@classmethod
	def add_class_method(cls, a, b):
		return a + b

	# staticmethod는 특별히 추가되는 인자가 없음
	@staticmethod
	def add_static_method(a, b):
		return a + b
"""
정적 메소드
- 일반적으로 인스턴스 데이터를 액세스할 필요가 없는 경우 클래스 메소드 혹은 정적 메소드 사용
- 클래스 변수를 액세스할 필요가 있을 때 클래스 메드 사용
- 클래스 변수를 액세스할 필요가 없을 때 정적 메소드 사용

@classmethod와 @staticmethod의 차이

staticmethod에서는 부모 클래스의 클래스 속성 값을 가져오지만,
classmethod에서는 cls 인자를 활용하여 cls의 클래스 속성을 가져옴
"""

class Language:
	default_language = "English"

	def __init__(self):
		self.show = '나의 언어는' + self.default_language

	# 클래스 메소드(classmethod): cls라는 클래스를 의미하는 파라미터 전달 받아서, 클래스 변수 등에 액세스 가능
	# 
	@classmethod
	def class_my_language(cls):
		return cls()

	# 정적 메소드(staticmethod): self 파라미터를 갖지 않고 인스턴스 변수에 액세스할 수 없음
	# 보통 객체 필드와 독립적이지만 로직상 클래스 내에 포함되는 메소드에 사용
	@staticmethod
	def static_my_language():
		return Language()

	def print_language(self):
		print(self.show)

class KoreanLanguage(Language):
	default_language = "한국어"
"""
정적 메소드 사용 예제

- 인스턴스 데이터 액세스 필요 없을 때: class method, static method 사용
- 클래스 변수 액세스 필요 있을 때: class method 사용
- 클래스 변수 액세스 필요 없을 때: static method 사용
"""

class Rectangle:

	# 클래스 변수
	count = 0

	def __init__(self, width, height):
		self.width = width
		self.height = height
		Rectangle.count += 1

	# instance method
	def calc_area(self):
		area = self.width * self.height
		return area

	# static method
	@staticmethod
	def is_square(rectWidth, rectHeight):
		return rectWidth == rectHeight

	# class method
	@classmethod
	def print_count(cls):
		print(cls.count)


Abstract Class

"""
추상 클래스(abstract class)
- 미구현 추상 메소드를 한 개 이상 가지며, 자식 클래스에서 해당 추상 메소드를 반드시 구현하도록 강제
- 상속받은 클래스는 추상 메소드를 구현하지 않아도, import 할 때까지는 에러 발생하지 않으나 객체 생성 시 에러 발생
- 반드시 `abc` 모듈 import 필요
- 추상메소드를 추가한 후에는 해당 클래스로 객체를 생성하면 에러 발생

e.g.
from abc import *
class 추상클래스명(metaclass=ABCMeta):

	@abstractmethod
	def 추상메소드(self):
		pass
"""
from abc import *

class AbstractCountry(metaclass=ABCMeta):
	name = '국가명'
	population = '인구'
	capital = '수도'

	def show(self):
		print('국가 클래스의 메소드입니다.')

	# 추상메소드 추가
	@abstractmethod
	def show_capital(self):
		print('국가의 수도는?')

class Korea(AbstractCountry):

	def __init__(self, name, population, capital):
		self.name = name
		self.population = population
		self.capital = capital

	def show_name(self):
		print('국가 이름은 : ', self.name)

	# 상속받은 추상메소드 구현(필수)
	def show_capital(self):
		super().show_capital()
		print(self.capital)


Duck Typing

"""
Duck Typing
'If it walks like a duck and it quacks like a duck, then it must be a duck'
'오리처럼 걷고, 오리처럼 꽥꽥거리면, 그것은 틀림없이 오리다.'

- 본질적으로 다른 클래스라도 객체의 적합성은 객체의 실제 유형이 아니라 특정 메소드와 속성의 존재에 의해 결정됨
"""

class Parrot:
	def fly(self):
		print("Parrot flying")

class Airplone:
	def fly(self):
		print("Airplane flying")

class Whale:
	def swim(self):
		print("Whale swimming")

# Parrot 클래스와 Airplane 클래스는 서로 상속 등의 관계가 없으나, 내부에 동일한 메소드인 fly()가 있기 때문에, lift_off 함수 호출 시 fly 정상 실행
# 반면, Whale 클래스는 fly() 메소드가 없기 때문에, AttributeError 발생
def lift_off(entity):
	entity.fly()

parrot = Parrot()
airplane = Airplone()
whale = Whale()

lift_off(parrot)  # prints "Parrot flying"
lift_off(airplane)  # prints "Airplane flying"
lift_off(whale)  # Throws the error `'Whale' object has no attribute 'fly'`