[Programming Language]/[Python]

[Python] :: 클래스 총정리(객체, 인스턴스, 생성자 및 소멸자, isinstance(), 속성, 메소드, 클래스 변수, 정적 메소드와 클래스 메소드, 상속, 추상클래스)

Semincolon 2023. 7. 31. 05:01

◎ 클래스(Class)

  파이썬은 Java와 동일하게 객체 지향 프로그래밍 언어(Object Oriented Programming Language)이다. 사실 대부분의 프로그래밍 언어는 객체 지향 프로그래밍 언어이다. 이는 클래스를 기반으로 한 객체를 생성하여 프로그래밍 하는 것을 뜻한다. 그렇다면 객체란 무엇인가?

 

1) 객체(Object)

  객체란 여러 가지 속성을 가질 수 있는 대상을 의미한다. 예를 들어 '사람'은 '이름', '성별', '나이' 등의 속성을 가질 수 있으므로 하나의 객체라고 볼 수 있는 것이다.

 

1-1) 인스턴스(Instance)

  인스턴스는 클래스를 기반으로 만들어진 객체를 의미한다. 즉, '인스턴스 == 객체' 관계인 것이다. 인스턴스라는 용어는 자주 사용되니 기억해두는 것이 좋다.

 

2) 클래스(Class)

  클래스는 비슷한 성질을 갖는 객체를 위한 하나의 큰 집합이라고 볼 수 있다. 클래스는 속성(attribute)과 메소드(method)를 가질 수 있다. 아래는 클래스의 기본 형태이다. 참고로 파이썬에서 클래스 명을 지을 때 각 단어의 앞 글자를 대문자로 하는 '캐멀 케이스(Camel case)' 방식을 사용한다.

class 클래스명:
	클래스 내용
class TestClass:
	pass
    
a = TestClass() # TestClass 기반 인스턴스 a 생성
b = TestClass() # TestClass 기반 인스턴스 b 생성

 

2-1) 생성자(__init__)

  인스턴스가 생성될 때 클래스 내의 생성자가 호출된다. 클래스에는 기본 생성자가 정의되어 있는데 별도의 생성자를 정의하지 않으면 이 기본 생성자가 사용되므로 생성자를 사용하지 않아도 문제는 없다. 생성자의 형태는 다음과 같다.

class TestClass:
    def __init__(self):
        print("인스턴스가 생성되었습니다.")
        
a = TestClass() # 인스턴스 a 생성

  'init' 앞 뒤에 존재하는 언더바(_)는 개수가 각각 2개라는 점에 유의해야 한다. 이처럼 앞 뒤로 언더바가 2개씩 붙은 메소드는 파이썬에서 자동으로 호출하는 메소드임을 뜻한다. 생성자의 매개변수를 보면 'self'가 존재하는데 생성자를 포함한 클래스 내의 모든 메소드의 첫 매개변수에는 반드시 'self'가 존재해야 한다. 매개변수의 이름은 'self'가 아닌 다른 것으로 해도 무방하지만 일반적으로 자기 자신이라는 뜻의 'self'가 사용되니 이를 따르는 것이 좋다.

 

2-1 ①) isinstance()

  isinstance() 함수는 생성된 인스턴스가 해당 클래스로부터 생성된 것이 맞는지 확인이 필요할 때 사용하는 함수이다. 참이면 True, 거짓이면 False를 반환하는데 이는 많이 사용되는 함수이니 알아두면 좋다.

isinstance(인스턴스명, 클래스명)
class TestClass:
    def __init__(self):
        print("인스턴스가 생성되었습니다.")
        
a = TestClass() # 인스턴스 a 생성
print(isinstance(a, TestClass))

2-2) 소멸자(__del__)

  생성자는 인스턴스가 생성될 때 호출되는 메소드라 하였다. 소멸자는 반대로 인스턴스가 소멸될 때 호출되는 메소드이다. 소멸자의 형태는 다음과 같다.

class TestClass:
    # 생성자
    def __init__(self):
        print("인스턴스가 생성되었습니다.")

    # 소멸자
    def __del__(self):
        print("인스턴스가 소멸되었습니다.")
        
a = TestClass() # 인스턴스 a 생성
print("***프로그램 종료***")

  소멸자에서 중요한 포인트는 인스턴스가 언제 소멸되는지이다. 위 코드에서는 변수 a에 인스턴스를 할당하고 "***프로그램 종료***"를 출력한 뒤에 소멸자가 호출되는 것을 확인할 수 있다. 이는 변수 a에 할당된 인스턴스가 언제 사용될 지 모르니 최후까지 보류하였다가 맨 마지막 문장을 실행함으로써 사용되지 않는 것을 확인하고 소멸된 것이다.

class TestClass:
    # 생성자
    def __init__(self):
        print("인스턴스가 생성되었습니다.")

    # 소멸자
    def __del__(self):
        print("인스턴스가 소멸되었습니다.")
        
TestClass() # 변수 a에 할당하지 않음
print("***프로그램 종료***")

  만약 위와 같이 따로 변수에 할당하지 않는다면 파이썬에서는 생성된 인스턴스를 굳이 가지고 있으면서 메모리를 차지할 필요가 없다고 판단하여 바로 소멸자를 호출하게 된다.

 

2-3) 메소드(method)

  앞서 계속 메소드라는 용어를 언급하였는데 메소드는 클래스가 가지고 있는 함수를 말한다. 즉, '메소드 == 함수' 관계인 것이다. 그러나 함수라는 용어를 메소드라고 칭하기도 하니 유연하게 바라보면 될 것 같다. 이미 언급한 것처럼 메소드를 선언할 때는 반드시 첫 번째 매개변수로 'self'가 와야 한다. 이 'self'에는 메소드를 호출한 인스턴스 자기 자신이 들어간다. 아래는 메소드의 기본 형태이다.

class 클래스명:
	def 메소드명(self, ...) # ...는 추가 매개변수를 뜻함
            문장
class TestClass:
    # 생성자
    def __init__(self):
        print("인스턴스가 생성되었습니다.")

    # 소멸자
    def __del__(self):
        print("인스턴스가 소멸되었습니다.")

    # hello() 메소드 선언
    def hello(self, who):
        print("{0}님, 안녕하세요!".format(who))
        
a = TestClass()
a.hello("홍길동") # hello() 메소드 호출

print("***프로그램 종료***")

2-4) 속성(attribute)

  앞서 인스턴스(객체)란 속성을 가질 수 있는 대상이라 하였다. 예를 들어 Person 클래스는 그 속성으로 name, age 등을 가질 수 있는 것이다. 속성은 기본적으로 생성자에서 선언한다. 다음은 속성을 선언하고 값을 할당하여 사용하는 예이다.

class Person:
    def __init__(self, name, age):
        print("생성자 호출")
        self.name = name
        self.age = age

a = Person("홍길동", 24)
print("이름: {0}, 나이: {1}".format(a.name, a.age))

  속성은 보통 위와 같은 형태로 선언하지만 인스턴스를 생성한 이후에도 선언할 수 있다. 이때 주의사항으로는 인스턴스에 선언한 속성은 해당 인스턴스에만 한정적이고 다른 인스턴스에는 적용되지 않는다는 것이다. 아래 코드는 a, b에 인스턴스를 할당하고 a에 추가로 선언한 속성이 b에는 적용되지 않은 것을 보여준다.

class Person:
    def __init__(self, name, age):
        print("생성자 호출")
        self.name = name
        self.age = age

a = Person("홍길동", 24)
b = Person("파이썬", 20)
a.city = "Seoul"

print(a.city)
print(b.city) # 오류 발생

2-4 ⓐ) 비공개 속성(private attribute)

  앞서 다룬 속성은 공개 속성이다. 이는 외부에서 해당 속성에 자유롭게 접근할 수 있는 것을 말한다. 그러나 만약 중요한 속성인데도 불구하고 실수로 외부에서 값을 잘못 바꿔버린다면 치명적인 문제가 발생할 것이다. 이를 위해 파이썬에서는 속성을 외부에서 접근하지 못하고 오직 해당 클래스 내에서만 접근 가능한 비공개 속성을 지원한다. 이를 사용하는 방법은 일반 속성을 선언하는 것에서 속성명 앞에 언더바(_)를 2개 붙여주기만 하면 된다. 이렇게 하면 외부에서 해당 속성에 접근할 때 오류가 발생하게 된다.

class Person:
    def __init__(self, name, age):
        print("생성자 호출")
        self.__name = name  # 비공개 속성
        self.__age = age    # 비공개 속성

a = Person("홍길동", 24)

print("이름: {0}, 나이: {1}".format(a.name, a.age))

2-4 ⓑ) 비공개 속성에 접근하기 위한 메소드 getter/setter

  비공개 속성은 앞서 말했듯 외부에서 직접적인 접근이 불가하고 클래스 내에서만 접근이 가능하다. 따라서 해당 속성에 접근하기 위해서는 클래스 내에 정의된 메소드가 필요하다. 보통 속성의 값을 가져오는 메소드를 게더(getter)라고 하며 속성의 값을 변경하는 메소드를 세더(setter)라고 한다. 아래 코드는 앞서 선언했던 비공개 속성에 대한 게더와 세더를 선언하여 사용하고 있다.

class Person:
    def __init__(self, name, age):
        print("생성자 호출")
        self.__name = name  # 비공개 속성
        self.__age = age    # 비공개 속성

    # getter 메소드 정의
    def get_name(self):
        return f'{self.__name}'
    def get_age(self):
        return self.__age
    
    # setter 메소드 정의
    def set_name(self, name):
        self.__name = name
    def set_age(self, age):
        self.__age = age

a = Person("홍길동", 24)

# getter을 통한 속성 접근
print("이름: {0}, 나이: {1}".format(a.get_name(), a.get_age()))

# setter을 통한 속성 값 변경
a.set_name("김철수")
a.set_age(20)
print("이름: {0}, 나이: {1}".format(a.get_name(), a.get_age()))

2-4 ⓒ) 비공개 메소드(private method)

  속성과 동일하게 메소드 역시 비공개로 선언할 수 있다. 전체적인 내용은 비공개 속성과 동일하다.

class Person:
    def __init__(self, name, age):
        print("생성자 호출")
        self.__name = name  # 비공개 속성
        self.__age = age    # 비공개 속성

    # 비공개 메소드 export()
    def __export(self):
        print("이름: {0}, 나이: {1}".format(self.__name, self.__age))

    def fun(self):
        self.__export()

a = Person("홍길동", 24)

# fun() 메소드를 통해 비공개 메소드 export() 호출
a.fun()

# 비공개 메소드 export()에 직접 접근하면 오류 발생
a.__export()

2-5) 클래스 속성(클래스 변수)

  앞서 다루었던 속성은 모두 인스턴스 속성이다. 즉, 각 속성들은 인스턴스 별로 구분된다는 것이다. 그러나 클래스 속성은 모든 인스턴스에 대해 공통적으로 작용한다. 클래스 속성도 비공개 속성으로 사용 가능하다.

class Person:
    # 클래스 속성 count 선언
    count = 0

    def __init__(self, name, age):
        print("생성자 호출")
        self.name = name
        self.age = age

        # 클래스 속성 count 1 증가
        Person.count += 1

a = Person("홍길동", 24)
b = Person("김철수", 20)

print("a.count: {0}, b.count: {1}".format(a.count, b.count))

c = Person("파이썬", 22)
print("a.count: {0}, b.count: {1}, c.count: {2}".format(a.count, b.count, c.count))

  위 코드는 클래스 속성 count를 선언하고 생성자가 호출될 때마다 1씩 증가하도록 하고 있다. 변수 a, b, c에 할당된 3개의 인스턴스 모두 동일한 count 값을 가지고 있는 것을 확인할 수 있다. 여기서 2가지의 주의사항이 존재한다.

※ 주의사항 1) 클래스 속성에 접근하기 위해선 '클래스명.속성명' 형태로 사용해야 한다.
  '클래스명.속성명' 형태가 아닌 'self.속성명' 형태로 사용하게 되면 이는 하나의 새로운 인스턴스 속성이 된다.
def __init__(self, name, age):
        print("생성자 호출")
        self.name = name
        self.age = age

        # 이는 클래스 속성이 아닌 인스턴스 속성이 됨
        self.count += 1​
- - -
※ 주의사항 2) 클래스 속성의 값을 변경할 때도 역시 '클래스명.속성명' 형태를 사용해야 한다.
  값을 변경할 때도 '클래스명.속성명' 형태를 사용해야 한다. '인스턴스를 할당한 변수.속성명' 형태를 사용하게 되면 이 역시 하나의 새로운 인스턴스 속성이 된다.
...(생략)
a = Person("홍길동", 24)
a.count = 10 # 이는 하나의 새로운 인스턴스 속성으로 인식됨
Person.count = 10 # 이렇게 해야 클래스 속성의 값이 변경됨​

 

2-6) 정적 메소드(static method)

  정적 메소드란 인스턴스를 생성하지 않아도 호출할 수 있는 메소드를 뜻한다. 이는 보통 메소드의 실행이 인스턴스 등에 영향을 끼치지 않는 경우에 사용한다. 정적 메소드는 @staticmethod 데코레이터를 붙여 선언한다.

class Calculator:
    # 정적 메소드(static method) 선언
    @staticmethod
    def add(x, y):
        print("{0}+{1}={2}".format(x, y, x+y))

# 별도의 인스턴스 생성 없이 바로 사용 가능
Calculator.add(3, 5)

2-7) 클래스 메소드(class method)

  클래스 메소드는 앞서 다룬 정적 메소드와 거의 비슷하다. 단, 클래스 메소드는 클래스 내의 속성이나 다른 메소드에 대한 접근이 필요할 때 사용한다. 클래스 메소드는 @classmethod 데코레이터를 붙여 선언한다. 클래스 메소드를 선언할 때 첫 번째 매개변수에는 class의 약자를 뜻하는 cls가 와야 한다. 다른 이름을 사용해도 무방하나 이 역시 self와 동일하게 일반적으로 사용되는 개념이므로 cls를 사용하는 것이 좋다.

class 클래스명:
    @classmethod
    def 메소드명(cls, ...):
        문장
class School:
    # 클래스 속성 선언
    man = 100
    woman = 200

    # 클래스 메소드 선언
    @classmethod
    def print(cls):
        print("man: {0}, woman: {1}".format(cls.man, cls.woman))
        # cls.man과 cls.woman은 School.man, School.woman으로도 사용 가능하긴 함

# 정적 메소드와 동일하게 인스턴스 생성 없이 바로 호출 가능
School.print()

 

3) 상속(inheritance)

  클래스의 상속은 상속을 하는 입장의 부모 클래스(parent class)상속을 받는 입장의 자식 클래스(child class)로 나눌 수 있다. 부모 클래스를 상속받은 자식 클래스는 부모 클래스의 속성과 메소드를 사용할 수 있다. 자식 클래스는 여러 부모 클래스를 상속받을 수 있다. 상속의 형태는 다음과 같다.

class 부모클래스:
	...
    
class 자식클래스(부모클래스):
	...
class Animal:
    def __init__(self):
        print("Animal 클래스의 생성자")

class Dog(Animal):
    def __init__(self):
        Animal.__init__(self) # 부모 클래스 생성자 호출
        print("Dog 클래스의 생성자")

d = Dog()

  위 코드에서 자식 클래스인 Dog 클래스는 부모 클래스로 Animal 클래스를 상속받고 있다. 자식 클래스에서 생성자(__init__)를 정의하지 않았다면 자식 클래스의 생성자가 호출될 때 자동으로 부모 클래스의 생성자가 호출되지만, 정의했다면 부모 클래스의 생성자 호출을 해줘야 한다.

 

3-1) 부모 클래스의 속성 사용

  부모 클래스를 상속받은 자식 클래스는 부모 클래스의 속성을 사용할 수 있다.

class Animal:
    # 클래스 속성 count
    count = 0

    def __init__(self):
        print("Animal 클래스의 생성자")
        Animal.count += 1

class Dog(Animal):
    def __init__(self):
        Animal.__init__(self) # 부모 클래스 생성자 호출
        print("Dog 클래스의 생성자")

d1 = Dog()
d2 = Dog()

print(d1.count, d2.count)

  분명 자식 클래스인 Dog 클래스에는 count 속성이 존재하지 않지만 해당 속성은 부모 클래스인 Animal 클래스에 존재하므로 정상적으로 출력이 이뤄진다. 만약 자식 클래스에 count 속성이 존재한다면 부모 클래스의 속성이 아닌 자식 클래스의 속성이 출력된다.

 

3-2) 부모 클래스의 메소드 사용

  부모 클래스를 상속받은 자식 클래스는 속성과 동일하게 부모 클래스의 메소드 역시 사용 가능하다.

class Animal:
    def __init__(self):
        print("Animal 클래스의 생성자")
    
    def print(self):
        print("Animal Class")

class Dog(Animal):
    def __init__(self):
        Animal.__init__(self) # 부모 클래스 생성자 호출
        print("Dog 클래스의 생성자")

d1 = Dog()

d1.print()

3-3) 메소드 오버라이딩(method overriding) or 메소드 재정의

  메소드 오버라이딩이란 이미 존재하는 메소드와 같은 이름의 다른 메소드를 선언함으로써 기존의 메소드를 대체하는 새로운 메소드를 선언하는 것이다. 이는 보통 메소드의 이름은 변경하지 않고 수행하는 기능만을 변경하고자 할 때 사용한다.

class Animal:
    def __init__(self):
        print("Animal 클래스의 생성자")
    
    def print(self):
        print("Animal Class")

class Dog(Animal):
    def __init__(self):
        Animal.__init__(self) # 부모 클래스 생성자 호출
        print("Dog 클래스의 생성자")

    # method overriding
    def print(self):
        print("Dog Class")

d1 = Dog()

d1.print()

  자식 클래스인 Dog 클래스에서 부모 클래스의 print() 메소드를 재정의(overriding)하고 있다. 이로써 기존의 부모 클래스의 print() 메소드가 호출되었던 것에 비해 이제는 자식 클래스의 print() 메소드가 호출되고 있다. 재정의한 자식 클래스의 메소드에서 부모 클래스의 메소드를 호출할 때는 super() 메소드를 사용하면 된다.

class Animal:
    def __init__(self):
        print("Animal 클래스의 생성자")
    
    def print(self):
        print("Animal Class")

class Dog(Animal):
    def __init__(self):
        Animal.__init__(self) # 부모 클래스 생성자 호출
        print("Dog 클래스의 생성자")

    # method overriding
    def print(self):
        super().print() # 부모 클래스의 메소드 호출
        print("Dog Class")

d1 = Dog()

d1.print()

4) 추상 클래스(abstract class)

  부모 클래스는 추상 클래스로 선언할 수 있다. 추상 클래스란 말 그대로 추상적인 형태만 가지고 있는 클래스를 뜻한다. 추상 클래스의 선언을 위해서는 abc(abstract base class) 모듈을 import 해야 한다. 기본 형태는 다음과 같다.

from abc import *

class 추상클래스명(metaclass=ABCMeta):  
    @abstractmethod
    def 메소드명(self):
    	...

  자식 클래스가 추상 클래스를 상속받는 경우에는 추상 클래스 내의 모든 메소드를 재정의(overriding) 해야 한다. 만약 아래 코드처럼 모든 메소드를 재정의 하지 않게되면 오류가 발생한다.

from abc import *

# 추상 클래스 선언
class Parent(metaclass=ABCMeta):
    @abstractmethod
    def fun1(self):
        pass

    @abstractmethod
    def fun2(self):
        pass

# 추상 클래스 상속 받는 자식 클래스 선언
class Child(Parent):
    def fun1(self):
        print("fun1")

a = Child()
a.fun1()