18 분 소요

객체와 클래스 :

인스턴스/클래스 변수, 인스턴스/정적/클래스 메서드, 가변 인자, 클래스 상속

1. 클래스 선언과 객체 생성

1.1. 객체란?

  • 객체란, 속성과 행위로 구성된 대상
    • 객체 = 속성 + 행위
    • 속성 : 상태, 특징, 변수로 구현
    • 행위 : 행동, 동작, 기능, 함수(메서드)로 구현
    • ex) 자전거 = 속성(바퀴 크기, 색깔) + 행위(전진, 방향 전환, 정지)
  • 객체 지향 프로그래밍(OOP) 언어
    • Object-Oriented Programming
    • 객체를 만들고 이용할 수 있는 기능을 제공하는 프로그래밍 언어

1.2. 클래스와 객체의 관계

  • 객체를 만들기 전 클래스를 선언해야 함
  • 클래스란, 객체의 공통된 속성과 행위를 변수와 함수로 정의한 것
    • 클래스 : 객체를 만들기 위한 기본 틀 (붕어빵 틀)
    • 객체 : 기본 틀을 바탕으로 만들어진 결과 (단팥 붕어빵, 슈크림 붕어빵, 피자 붕어빵 등)
    • 객체의 속성을 규정한 변수에 무슨 값을 할당하는지에 따라 객체의 속성이 달라짐
  • 객체 = 클래스의 인스턴스(Instance)
    • 객체는 클래스에서 생성하므로 클래스의 사례
  • 클래스 선언
    • 클래스에서 정의한 함수의 첫 번째 인자는 self, 객체 생성 후에 자신을 참조하는데 이용
class Class(a, b):
	var1 = a
	var2 = b
	...
	def func1(self, a, b, ... , n):
		...
	def func2(self, a, b, ... , n):
		...

1.3. 객체 생성 및 활용

  • 클래스 선언 및 객체 실행
    • 객체 실행하면 객체의 클래스와 객체를 생성할 때 할당 받은 메모리의 주솟값을 출력
# 클래스 선언
class Bicycle():
	pass
# 객체 생성
my_bicycle = Bicycle()
# 객체 실행
my_bicycle
---
# my_bicycle 객체는 Bicycle 클래스의 인스턴스 at 객체의 메모리 주소
<__main__.Bicycle at 0x1dd5bdfc240>
  • 객체 속성 설정 및 호출
    • 객체명.변수명 = 속성값 / 객체명.변수명
# 객체 속성 설정
my_bicycle.wheel_size = 26
my_bicycle.color = 'black'
# 객체 속성 출력
print(my_bicycle.wheel_size)
print(my_bicycle.color)
  • 클래스에 함수 추가 및 호출
    • 클래스의 함수 안에서 객체 속성을 가져올 때는 self.변수명으로 속성값 설정 및 호출
    • 객체명.메서드명([인자1, 인자2, ... , 인자n])
class Bicycle():
	
	def move(self, speed):
		print("자전거: 시속{0}킬로미터로 전진".format(speed))

	def turn(self, direction):
		print("자전거: {0}회전".format(direction))

	def stop(self):
		print("자전거({0}, {1}): 정지 ".format(self.wheel_size, self.color))
  • 클래스 활용
    • 객체는 필요한 만큼 얼마든지 만들어 사용 가능(속성 다양하게 설정 가능)
# 객체 생성
my_bicycle = Bicycle()
# 객체 속성 설정
my_bicycle.wheel_size = 26
my_bicycle.color = 'black'
# 객체의 메서드 호출
my_bicycle.move(30)
my_bicycle.turn('좌')
my_bicycle.stop()
---
자전거: 시속 30킬로미터로 전진
자전거: 좌회전
자전거(26, black): 정지

1.4. 객체 간 속성은 독립적

  • 각 객체 간의 속성은 서로 영향을 미치지 않음
    • 동일한 클래스에서 생성된 각 객체들의 속성은 서로 독립적이기 때문에 bicycle1 객체의 속성을 지정해줬다고 해서 bicycle2 객체의 속성이 생기는 것이 아님
    • 하지만 이는 인스턴스 변수인 경우에 한해서 독립적
class Bicycle():
    pass

bicycle1 = Bicycle()
bicycle2 = Bicycle()

bicycle1.wheel_size = 27
bicycle1.color = 'red'

print(bicycle1.wheel_size)
print(bicycle2.wheel_size)
---
27
Traceback (most recent call last):
  File "D:/workspaces/workspace_project/multi_pjt2/test/python_class.py", line 12, in <module>
    print(bicycle2.wheel_size)
AttributeError: 'Bicycle' object has no attribute 'wheel_size'

1.5. 객체 초기화

  • 앞에서는 클래스 선언 > 객체 생성 > 객체 속성 설정
  • 클래스 선언 시, __init__() 구현을 통해 객체 생성과 동시에 속성 값 지정 가능
  • __init__() 함수
    • 클래스의 인스턴스가 생성될 때 자동으로 실행
    • 초기화하려는 인자를 정의하면 객체 생성 시 속성 초기화 가능
      • 만일 속성 초기화를 해주지 않으면 객체 생성 후에 일일이 속성 값을 직접 정의해줘야 함
class Bicycle():

	def __init__(self, wheel_size, color):
		self.wheel_size = wheel_size
		self.color = color
	
	def move(self, speed):
		print("자전거: 시속{0}킬로미터로 전진".format(speed))

	def turn(self, direction):
		print("자전거: {0}회전".format(direction))

	def stop(self):
		print("자전거({0}, {1}): 정지 ".format(self.wheel_size, self.color))
  • 클래스에 __init__() 함수 정의되어 있으면 객체 생성 시 인자를 반드시 입력해야 함
# 객체 생성과 동시에 속성값 가짐
my_bicycle = Bicycle(26, 'black')
# 객체 메서드 호출
my_bicycle.move(30)
my_bicycle.turn('좌')
my_bicycle.stop()
---
자전거: 시속 30킬로미터로 전진
자전거: 좌회전
자전거(26, black): 정지

2. 클래스를 구성하는 변수와 함수

2.1. 클래스에서 사용하는 변수

(1) 클래스 변수 vs 인스턴스 변수

  클래스 변수 (class variable) 인스턴스 변수 (instance variable)
위치 클래스 안, 함수 밖 클래스 안, 함수 안
선언 함수 밖에서 변수명 = 데이터 함수 안에서 self.변수명 = 데이터
범위 동일한 클래스의 객체 모두 사용 가능 각 객체에서 개별적 사용
호출(밖) 클래스명.변수명
객체명.변수명 (인스턴스 변수 없을 때)
객체명.변수명
호출(안) 클래스명.변수명 self.변수명

(2) 변수 예시

  • 클래스 변수와 인스턴스 변수 정의
class Car():
	# 클래스 변수 생성 및 초기화
	instance_count = 0
    
	def __init__(self, size, color):
		# 인스턴스 변수 생성 및 초기화
		self.size = size
		self.color = color
		# 클래스 변수 사용
		Car.instance_count = Car.instance_count + 1
		print("자동차 객체의 수: {0}".format(Car.instance_count))

	def move(self):
		print("자동차({0} & {1})가 움직입니다.".format(self.size, self.color))
  • 클래스 변수 접근 : 클래스명.변수명, 객체명.변수명
car1 = Car('small', 'white')
car2 = Car('big', 'black')
# 클래스 변수 호출
print("클래스의 총 인스턴스 개수: {}".format(Car.instance_count))
print("클래스의 총 인스턴스 개수: {}".format(car1.instance_count))
print("클래스의 총 인스턴스 개수: {}".format(car2.instance_count))
---
자동차 객체의 : 1
자동차 객체의 : 2
Car 클래스의  인스턴스 개수: 2
Car 클래스의  인스턴스 개수: 2
Car 클래스의  인스턴스 개수: 2
  • 인스턴스 변수 접근 : 객체명.변수명
car1.move()
car2.move()
---
자동차(small & white) 움직입니다.
자동차(big & black) 움직입니다.
  • 변수명이 같아도 클래스 변수와 인스턴스 변수는 별개로 동작
class Car2():
	count = 0

	def __init__(self, size, num):
		self.size = size
		self.count = num
		Car2.count = Car2.count + 1
        # 클래스 변수 사용
		print("클래스 변수:", Car2.count)
        # 인스턴스 변수 사용
		print("인스턴스 변수:", self.count)

car1 = Car2("big", 20)
car2 = Car2("small", 30)
---
클래스 변수: 1
인스턴스 변수: 20
클래스 변수: 2
인스턴스 변수: 30

2.2. 클래스에서 사용하는 함수

(1) 인스턴스 vs 정적 vs 클래스

  인스턴스 메서드(instance) 정적 메서드(static) 클래스 메서드(class)
선언   @staticmethod @classmethod
인자 self (객체) X cls (클래스)
활용 인스턴스 변수, 일반 동작 등 주로 날짜, 환율 정보 제공, 단위 변환, 유틸리티 메서드 등 클래스 변수 사용, 팩토리 메서드 등
범위 각 객체에서 개별적 동작 인스턴스와 무관하게 독립적 작용 클래스 전체에서 관리할 기능
호출 객체명.메서드명(인자) 클래스명.메서드명(인자) 클래스명.메서드명(인자)
특징 - 인스턴스 속성 생성 및 사용
- 생성된 인스턴스 변수는 다른 메서드에서 사용 가능
- 인스턴스/클래스 속성, 메서드 호출 불가
- 객체 생성 필요 없음
- 인스턴스 메서드나 인스턴스 변수 호출 불가, cls로 클래스 속성과 메서드 호출 가능
- 객체 생성 필요 없음

(2) 인스턴스 메서드

# 인스턴스 메서드
class ClassMethod():
	def instance_m(self, a, b, ... ,n):
		# 인스턴스 변수 생성
		self.var1 = a
		self.var2 = b
		...
        
# 객체 생성
obj = ClassMethod()
# 인스턴스 메서드 호출
obj.instance_m(a, b, ... , n)

(3) 정적 메서드

# 정적 메서드
class ClassMethod():
	@staticmethod
	def static_m(a, b, ..., n):
		<코드 블록>

# 정적 메서드 호출
ClassMethod.static_m(a, b, ..., n)
  • 유틸리티 클래스
    • 인스턴스 메서드, 인스턴스 변수를 제공하지 않고, 정적 메서드와 변수만 제공하는 클래스
    • 비슷한 기능의 메서드와 상수를 모아서 캡슐화
class StringUtils:
    @staticmethod
    def toCamelcase(text):
        words = iter(text.split("_"))
        return next(words) + "".join(i.title() for i in words)
    
    @staticmethod
    def toSnakecase(text):
        letters = ["_" + i.lower() if i.isupper() else i for i in text]
        return "".join(letters).lstrip("_")

(4) 클래스 메서드

# 클래스 메서드
class ClassMethod():
	@classmethod
	def class_m(cls, a, b, ..., n):
		...

# 클래스 메서드 호출
ClassMethod.class_m(a, b, ..., n)
  • 클래스 메서드로 생성자(__init__) 호출 및 인스턴스 생성
class DBConnect:
    def __init__(self, url):
        self.url = url
        self.db = url.split(":")[0]
        print(f"{url}에 접속했습니다.")

    @classmethod
    def mysql(cls, user, password):
        return cls(f"mysql://{user}:{password}@localhost:3306/database")

    @classmethod
    def postgresql(cls, user, password):
        return cls(f"postgresql://{user}:{password}@localhost:5432/database")


conn = DBConnect.postgresql("admin", "password")
print(conn.db)
---
postgresql://admin:password@localhost:5432/database에 접속했습니다.
postgresql

(5) 메서드 예시

class Car():
    instance_count = 0

    # 인스턴스 메서드
    def __init__(self, size, color):
        self.size = size
        self.color = color
        Car.instance_count = Car.instance_count + 1
        print("자동차 객체의 수: {0}".format(Car.instance_count))

    # 인스턴스 메서드
    def move(self, speed):
        self.speed = speed
        print("자동차({0} & {1})가 ".format(self.size, self.color), end='')
        print("시속 {0}킬로미터로 전진".format(self.speed))

    # 인스턴스 메서드
    def auto_cruise(self):
        print("자율 주행 모드")
        self.move(self.speed)
        
    @staticmethod
	def check_type(model_code):
		if(model_code >= 20):
			print("이 자동차는 전기차입니다.")
		else:
			print("이 자동차는 디젤차입니다.")
            
	@classmethod
	def count_instance(cls):
		print("자동차 객체의 개수: {0}".format(cls.instance_count))

        
car1 = Car("small", "red")
car2 = Car("big", "green")

car1.move(20)
car2.auto_cruise()

Car.check_type(25)
Car.count_instance()
---
자동차 객체의 : 1
자동차 객체의 : 2
자동차(small & red) 시속 20킬로미터로 전진
자율 주행 모드
자동차(small & red) 시속 20킬로미터로 전진
 자동차는 전기차입니다.
자동차 객체의 개수: 2

2.3. 가변 인자 변수

  • *args 란?
    • arguments : 인자값을 여러 개 넣으면 함수에 튜플로 묶어서 전달
  • **kwargs 란?
    • keyword arguments : 인자값을 키워드=값 형태로 넣으면 함수에 딕셔너리로 전달, 파라미터 명을 함께 보낼 수 있음
class Siri:
    unlocked = False

    def respond(self, **kwargs):
        if kwargs["password"] == "right":
            self.unlocked = True
            print(f"Yes, {kwargs['name']}")
        else:
            print("Please type your password for using.")

    def calculate(self, *args):
        if self.unlocked:
            res = 0
            for i in args:
                res += i
            print(res)
        else:
            print("Please type your password for using.")


siri = Siri()
siri.calculate(1, 2, 3, 4, 5)
siri.respond(password="right", name="hanalog")
siri.calculate(1, 2, 3, 4, 5)
---
Please type your password for using.
Yes, hanalog
15

3. 객체와 클래스를 사용하는 이유

3.1. 클래스로 효율성 증대

  • 왜 굳이 클래스와 객체를 이용해 코드를 작성해야 하는가?
    • 공간적 효율성 : 코드 양 줄어듦
      • 유사한 객체 추가 시마다 클래스 사용하면 코드 작성 양 줄어듦
      • 클래스 상속을 활용하면 코드 2번 작성할 필요 없이 재사용 가능
    • 시간적 효율성 : 코드 관리 편리함
      • 코드 수정 시 여러 줄 수정할 필요 없이 class만 수정하면 됨
  • 규모가 큰 프로그램이나 유사한 객체가 많은 프로그램 개발 시 필요

(1) Before : 클래스 미사용

  • 클래스를 사용하지 않는 경우 로봇 위치 변경 프로그램
    • 로봇 개수가 증가할 수록 코드(변수, 함수 등)가 그만큼 증가
robot1_name = 'R1'
robot1_pos = 0

def robot1_move():
	# 지역변수가 아닌 전역변수 변경을 위해 선언
	global robot1_pos
	robot1_pos += 1
	print("{0} position: {1}".format(robot1_name, robot1_pos))

robot2_name = 'R2'
robot2_pos = 10

def robot2_move():
	# 지역변수가 아닌 전역변수 변경을 위해 선언
	global robot2_pos 
	robot2_pos += 1
	print("{0} position: {1}".format(robot2_name, robot2_pos))

(2) After : 클래스 사용

  • 클래스를 사용하는 경우 로봇 위치 변경 프로그램
    • 각 로봇 별로 변수와 함수의 역할은 같음
    • 로봇이 아무리 증가해도 코드는 두 줄만 추가하면 됨(객체만 생성하면 됨)
    • 코드 수정할 때도 class 만 수정하면 됨
class Robot():
	def __init__(self, name, pos):
		self.name = name
		self.pos = pos

	def move(self):
		self.pos += 1
		print("{0} position: {1}".format(robot2_name, robot2_pos))

robot1 = Robot('R1', 0)
robot1.move()

robot2 = Robot('R2', 10)
robot2.move()

robot3 = Robot('R3', 20)
robot3.move()

4. 클래스 상속

4.1. 상속이란?

  • 클래스 선언 방법 2가지
    • 처음부터 클래스 선언
    • 만들어진 클래스의 변수와 함수를 그대로 이어받고 새로운 내용만 추가해서 클래스 선언
  • 만들어진 클래스 = 부모 클래스, 상위 클래스, 슈퍼 클래스
  • 상속 받는 클래스 = 자식 클래스, 하위 클래스, 서브 클래스
  • 클래스 상속의 특징
    • 부모 클래스의 속성(변수)과 행위(함수)를 그대로 이용 가능
    • 상속 후 자식 클래스 만의 속성과 행위 추가 가능
    • 단, 부모 클래스에서 정의한 함수와 자식 클래스에서 정의한 함수가 이름이 같은 경우 부모 클래스 함수를 호출하려면 부모클래스명.함수명(), super().함수명() 와 같이 명시해줘야 함
  • 클래스 상속의 장점
    • 코드의 재사용성이 좋아짐
    • 유사한 클래스를 여러 개 만들어야 할 경우 공통 부분은 부모 클래스로 구현하고 이를 상속하는 자식 클래스를 각각 구현하면 최고
# 클래스 상속
class ChildClass(ParentClass):
	...

4.2. 상속 예시

# 부모 클래스 : 일반 자전거
class Bicycle():

	def __init__(self, wheel_size, color):
		self.wheel_size = wheel_size
		self.color = color
	
	def move(self, speed):
		print("자전거: 시속{0}킬로미터로 전진".format(speed))

	def turn(self, direction):
		print("자전거: {0}회전".format(direction))

	def stop(self):
		print("자전거({0}, {1}): 정지 ".format(self.wheel_size, self.color))

# 자식 클래스 : 접는 자전거 (일반 자전거의 속성과 동작 그대로 상속)
class FoldingBicycle(Bicycle):
	
		def __init__(self, wheel_size, color, state):
			# 부모 클래스의 초기화 재사용
			Bicycle.__init__(self, wheel_size, color)
			#super().__init__(self, wheel_size, color)
			
            # 자식 클래스만의 새로운 변수 추가
			self.state = state

		# 자식 클래스만의 함수
		def fold(self):
			self.state = 'folding'
			print("자전거: 접기, state={0}".format(self.state))
		
		# 자식 클래스만의 함수
		def unfold(self):
			self.state = 'unfolding'
			print("자전거: 펴기, state={0}".format(self.state))

folding_bicycle = FoldingBicycle(27, 'white', 'unfolding')
folding_bicycle.move(20)
folding_bicycle.fold()
folcing_bicycle.unfold()
---
자전거: 시속 20킬로미터로 전진
자전거: 접기, state=folding
자전거: 펴기, state=unfolding

4.3. 오버 로딩과 오버 라이딩

  • 오버로딩
    • 동일한 이름의 함수를 매개변수에 따라 다른 기능으로 동작 가능하게 만듦 (하나의 메서드에 다형성 부여)
    • 파이썬은 지원하지 않지만, MultipleDispatch 혹은 가변인자 변수를 활용하여 구현 가능
  • 오버라이딩
    • 부모클래스에서 정의한 메서드를 자식클래스에서 변경
    • 팩토리 메서드 구현 시 사용

REFERENCES