객체와 클래스

객체

파이썬에서 객체란 고유성(identity), 형(type)과 값(value)로 구성됩니다. 고유성은 객체가 생성되면 객체의 유일성을 나타내는 값으로 일반적으로 객체의 메모리 주소를 나타냅니다.

앞에서 배운 기본 타입들인 int, float, list, tuple 등도 모두 객체입니다

클래스

클래스는 데이터(또는 필드)와 기능(함수 또는 메소드)을 함께 묶는 방법을 제공합니다. 새로운 클래스를 정의하는 것은 새로운 데이터 형을 만든다는 것입니다.

인스턴스

클래스를 이용해서 인스턴스 instance 들을 만들어 낼 수 있습니다. 인스턴스는 인스턴스의 상태를 나타내는 필드 field (또는 데이터 속성 data attribute)들과 인스턴스의 상태를 변경할 수 있는 메소드 method 들을 가질 수 있습니다. 클래스는 붕어빵을 만들어내는 붕어빵 틀과 같은 것이고 인스턴스란 붕어빵 틀에서 나온 붕어빵들을 의미한다고 볼 수 있습니다. 붕어빵을 만들 때 속재료에 해당하는 것이 인스턴스의 상태를 나타내는 필드라고 할 수 있습니다. 속재료의 내용을 변경하려고 할 때 사용하는 동작에 해당하는 것이 메소드라고 할 수 있습니다.

클래스 정의와 인스턴스 만들기

클래스는 예약어 class 뒤에 클래스 이름을 적고 콜론 :으로 마치고, 다음 줄에 들여쓰기 하면서 클래스 몸통을 시작합니다.

class <클래스 이름>:
    <클래스 몸통>

가장 간단한 클래스를 만들어 봅니다.

class 붕어빵:
    pass

붕어빵이란 이름의 클래스를 만들었고 몸통은 비워두었습니다. 클래스의 인스턴스를 만들기 위해서는 클래스 이름에 이어 소괄호 ()를 열고 닫으면 됩니다. 즉 붕어빵 클래스의 인스턴스는 아래와 같이 하면됩니다.

 = 붕어빵() # 인스턴스 객체 만들기
print()
<__main__.붕어빵 object at 0x000001FA3AEA97F0>

위에서 = 붕어빵() 부분에서 붕어빵 인스턴스 객체를 만들어 이란 변수에 할당한 것입니다. 을 출력해보면 이 참조하고 있는 인스턴스 객체의 저장위치 주소가 출력되는 것을 알 수 있습니다.

직접하기

  1. Rectangle 클래스를 만들고 인스턴스를 만들어 출력해보세요.

    rect = Rectangle()
    print(rect)
    
  2. Dog 클래스를 만들고 인스턴스를 만들어 출력해보세요.

    dog = Dog()
    print(dog)
    

메소드와 self

클래스에 기능을 추가해 봅니다. 기능을 추가하기 위해서는 클래스의 메소드 method 를 이용하면 됩니다. 메소드란 앞에서 배운 함수와 같은 역할을 하는 것으로 특별히 클래스에서는 함수라는 용어대신 메소드라고 부릅니다. 메소드 정의는 함수 정의할 때와 마찬가지로 def 예약어를 사용합니다.

다음은 Cat 클래스에 speak() 메소드를 추가한 것입니다.

In [1]: class Cat:
   ...:   def speak(self):
   ...:     print("야옹~~!")
   ...: 

여기서 주목해야할 것은 self라는 매개변수입니다. self란 인스턴스 객체를 의미하는 것으로 메소드를 호출할 때 파이썬이 자동으로 첫번째 인수로 넘겨줍니다. 따라서 인스턴스 메소드를 정의할 때는 반드시 첫번째 매개변수로 self를 입력해야 합니다. self 대신 다른 이름을 사용할 수 있지만 통상적으로 사용하는 이름이므로 그대로 사용합니다. self는 인스턴스 메소드를 호출할 때 파이썬이 자동으로 넘겨주기 때문에 사용자가 인수로 입력할 필요가 없습니다.

메소드를 부르기 위해서는 먼저 인스턴스 객체를 만들어야 합니다. 인스턴스 객체는 클래스 이름 뒤에 소괄호를 이용해 만듭니다.

In [2]: cat = Cat() # 인스턴스 객체 만들기

메소드를 실행하려면 인스턴스 객체 바로 뒤에 점을 찍고 메소드 이름을 적어 소괄호를 이용해서 함수 호출하는 것과 똑같이 합니다.

In [3]: cat.speak() # 메소드 부르기
야옹~~!

__init__ 메소드

__init__() 메소드는 인스턴스를 만들 때 인스턴스의 초기 상태를 설정하기 위해 사용됩니다. init 앞 뒤로 두겹 밑줄을 이용하여 만듭니다. 파이썬은 인스턴스를 만들 때 자동으로 __init__() 메소드를 실행합니다.

Cat 인스턴스를 만들 때, 고양이의 이름을 정하고 싶을 때 __init__ 메소드를 사용하면 됩니다.

In [4]: class Cat:
   ...:   def __init__(self, name):
   ...:     self.name = name
   ...: 

self.name 에서 self 는 인스턴스를 가리키며 .name 그 인스턴스에 속해있는 필드 이름 name을 의미합니다. 즉, 다음과 같이 Cat 인스턴스를 만들 때 인수로 이름을 입력하여 cat을 만들면 cat의 이름은 체리을 가지게 됩니다.

In [5]: cat = Cat('체리')

cat 인스턴스의 이름이 무었인지를 확인하기 위해서는 다음과 같이 인스턴스 뒤에 점을 찍고 필드 이름을 적어서 확인 할 수 있습니다.

In [6]: print(cat.name)
체리

인스턴스 필드들은 클래스 내에서 언제든지 사용할 수 있습니다.

In [7]: class Cat:
   ...:   def __init__(self, name):
   ...:     self.name = name
   ...: 
   ...:   def speak(self):
   ...:     print(f'야옹~~! 난 {self.name}야.')
   ...: 
   ...: cat = Cat('체리')
   ...: cat.speak()
   ...: 
야옹~~! 난 체리야.

직접하기

  1. Person 클래스와 __init__(self, name, age, job, gender, blood) 메소드를 구현해봅니다. Person 클래스의 필드로 이름, 나이, 직업, 성별, 혈액형 등을 구현합니다.

  2. Dog 클래스에 __init__(self, name, age) 메소드를 구현해봅니다. Dog 클래스의 필드로 name, age 를 구현하고 eat(self, food). sleep(self) 등의 메소드를 만들어 보세요.

  3. User 클래스를 구현해보세요. name, email, password 속성을 추가하세요. describe() 메소드를 이용해 유저 정보들을 출력하세요. greet() 메소드를 이용해 자신을 소개하는 내용을 출력해보세요. 다수의 인스턴스들을 만들고 각각 메소드들을 실행해보세요.

  4. Restaurant 클래스를 만들어 보세요. __init__ 메소드를 이용해 restaurant_name, cuisine_type 속성을 추가하세요. describe() 메소드를 이용해 식당 이름과 식당 타입을 출력해보세요. open() 메소드를 이용해 식당이 영업중이라고 출력해보세요. Restaurant 인스턴스를 만들어 각각의 메소드들을 실행해보세요.

속성 변경

직접하기

  1. Restaurant 클래스에 number_served 속성을 추가하고 기본값은 0으로 설정합니다. 인스턴스를 만들어 손님의 수를 바꿔보고 바뀐 값을 출력해봅니다. set_number_served(number) 메소드를 추가하여 방문한 손님의 수를 설정합니다. 메소드를 실행해보고 방문한 손님의 수를 출력해봅니다. increment_number_served(number) 메소드를 이용하여 손님의 수를 증가시켜보세요.

  2. User 클래스에 login_attepmts 속성을 추가하고 increment_login_attempts() 메소드를 이용하여 로그인 횟수를 하나씩 증가해보세요. reset_login_attempts() 메소드를 이용하여 로그인 회수 login_attepmts 를 0로 만들어 보세요.

Note

클래스의 메소드는 인스턴스 메소드 instance method, 클래스 메소드 class method, 정적 메소드 static method 로 나눌 수 있습니다. 인스턴스 메소드는 메소드 정의시 반드시 self 매개변수가 맨 먼저 와야하며 인스턴스의 상태를 변경하거나 반영할 수 있습니다. 클래스 메소드는 클래스의 상태와 관련된 메소드로 메소드 정의시 @classmethod 장식자(decorator)를 붙이고 cls라는 이름의 매개변수가 맨 먼저 와야 하며 cls는 클래스 객체를 가리키는 변수입니다. 여기서 cls는 변수 이름이며 다른 이름을 사용해도 됩니다. 정적 메소드는 클래스와 상관없이 정의되는 것으로 클래스의 문맥상 클래스 안에 정의할 뿐입니다. @staticmethod 장식자(decorator)를 함수 정의하기 전에 사용합니다. 정적 메소드는 모듈의 함수와 비슷한 역할을 한다고 볼 수 있습니다.

클래스 객체와 인스턴스 객체

클래스/인스턴스 객체 만들기

파이썬 클래스와 관련된 객체는 클래스 객체인스턴스 객체로 나눌 수 있습니다. 클래스 객체는 클래스 정의가 되자마자 만들어집니다. 즉, 파이썬 인터프리터가 소스 파일로부터 클래스 정의 부분을 읽어들일 때 만들어집니다.

In [8]: class Rectangle:
   ...:     name = "직사각형"
   ...: 
   ...:     def __init__(self, width, height):
   ...:         self.width = width
   ...:         self.height = height
   ...: 
   ...:     def info(self):
   ...:         print('직사각형 클래스입니다')
   ...: 
   ...:     def area(self):
   ...:         return self.width * self.height
   ...: 
   ...:     def perimeter(self):
   ...:         return 2 * (self.width + self.height)
   ...: 

클래스 객체는 앞에서 봤던 인스턴스 객체와 다르게 소괄호를 통해서 만들어질 필요가 없습니다. 클래스 객체는 클래스 이름으로 접근하면 됩니다. 그리고 클래스 객체를 이용해서 클래스변수와 메소드들을 접근할 수 있습니다.

In [9]: print(Rectangle.name)
직사각형

메소드와 함수

클래스에 정의된 메소드를 클래스 객체를 이용해서 접근할 수도 있습니다. 이때는 인스턴스와는 다르게 첫번째 인자로 인스턴스 객체를 넘겨 주어야 합니다. 클래스 객체를 이용해서 접근하는 것은 일반적인 함수 객체를 접근하는 것과 같습니다.

In [10]: Rectangle.area() # 에러 발생. 첫번째 인수가 필요
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-10-479b64445857> in <module>
----> 1 Rectangle.area() # 에러 발생. 첫번째 인수가 필요

TypeError: area() missing 1 required positional argument: 'self'

하지만 다음과 같이 첫번째 인수로 인스턴스 객체를 넘겨주면 작동합니다.

In [11]: rect = Rectangle(2, 3)
   ....: Rectangle.area(rect)    # 첫번째 인수로 인스턴스 객체 넘겨줌
   ....: 
Out[11]: 6

__init__() 메소드 차이

인스턴스 객체는 위에서 본 바와 같이 <클래스 이름>()을 통하여 만들어 지고 인스턴스 객체가 생성되면서 자동적으로 __init__메소드가 실행이 됩니다. 하지만 클래스 객체는 __init__ 메소드를 실행하지 않습니다.

In [12]: class MyClass:
   ....:     cvar = "클래스 변수"
   ....: 
   ....:     def __init__(self, var1):
   ....:         self.var1 = var1
   ....:         print("__init__메소드가 실행되었습니다. self.var1은 {}입니다.".format(self.var1))
   ....: 
   ....:     def method1(self):
   ....:         print("메소드1 입니다.")
   ....: 
   ....:     def method2(self, var2):
   ....:         print("method2의 인자는 {}입니다.".format(var2))
   ....: 

클래스 객체를 통해서 실행해봅니다.

In [13]: MyClass.method1("Don't Care!")
메소드1 입니다.

보는바와 같이 __init__ 메소드가 실행이 되지 않습니다. 하지만 인스턴스를 만들어서 실행하면 다음과 같이 됩니다.

In [14]: my = MyClass("사과")
__init__메소드가 실행되었습니다. self.var1은 사과입니다.

인스턴스 객체가 만들어지면 언제든지 인스턴스.변수 또는 인스턴스.메소드()를 통하여 자신의 값을 접근하고 변경할 수 있습니다.

In [15]: print(my.var1)
사과
In [16]: my.var1 = "배"
   ....: print(my.var1)
   ....: 

클래스 변수, 인스턴스 변수

클래스변수란 클래스 안에, 메소드 밖에서 정의된 변수를 말합니다. 위에서 cvar이 클래스변수입니다. 클래스 변수는 모든 인스턴스가 공유하는 속성과 메소드를 의미합니다. 클래스 변수 접근은 클래스이름.변수를 이용합니다.

In [17]: class 클래스:
   ....:   변수 = '클래스 변수'
   ....: 
   ....:   def __init__(self, 이름):
   ....:     self.이름 = 이름 # self.이름은 인스턴스 변수
   ....: 
   ....: print(클래스.변수)
   ....: 
클래스 변수

인스턴스 변수는 인스턴스에 종속되어 변하는 데이터에 연결된 변수를 의미하고 인스턴스 변수 접근은 인스턴스객체변수.변수와 같이 합니다.

In [18]:  = 클래스("객체이름")
   ....: print(.이름)
   ....: 
객체이름

식별자 유효범위(scope)

클래스의 메소드에서 사용되는 식별자는 그 식별자가 인스턴스에 속한 것인지 클래스에 속한 것인지 아니면 지역에 속한 것인지 전역에 속한 것인지를 명확하게 구분해줘야 합니다.

인스턴스 객체의 메소드 식별자 검색 순서는 먼저 지역 변수인지 확인하고 다음으로 인스턴스 객체 변수인지 확인하고 다음은 클래스 객체 변수인지 확인하고 마지막으로 전역변수인지를 확인해서 없으면 NameError가 발생합니다.

In [19]: 변수 = "전역 변수"
   ....: 
   ....: class 클래스:
   ....:     변수 = '클래스 변수'
   ....: 
   ....:     def __init__(self, 이름):
   ....:         self.이름 = 이름 # self.이름은 인스턴스 변수
   ....: 
   ....:     def 메소드(self):
   ....:         변수 = "어디에 속할까?"
   ....:         self.변수 = "인스턴스 변수"
   ....: 
   ....:     def 상태(self):
   ....:         print("변수는:", 변수)
   ....:         print("self.변수는:", self.변수)
   ....: 
   ....: print(변수)
   ....:  = 클래스("클래스 이름")
   ....: .상태()
   ....: 
전역 변수
변수는: 전역 변수
self.변수는: 클래스 변수

위에서 self.변수의 값이 클래스 변수로 나온 것을 볼 수 있습니다. 이것은 self.변수 식별자를 지역, 인스턴스, 클래스, 전역 순서로 찾기때문입니다. 지역에도 없고 인스턴스에도 정의가 되어 있지 않으므로 클래스 객체의 값을 참조하게되는 것입니다.

메소드에서 클래스 변수의 값을 변경하기 위해서는 인스턴스객체.__class__를 이용합니다. 인스턴스객체.__class__는 인스턴스 객체가 속한 클래스 객체의 참조입니다.

In [20]: 변수 = "전역 변수"
   ....: 
   ....: class 클래스:
   ....:     변수 = '클래스 변수'
   ....: 
   ....:     def __init__(self, 이름):
   ....:         self.이름 = 이름 # self.이름은 인스턴스 변수
   ....: 
   ....:     def 메소드(self):
   ....:         변수 = "어디에 속할까?"
   ....:         self.변수 = "인스턴스 변수"
   ....: 
   ....:     def 클래스변수값변경(self):
   ....:         self.__class__.변수 = "클래스 변수값이 변경되었습니다."
   ....: 
   ....:     def 상태(self):
   ....:         print("변수는:", 변수)
   ....:         print("클래스 변수는:", self.__class__.변수)
   ....: 
   ....:  = 클래스("클래스 이름")
   ....: .클래스변수값변경()
   ....: .상태()
   ....: 
변수는: 전역 변수
클래스 변수는: 클래스 변수값이 변경되었습니다.

메소드 안에서 다른 메소드를 부를 때도 인스턴스의 메소드인지 클래스의 메소드인지를 명확하게 해주어야 합니다.

In [21]: 변수 = "전역 변수"
   ....: 
   ....: class 클래스:
   ....:     변수 = '클래스 변수'
   ....: 
   ....:     def __init__(self, 이름):
   ....:         self.이름 = 이름 # self.이름은 인스턴스 변수
   ....: 
   ....:     def 메소드(self):
   ....:         변수 = "어디에 속할까?"
   ....:         self.변수 = "인스턴스 변수"
   ....: 
   ....:     def 클래스변수값변경(self):
   ....:         메소드()
   ....:         self.__class__.변수 = "클래스 변수값이 변경되었습니다."
   ....: 
   ....:     def 상태(self):
   ....:         print("변수는:", 변수)
   ....:         print("클래스 변수는:", self.__class__.변수)
   ....: 
   ....:  = 클래스("클래스 이름")
   ....: .클래스변수값변경()
   ....: 
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-21-0401b0c31ab2> in <module>
     20 
     21  = 클래스("클래스 이름")
---> 22 .클래스변수값변경()

<ipython-input-21-0401b0c31ab2> in 클래스변수값변경(self)
     12 
     13     def 클래스변수값변경(self):
---> 14         메소드()
     15         self.__class__.변수 = "클래스 변수값이 변경되었습니다."
     16 

NameError: name '메소드' is not defined

14줄에서 메소드()라는 이름을 찾을 수 없다는 에러가 발생하는 것을 볼 수 있습니다. 이 메소드를 전역에서 찾기때문입니다. 인스턴스의 메소드로 사용하기 위해서는 self.메소드()와 같이 메소드가 어디에 속했는지를 밝혀서 사용해야 합니다.

가변/불변 객체(Object)

모든 객체는 고유성 identity를 가지고 있습니다. 고유성은 형 type과 값 value로 구성됩니다. 값이 변할 수 있는 객체를 가변 객체 mutable object라고 하고 값이 변하지 않는 객체를 불변 객체 immutable object라고 합니다. 불변 객체로는 숫자형(int, float, complex), 문자열(str), 튜플(tuple)등이 있습니다. 객체의 고유성은 객체가 일단 한 번 생성되면 절대 변하지 않습니다. 고유성은 객체의 메모리 주소라고 생각할 수 있습니다. id() 함수를 이용해 고유성을 나타내는 정수를 확인할 수 있습니다.

is 연산자를 이용하여 두 객체가 같은 고유성을 갖는지를 알 수 있습니다. is는 두 객체의 id() 값이 같으면 True를 다르면 False를 반환합니다. is not은 그 반대로 작동합니다.

불변 객체라고 해도 불변 객체를 이루고 있는 성분이 가변 객체라면 가변 객체의 내용이 변함에 따라 불변 객체에 대응되는 가변 객체 성분이 변할 수 있습니다. 예를 들어 봅니다.

In [22]: clist = [1, 2, 3]
   ....: x = (1, "a", clist)
   ....: print("변경 전 clist:", clist)
   ....: print("clist 변경 전 x:", x)
   ....: 
   ....: print('clist 변경 전 id(x):',id(x))
   ....: 
   ....: clist[0] = 100
   ....: print("\n변경된 clist:", clist)
   ....: 
   ....: print("clist 변경 후 x:", x)
   ....: print('clist 변경 후 id(x):',id(x))
   ....: 
변경 전 clist: [1, 2, 3]
clist 변경 전 x: (1, 'a', [1, 2, 3])
clist 변경 전 id(x): 1716336023296

변경된 clist: [100, 2, 3]
clist 변경 후 x: (1, 'a', [100, 2, 3])
clist 변경 후 id(x): 1716336023296

불변 객체 튜플 x = (1, "a", clist)의 3번째 성분이 가변 객체 리스트 clist입니다. clist[0] = 100으로 변경하면 x의 3번째 성분도 함께 변경됩니다. 하지만 xid는 변하지 않습니다.

상속 inheritance

상속은 기존 클래스의 성질을 이어 받아 새로운 클래스를 만드는 방법입니다. 기존 클래스를 기반 클래스 base class 또는 상위 클래스 superclass 또는 부모 클래스라고 하고 새로운 클래스를 파생 클래스 derived class 또는 하위 클래스 subclass 또는 자식 클래스라고 합니다.

자식이 부모로부터 재산을 상속받듯이 자식 클래스도 부모 클래스가 가지고 있는 속성(데이터, 메소드)들을 물려받습니다. 구현하려고 하는 여러 클래스들의 공통된 속성을 부모클래스에 정의하고, 상속받는 클래스들에서는 자신들만의 특성에 맞는 데이터와 메소드들을 구현합니다.

예를 들면, 학생이라는 클래스가 있으면 학생은 사람이므로 사람이 가지는 공통 속성들, 이름, 성별, 나이등을 상속을 받고 학생 객체의 특징인 소속학과, 학번등을 새롭게 추가하여 사용합니다. 이렇게하면 사람 클래스를 이용해서 중복 사용되는 부분을 줄일 수 있습니다. 만일 교직원 클래스를 만들려고 할 때도 마찬가지로 사람 클래스로부터 상속받으면 공통된 부분에 대해서 중복을 피할 수 있는 것입니다.

상속 만들기

<자식클래스이름>(부모클래스)와 같이 자식클래스와 괄호 안에 부모클래스를 적어서 상속을 받습니다. Person 클래스를 부모클래스로 하고 Student 클래스를 자식클래스로하는 예를 들면 다음과 같습니다.

In [23]: class Person:
   ....: 
   ....:   def __init__(self, name, age, gender):
   ....:     self.name = name
   ....:     self.age = age
   ....:     self.gender = gender
   ....: 
   ....:   def who(self):
   ....:     print("name: {}, age: {}, gender: {}".format(self.name, self.age, self.gender))
   ....: 
   ....: class Student(Person):
   ....: 
   ....:   def __init__(self, name, age, gender, major, id):
   ....:     self.name = name
   ....:     self.age = age
   ....:     self.gender = gender
   ....:     self.major = major
   ....:     self.id = id
   ....: 
   ....: kildong = Student("홍길동", 28, "남", "수학과", 20171101)
   ....: kildong.who()
   ....: 
name: 홍길동, age: 28, gender: 남

상속 리스트가 없는 클래스는 기본으로 object 클래스를 상속받습니다.

부모클래스 생성자 부르기

위에서 Student 클래스에서 이름, 나이, 성별은 Person 클래스와 중복되는 속성입니다. 이 부분을 부모클래스의 생성자를 호출함으로 중복 코드를 제거합니다. 부모클래스의 생성자를 부르는 방법은 부모클래스이름.__init__(self) 메소드를 부르든지 super().__init__() 부르면 됩니다. 여기서 super()는 부모클래스의 객체를 의미합니다. super().__init__() 메소드에 첫번째 인수로 self는 자동으로 입력되므로 필요없습니다.

In [24]: class Person:
   ....: 
   ....:     def __init__(self, name, age, gender):
   ....:         self.name = name
   ....:         self.age = age
   ....:         self.gender = gender
   ....: 
   ....:     def who(self):
   ....:         print("name: {}, age: {}, gender: {}".format(self.name, self.age, self.gender))
   ....: 
   ....: class Student(Person):
   ....: 
   ....:     def __init__(self, name, age, gender, major, id):
   ....:         Person.__init__(self, name, age, gender)
   ....:         self.major = major
   ....:         self.id = id
   ....: 
   ....: kildong = Student("홍길동", 28, "남", "수학과", 20171101)
   ....: kildong.who()
   ....: 
name: 홍길동, age: 28, gender: 남

직접하기

  1. Animal 클래스를 구현합니다. age 속성을 설정하세요. speak() 메소드를 추가하세요.

def speak(self):
  print('동물은 소리를 냅니다')
  1. Animal 클래스를 상속하여 Pig 클래스를 만들어 보세요. Pig 클래스의 인스턴스를 만들고 메소드들을 실행해보세요.

  2. super() 함수를 이용해서 부모클래스의 생성자를 불러봅니다.

자식 인스턴스를 만들 때 자식의 __init__ 메소드가 정의되어 있지 않으면 부모의 __init__ 메소드를 실행합니다.

In [25]: class Parent:
   ....:   def __init__(self):
   ....:     print('parent init')
   ....: 
   ....: class Child(Parent):
   ....:   pass
   ....: 
   ....: c1 = Child()
   ....: 
parent init

메소드 추가하기

부모클래스를 상속하면 자식클래스는 부모의 변수와 메소드를 모두 사용할 수 있게 됩니다. 자식클래스가 부모클래스에 없는 기능이 필요하면 메소드를 추가할 수 있습니다.

In [26]: class Student(Person):
   ....: 
   ....:   def __init__(self, name, age, gender, major, id):
   ....:     Person.__init__(self, name, age, gender)
   ....:     self.major = major
   ....:     self.id = id
   ....: 
   ....:   def info(self):
   ....:     print("major: {}, id: {}".format(self.major, self.id))
   ....: 
   ....: kildong = Student("홍길동", 28, "남", "수학과", 20171101)
   ....: kildong.info()
   ....: 
major: 수학과, id: 20171101

메소드 재정의(overriding)

부모클래스의 메소드가 적당치 않으면 자식클래스에서 같은 이름으로 다르게 정의해서 사용할 수 있습니다.

In [27]: class Student(Person):
   ....: 
   ....:   def __init__(self, name, age, gender, major, id):
   ....:     super().__init__(name, age, gender)
   ....:     self.major = major
   ....:     self.id = id
   ....: 
   ....:   def info(self):
   ....:     print("major: {}, id: {}".format(self.major, self.id))
   ....: 
   ....:   def who(self):
   ....:     print("name: {}, age: {}, gender: {}, major: {}, id: {}".format(self.name, self.age, self.gender, self.major, self.id))
   ....: 
   ....: kildong = Student("홍길동", 28, "남", "수학과", 20171101)
   ....: kildong.who()
   ....: 
name: 홍길동, age: 28, gender: 남, major: 수학과, id: 20171101

C++, Java에서 메소드 재정의(method overriding)는 메소드 이름과 매개변수, 반환값이 모두 같아야하지만 파이썬은 이름만 같아도 됩니다.

직접하기

  1. Person 클래스에 메소드를 추가하고 Student 클래스에서 메소드 재정의를 해서 사용해봅니다.

  2. Student 클래스 재정의된 메소드의 매개변수를 바꿔서 실행해봅니다.

  3. 위에서 정의한 Pig, Dog 클래스의 speak() 메소드를 재정의 해보세요.

  4. User 클래스를 상속하여 Admin 클래스를 만들고 can add post, can delete post, can ban user 등의 문자열을 포함하는 privileges 리스트를 속성으로 추가하세요. show_privileges() 메소드를 추가하여 권한들을 출력하도록 하세요. 인스턴스를 만들어 메소드를 실행해보세요.

  5. Privileges 클래스를 만들어 privileges 속성을 추가하고 Admin에 정의되었던 show_privileges() 메소드를 옮겨 주세요. Admin 클래스에 Privileges 인스턴스를 만들어 속성을 대체하세요. Admin 인스턴스를 만들어 권한을 출력해보세요.

__str__() 메소드

__str__ 메소드를 이용하면 클래스 출력값을 변경할 수 있습니다. 다음과 같이 Person 클래스에 __str__ 메소드를 재정의합니다.

class Person:

    def __init__(self, 이름, 나이, 성별):
        self.이름 = 이름
        self.나이 = 나이
        self.성별 = 성별

    def __str__(self):
        return "저는 {}입니다. 나이는 {}세, {}입니다.".format(self.이름, self.나이, self.성별)

Person 클래스의 인스턴스를 직접 출력해봅니다.

영희 = Person('김영희', 25, '여자')
print(영희)
저는 김영희 입니다. 나이는 25, 여자 입니다.

상속과 이름공간

위에서 인스턴스 객체를 통해 변수나 함수의 이름을 찾는 규칙은 인스턴스 객체 영역 -> 클래스 객체 영역 -> 전역 영역 순서였습니다. 상속 관계에 있는 클래스의 인스턴스 객체의 이름 규칙은 인스턴스 객체 영역 -> 클래스 객체 영역 -> 부모 클래스 객체 영역 -> 전역 영역 순입니다.

변수 = "전역 변수"

class 부모:
    변수 = "부모변수"

    def __init__(self):
        pass

    def 메소드(self):
        print("부모 메소드")

class 자식(부모):
    변수 = "자식 변수"

    def __init__(self):
        pass

    def 메소드(self):
        print("자식 메소드")

 = 자식()
.메소드()
자식 메소드

직접하기

  • 자식 클래스의 메소드()에서 부모 클래스의 메소드()를 불러봅니다.

  • 자식 클래스의 메소드() 안에 다음 문장을 넣고 실행해보고 이야기 해봅니다.

    print(변수)
    print(self.변수)
    
  • 자식 클래스의 메소드() 안에서 부모 클래스의 변수를 출력해봅니다.

특별한 메소드들

파이썬 클래스는 특별한 메소드들을 제공합니다. special method 또는 magic method 또는 double under method(줄여서 dunder method라고도 불립니다.)라는 메소드들이 있습니다. 앞에서 봤던 __init__ 메소드가 예입니다. 그외에도 여러 가지 특별한 메소드들이 있습니다.

수학 기호와 관련된 특별한 메소드들

__add__는 덧셈 기호 +를 사용할 수 있는 연산을 정의합니다. __sub__- 기호에 대한 정의를 합니다. 자세한 것은 이곳을 참조하세요.

메소드

기호

설명

__add__(self, other)

+

덧셈

__sub__(self, other)

-

뺄셈

__mul__(self, other)

*

곱셈

__matmul__(self, other)

@

행렬 곱

__truediv__(self, other)

/

나눗셈

__floordiv__(self, other)

//

__mod__(self, other)

%

나머지

__divmod__(self, other)

divmod()

몫과 나머지 순서쌍

__pow__(self, other)

**

거듭제곱

__lshift__(self, other)

<<

왼쪽 시프트

__rshift__(self, other)

>>

오른쪽 시프트

__and__(self, other)

&

논리곱

__xor__(self, other)

^

배타적 합

__or__(self, other)

|

논리합

__call__ 메소드

__call__ 메소드는 클래스 객체를 함수 호출하듯이 부를 수 있습니다.

In [28]: class MyFun:
   ....:   def __init__(self, a, b):
   ....:     self.a = a
   ....:     self.b = b
   ....: 
   ....:   def value(self, x):
   ....:     return self.a * x + self.b
   ....: 
   ....:   def __call__(self, x):
   ....:     return self.a * x + self.b
   ....: 

함수의 객체를 만듭니다.

In [29]: f1 = MyFun(1, 1)
   ....: f1.value(10)
   ....: 
Out[29]: 11

객체를 함수 호출하듯이 사용할 수 있습니다.

In [30]: f1(10)
Out[30]: 11

수치 미분에 응용

함수 f(x)에 대한 수치 미분은 다음과 같이 구할 수 있습니다.

\[f'(x) \approx \frac{f(x+h) - f(x)}{h}\]

이러한 미분을 클래스로 정의하여 모든 함수에 대해서 미분값을 근사할 수 있습니다.

다음은 수치 미분을 Derivative란 이름의 클래스로 구현한 것입니다. 인스턴스를 만들 때 인수로 함수 f를 입력받고 __call__ 메소드를 호출하여 그 함수의 미분값을 반환합니다.

In [31]: class Derivative:
   ....:   def __init__(self, f, h=1E-5):
   ....:     self.f = f
   ....:     self.h = h
   ....: 
   ....:   def __call__(self, x):
   ....:     f, h = self.f, self.h # 간단한 이름으로 변경
   ....:     return (f(x+h) - f(x)) / h
   ....: 

다음은 일차함수 \(y = 3x +1\) 을 수치미분한 것입니다.

In [32]: f = lambda x: 3 * x + 1
   ....: df = Derivative(f)
   ....: df(2)
   ....: 
Out[32]: 3.000000000064062

직접하기

  1. Derivative 클래스를 이용하여 함수 f(x) = sin(x)의 수치미분을 x=1 에서 구해보세요.

참조

파이썬 객체는 내부적으로 객체를 참조하는 갯수를 세고 있다가 참조수(reference count)가 0이 되면 객체를 삭제합니다. __del__() 메소드를 이용하여 객체가 삭제될 때 작업을 할 수 있습니다.

 1  class A:
 2
 3      def __init__(self, name):
 4          self.name = name
 5          print("{}가 만들어졌습니다.".format(self.name))
 6
 7      def __del__(self):
 8          print("{}가 삭제되었습니다.".format(self.name))
 9
10  a1 = A("객체")
11  del a1
객체가 만들어졌습니다.
객체가 삭제되었습니다.
  • 10줄: 객체가 만들어져서 a1이라는 변수가 객체를 참조하고 있습니다.

  • 11줄: 참조를 삭제하면 객체가 삭제되었습니다라는 메시지를 볼 수 있습니다.

a1 = A("객체1")
a2 = A("객체2")
del a1, a2
객체1가 만들어졌습니다.
객체2가 만들어졌습니다.
객체1가 삭제되었습니다.
객체2가 삭제되었습니다.

아래의 경우는 a, b 변수가 동시에 하나의 객체를 참조하고 있습니다.

1  a = A("객체")
2  b = a
3  del a
객체가 만들어졌습니다.
  • 3줄: a를 제거하고 있지만 b라는 변수가 여전히 객체를 참조하고 있기 때문에 삭제되지 않습니다.

1  a = A("객체")
2  b = a
3  del a
4  del b
객체가 만들어졌습니다.
객체가 삭제되었습니다.
객체가 삭제되었습니다.
  • 4줄: ab를 삭제해야만 객체가 제거되는 것을 볼 수 있습니다.

순환참조(cycle reference)

다음과 같이 순환참조를 할 경우 객체 참조를 제거해도 객체는 계속 남아있게 됩니다.

 1  class A:
 2
 3      def __init__(self, name):
 4          self.name = name
 5          print("{}가 만들어졌습니다.".format(self.name))
 6
 7      def __del__(self):
 8          print("{}가 삭제되었습니다.".format(self.name))
 9
10  a1 = A("객체1")
11  a2 = A("객체2")
12
13  a1.ref = a2
14  a2.ref = a1
15
16  del a1, a2
객체1가 만들어졌습니다.
객체2가 만들어졌습니다.
  • 13, 14줄: a1.ref = a2를 통해서 객체1 안의 변수 ref객체2를 참조하고 반대로 a2.ref = a1을 통해 객체2 안의 변수 ref객체1을 참조하고 있습니다.

  • 16줄: a1, a2 참조를 제거해도 a1.ref, a2.ref 참조가 각각의 객체를 참조하고 있기 때문에 객체1, 객체2는 제거되지 않습니다.

아래 그림을 참조 합니다.

만일 객체2를 제거하기 원합다면 먼저 객체2의 참조들을 삭제해야 합니다.

1  a1 = A("객체1")
2  a2 = A("객체2")
3
4  a1.ref = a2
5  a2.ref = a1
6
7  del a2, a1.ref
8  del a1
객체1가 만들어졌습니다.
객체2가 만들어졌습니다.
객체2가 삭제되었습니다.
객체1가 삭제되었습니다.
  • 7줄: a2 참조와, a1.ref 참조를 차례로 제거하면 더이상 객체2에 대한 참조는 존재하지 않으므로 객체2가 삭제가 됩니다.

  • 그러면 a2.ref 참조도 자동적으로 삭제가 됩니다.

  • 8줄: a1을 삭제하면 객체1 참조들도 모두 사라져서 객체1이 제거됩니다.

참조 사이트