[Python] 객체와 클래스
객체와 클래스 :
인스턴스/클래스 변수, 인스턴스/정적/클래스 메서드, 가변 인자, 클래스 상속
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 : 인자값을
키워드=값
형태로 넣으면 함수에 딕셔너리로 전달, 파라미터 명을 함께 보낼 수 있음
- 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
- [서적] 데이터 분석을 위한 파이썬 철저 입문 (위키북스, 2019)
- [블로그] 파이썬 메서드 오버라이딩(Overriding) vs 오버로딩(Overloading)
- [블로그] 정적(static) 메서드와 클래스(class) 메서드