목차

클래스와 객체
클래스는 데이터와 함수들을 하나로 묶어놓은 구조를 말합니다.
객체를 만들기 위한 템플릿이라 생각하면 됩니다.
여기서 객체란, "클래스"라는 설계도를 기반으로 해서 만들어진 실제 인스턴스를 이야기합니다.
즉, 클래스가 붕어빵 틀, 객체(인스턴스)를 붕어빵이라고 한다면?
같은 틀로 여러 개의 붕어빵을 만들 수 있듯이,
하나의 클래스로 여러 개의 객체를 생성할 수 있습니다.
클래스를 사용하는 이유는 무엇일까요?
코드 재사용성이 높고,
비슷하거나 동일한 기능 및 데이터를 묶어서 관리하기가 좋습니다.
그래서 규모가 큰 프로젝트에서 관리를 하기가 훨씬 수월해지죠.
코드를 통해, 클래스와 객체에 대해 더 알아가 봅시다.
# class - 틀
class Person:
def eat(self): # 메서드
print("밥을 먹습니다.")
def sleep(self):
print("잠을 자요")
def study(self):
print('공부를 합니다.')
p1=Person() # 객체 생성
p1.eat()
p1.sleep()
p1.study()
p2=Person() # 객체 생성
print(p2)
다음과 같이 Person이라는 클래스를 만들어봤습니다.
def 는 이전에도 많이 봤지만 함수를 정의할 때 사용하는 키워드에요.
클래스 내에서 def로 정의를 한다면 클래스 안의 함수를 의미하겠죠? 이를 "메서드" 라고 합니다.
특이한 점은, 메서드의 매개 변수가 위치할 곳에 "self"라고 적을 수 있는데요,
이것은 현재 객체를 가리키기 위해 사용하는 것으로, 인스턴스의 속성과 메서드에 접근할 때 사용을 합니다.
그래서 클래스 안에 있는 모든 인스턴스 메서드는 반드시 첫 번째 인자에 self를 지정해줘야 합니다.
객체 생성을 하기 위해서는 클래스명()을 선언해주면 되요!
클래스에서는 객체가 생성될 때 자동으로 호출되는
생성자 메서드 __init__를 정의할 수 있습니다.
객체의 초기 상태를 설정할 수도 있고 인스턴스 변수를 정의할 수도 있어요.
class Person2:
def __init__(self, name, age, addr): # 생성자: 객체의 초기화
self.name=name
self.age=age
self.addr=addr
def prt(self): # 인스턴스 메서드 : 객체의 메서드
print(f"이름: {self.name}")
print(f"나이: {self.age}")
print(f"주소: {self.addr}")
p2 = Person2('hong', 20, '경기')
p2.prt()
코드와 같이 생성자에는 여러 매개변수를 지정할 수 있고, 호출을 통해 값을 받아서 초기화를 시켜줄 수 있어요.
즉, 객체 생성 시 전달한 값들이 __init__을 통해 초기화됩니다.
여기서 매개변수와 인스턴스 변수에 대해 헷갈릴 수 있습니다.
self.변수는 인스턴스 변수를 의미하며 클래스 내의 변수를 선언해주는 것이고,
name과 같은 변수는 매개변수로 외부에서 받은 값을 의미합니다.
따라서, 이름을 동일하게 써도 서로 다른 개념이라고 인식하시면 됩니다.🧐
지금까지 클래스의 기본 구조와 객체 생성,
그리고 self와 생성자의 역할까지 살펴보았습니다.
클래스를 처음 접하면 헷갈리는 개념들이 많은데요,
특히 아래 개념들은 비슷해 보이지만 역할이 다르기 때문에
명확하게 구분하는 것이 중요합니다.
- 클래스 변수 vs 인스턴스 변수
- 인스턴스 메서드 vs 클래스 메서드 vs 정적 메서드
다음 단계에서는 이 개념들을 비교하면서 정리해보면
클래스 구조를 훨씬 명확하게 이해하고자 합니다.
클래스 변수 vs 인스턴스 변수
클래스에는 클래스 변수와 인스턴스 변수가 있습니다.
클래스 변수는 모든 인스턴스가 공유하는 변수이고, 인스턴스 변수는 각각의 객체마다 독립적으로 가지는 변수를 말합니다.
class A:
age=10 # 클래스 변수
def __init__(self, name): # name: 매개변수
self.name=name # self.name: 인스턴스 변수
a1=A('hong')
a2=A('park')
print(a1.age,a2.age,A.age)
a1.age=20
print(a1.age,a2.age,A.age)
A.age=100
print(a1.age,a2.age,A.age)
예를 들어, 클래스 내부에 age와 같은 변수를 정의하면 이는 클래스 변수로서 모든 객체에서 동일한 값을 공유하게 됩니다.
따라서 "객체.변수명" 또는 "클래스명.변수명" 형태로 모두 접근할 수 있습니다.
하지만 객체를 통해 클래스 변수를 수정할 경우에는 주의가 필요한데요,
객체에서 a1.age = 20과 같이 값을 변경하면, 이는 클래스 변수를 수정하는 것이 아니라 해당 객체에 새로운 인스턴스 변수를 생성하는 동작이 됩니다. 그 결과, 해당 객체만 다른 값을 가지게 되고 다른 객체나 클래스에는 영향을 주지 않습니다.
반면에, A.age = 100과 같이 클래스 자체에서 값을 변경하면, 해당 클래스 변수를 공유하고 있는 모든 객체에 그 값이 반영됩니다. 다만, 이미 동일한 이름의 인스턴스 변수를 가지고 있는 객체는 클래스 변수의 영향을 받지 않습니다.
내부적으로 보면,
클래스 변수는 클래스.__dict__에 저장되어 클래스당 하나만 존재하고,
인스턴스 변수는 각 객체의 __dict__에 따로 저장됩니다.
정리하자면, 클래스 변수는 모든 객체가 함께 사용하는 공용 데이터이고, 인스턴스 변수는 각 객체가 개별적으로 관리하는 데이터입니다.
상황에 따라 적절히 구분해서 사용해야 될 것 같아요.🧐
인스턴스 메서드 vs 클래스 메서드 vs 정적 메서드
클래스 내부에는 크게 세 가지 종류의 메서드가 존재합니다.
바로 인스턴스 메서드, 클래스 메서드, 정적 메서드입니다.
겉으로 보기에는 비슷하지만, 각각의 역할과 사용 목적은 분명히 다릅니다.
인스턴스 메서드는 객체(인스턴스)를 기준으로 해서 동작을 하는 메서드입니다.
객체를 통해 호출하며, 객체명.메서드() 형태로 사용합니다.
가장 큰 특징은 인스턴스 변수와 클래스 변수 모두에 접근할 수 있다는 점입니다.
또한 첫 번째 매개변수로 항상 self를 받아야 하며, 이를 통해 현재 객체의 상태(데이터)에 접근하거나 값을 수정할 수 있습니다.
class Hospital:
country_name='대한민국'
def __init__(self, department:str, location:str, phone_number:str):
self.department=department # self.department: 인스턴스 변수
self.location=location
self.phone_number=phone_number
def prt(self):
print(self.department)
print(self.location) # 인스턴스 변수 접근은 self.변수
# print(Hospital.country_name) # 비추천
print(self.__class__.country_name) # self.__class__
예를 들어, 병원 정보를 다루는 클래스에서 각 객체의 진료과, 위치, 전화번호 등을 출력하는 기능은 인스턴스 메서드로 구현하는 것이 적절합니다.
이때 인스턴스 변수는 self.변수명으로 접근하며,
만약 클래스 변수에 접근을 한다고 했을 때, "클래스.클래스변수" 형태로 할 수도 있지만, 클래스 이름을 직접 언급해서 쓰면 클래스 이름이 변경 시에 모두 바꿔줘야 하니까 비효율적인 코드가 될 수도 있습니다.
따라서, self.__class__를 사용해서 접근하는 것을 추천합니다.😯
클래스 메서드는 클래스를 기준으로 동작하는 함수로,
객체가 아니라 클래스를 통해 호출하는 것이 일반적이며, 클래스명.메서드() 형태로 사용합니다.
클래스 메서드는 @classmethod 데코레이터를 사용하여 정의하며,
첫 번째 매개변수로 self 대신 cls를 사용해줘야 합니다.
# 클래스 메서드
class Person:
country='대한민국' # 클래스 변수
def __init__(self, name): # 생성자
self.name=name # self.name: 인스턴스변수, name: 매개변수
def prt(self): # 인스턴스 메서드
print(self) # 객체 정보를 출력
print(self.name, self.__class__.country)
@classmethod # @: annotation
def class_method(cls): # 클래스 메서드
print(cls) # 클래스에 대한 정보를 출력
print(cls.country) # 에러 X
# print(cls.name) # 에러 O
⚠️ 여기서, 왜 인스턴스 메서드는 인스턴스 변수, 클래스 변수 모두 접근이 가능한데, 클래스 메서드는 클래스 변수만 접근이 가능할까요? 인스턴스 메서드와 클래스 메서드의 가장 큰 차이는 어떤 대상을 기준으로 동작하느냐에 있습니다.
이 차이를 이해하면, 왜 접근 가능한 변수의 범위가 다른지도 자연스럽게 이해할 수 있습니다.
인스턴스 메서드는 첫 번째 매개변수로 self를 받아, 특정 객체(인스턴스)를 기준으로 동작합니다.
즉, 어떤 객체의 데이터를 다루는지 명확하게 알고 있기 때문에, 해당 객체가 가지고 있는 인스턴스 변수에 접근할 수 있습니다.
또한, 인스턴스는 자신이 속한 클래스의 정보도 함께 알고 있기 때문에 클래스 변수에도 접근이 가능합니다.
결과적으로 인스턴스 메서드는 인스턴스 변수와 클래스 변수 모두에 접근할 수 있습니다.
반면, 클래스 메서드는 첫 번째 매개변수로 cls를 받아, 클래스 자체를 기준으로 동작합니다.
이 메서드는 특정 객체를 기준으로 동작하는 것이 아니라, 클래스 자체에 대한 정보만을 가지고 있습니다. 따라서 클래스 변수에는 접근할 수 있지만, 어떤 객체의 데이터인지 알 수 없기 때문에 인스턴스 변수에는 접근할 수 없습니다.
여기서 중요한 점은, 이 차이가 단순히 “객체가 생성되었느냐”는 아닙니다.
객체가 이미 존재하더라도, 클래스 메서드는 그 객체를 참조하지 않기 때문에 인스턴스 변수에 접근할 수 없습니다.
즉, 접근 가능 여부는 생성 시점이 아니라 메서드가 어떤 대상을 참조하고 있는지에 따라 결정됩니다.
정리하자면, 인스턴스 메서드는 self를 통해 객체를 참조하기 때문에 더 많은 정보(인스턴스 + 클래스)에 접근할 수 있고, 클래스 메서드는 cls를 통해 클래스만 참조하기 때문에 클래스 수준의 데이터에만 접근할 수 있습니다.
마지막으로, 정적 메서드는 클래스나 인스턴스와 직접적인 관련이 없는 메서드입니다.
즉, 객체의 상태(self)도 사용하지 않고, 클래스 자체(cls)에도 의존하지 않는 독립적인 함수라고 볼 수 있습니다.
정적 메서드는 @staticmethod 데코레이터를 사용하여 정의하며,
매개변수로 self나 cls를 받지 않습니다.
# 정적 메서드
class Person:
country='대한민국'
def __init_(self, name):
self.name=name
@staticmethod
def doing():
print('동작을 합니다.')
# print(self.name) # 에러
# print(cls.country) # 에러
# print(self.__class__.country) # 에러.
p1=Person()
p1.doing()
Person.doing()
이러한 특성 때문에 정적 메서드 내부에서는 인스턴스 변수나 클래스 변수에 직접 접근할 수 없습니다.
다만, 논리적으로 해당 클래스와 관련된 기능이라면, 코드의 구조를 깔끔하게 유지하기 위해 클래스 내부에 포함시키는 경우가 많습니다.
메서드 간 호출
추가적으로, 클래스 내부에서는 메서드 간 호출도 가능합니다.
class Student:
def __init__(self, name, kor, eng, math):
self.name=name
self.kor=kor
self.eng=eng
self.math=math
def get_total(self):
return self.kor+self.eng+self.math
def get_avg(self):
total=self.get_total()
return total/3
def prt(self):
print(f"이름: {self.name}, 국어: {self.kor}, 영어: {self.eng}, 수학: {self.math}, 총점: {self.get_total()}, 평균: {self.get_avg():.1f}")
s1=Student('hong',90,80,100)
s1.prt()
예를 들어, 총점과 평균을 구하는 기능을 각각 메서드로 분리해두고,
출력 메서드에서 이를 호출하면 코드의 재사용성과 가독성을 높일 수 있습니다.
이때는 self.메서드명() 형태로 호출하면 됩니다.
정리하자면,
인스턴스 메서드는 객체의 상태를 다루기 위한 메서드이고,
클래스 메서드는 클래스 자체의 상태를 다루기 위한 메서드이며,
정적 메서드는 클래스와 관련은 있지만 상태를 사용하지 않는 기능을 정의할 때 사용됩니다.
추가사항: str
클래스로 객체를 생성한 뒤 print()로 출력해보면, 우리가 기대한 값이 아니라 메모리 주소 형태의 문자열이 출력되는 것을 확인할 수 있습니다. 이는 기본적으로 객체를 문자열로 표현하는 방법이 따로 정의되어 있지 않기 때문입니다.
그렇다면 객체에 저장된 값을 사람이 읽기 쉬운 형태로 출력하고 싶을 때는 어떻게 해야 할까요?
이때 사용하는 것이 바로 __str__ 메서드입니다.
#str
class Person:
def __init__(self,id:str,name:str,age:int=0,height:float=150.0,weight:float=0.0):
self.id=id
self.name=name
self.age=age
self.height=height
self.weight=weight
p1=Person('111', 'hong')
p2=Person('222','park',10,155,40)
print(p1) # 메모리 주소 출력됨.
class Person:
def __init__(self,id:str,name:str,age:int=0,height:float=150.0,weight:float=0.0):
self.id=id
self.name=name
self.age=age
self.height=height
self.weight=weight
def __str__(self):
print("__str__ 자동 호출!") # __str__ 자동 호출!
return f'id: {self.id}, name: {self.name}, age: {self.age}, height: {self.height}, weight: {self.weight}'
p1=Person('111', 'hong')
p2=Person('222','park',10,155,40)
print(p1) # id: 111, name: hong, age: 0, height: 150.0, weight: 0.0
__str__ 메서드는 객체를 문자열로 표현할 때 자동으로 호출되는 메서드로, print(객체) 또는 str(객체)를 실행하면
자동으로 __str__이 실행됩니다.
예를 들어, 생성자를 통해 id, name, age 등의 값을 저장한 뒤, __str__ 메서드에서 이 값들을 문자열로 반환하도록 하면,
객체를 출력할 때 원하는 출력값이 나오게 됩니다.
여기서 중요한 점은 __str__ 메서드는 반드시 문자열을 반환해야 한다는 것입니다. 만약 print()만 사용하고 값을 반환하지 않으면 정상적으로 동작하지 않습니다. 따라서 출력하고 싶은 내용을 return을 통해 문자열 형태로 반환해주어야 합니다..
이 기능은 디버깅이나 로그를 확인할 때 사용해보면 좋은 기능인것 같습니다.😮
'개발' 카테고리의 다른 글
| 파이썬: 파일 입출력 (0) | 2026.04.11 |
|---|---|
| 파이썬: 상속(Inheritance)이란? (0) | 2026.04.10 |
| 파이썬: 람다함수(Lambda Functions) (1) | 2026.04.09 |
| 파이썬: 함수 선언하기 (0) | 2026.04.09 |
| 파이썬: 반복문(for, while) (0) | 2026.04.09 |