07. 객체지향 프로그래밍

객체지향 프로그래밍

  • 객체(Object)

  • 객체지향프로그래밍(Object Oriented Programming)

  • 클래스(Class)와 인스턴스(Instance)

  • 변수, 이름공간, 메서드

  • 상속

7.1 객체

Python에서 모든 것은 객체(object) 입니다.

모든 객체는 타입(type), 속성(attribute), 조작법(method) 을 가집니다.

객체(Object)의 특징

  • 타입(type): 어떤 연산자(operator)와 조작(method)이 가능한가?

  • 속성(attribute): 어떤 상태(데이터)를 가지는가?

  • 조작법(method): 어떤 행위(함수)를 할 수 있는가?

7.1.1 타입과 인스턴스

type

instance

int

0, 1, 2

str

'', 'hello', '123'

list

[], ['a', 'b']

dict

{}, {'key': 'value'}

타입 (Type)

  • 공통된 속성 (attribute)과 조작법 (method)을 가진 객체들의 분류

인스턴스 (Instance)

  • 특정 타입(type)의 실제 데이터 예시(instance)입니다.

  • 파이썬에서 모든 것은 객체이고, 모든 객체는 특정 타입의 인스턴스입니다.

a = 10
b = 20

# a, b는 10, 20을 가리키는 이름         -- namespace
# 10, 20 (a, b가 가리키는 대상)은 객체   -- object
# 10, 20 은 int 타입(type)의 인스턴스
# a 의 type이 int형인지 is 연산자로 확인해 봅시다.

a = 10
type(a) is int
True

int()

int()int Instance를 만드는 함수입니다.

# isinstance 함수를 활용해 확인해 봅시다.

isinstance(a, int)
True

7.1.2 속성과 메서드

객체의 속성(상태, 데이터)과 조작법(함수)을 명확히 구분해 봅시다.

type

attributes

methods

complex

.real, .imag

str

_

.capitalize(), .join(), .split()

list

_

.append(), .reverse(), .sort()

dict

_

.keys(), .values(), .items()

속성 (Attribute)

  • 속성(attribute)은 객체(object)의 상태/데이터를 뜻합니다.

활용법

<객체>.<속성>

예시

3+4j.real
  • complex 타입 인스턴스가 가진 속성을 확인해봅시다.

# 복소수를 만들어보고, 타입을 출력해봅시다.

img_number = 3 + 4j
print(type(img_number))
<class 'complex'>
# 허수부랑 실수부 .real과 .imag 속성을 통해 각각 출력해봅시다. 
# complex 객체의 실수 속성과 허수 속성이라고도 표현 가능합니다.

print(img_number.real)
print(img_number.imag)
3.0
4.0

메서드 (Method)

  • 특정 객체에 적용할 수 있는 행위(behavior)를 뜻 합니다.

활용법

<객체>.<메서드>()

예시

[3, 2, 1].sort()
  • list type의 인스턴스에 적용 가능한 조작법(method)을 확인해 봅시다.

# 리스트를 하나 만들고 정렬해봅시다. 
# 리스트 타입 객체의 sort() 메서드로 정렬 가능합니다.

numbers = [3, 2, 1]
numbers.sort()
numbers
[1, 2, 3]
# list 타입의 객체들이 할 수 있는 것들을 알아봅시다. (list 타입 객체가 가지고 있는 모든 속성과 메서드를 보여줍니다.)

print(dir(list()))
['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
# 1 + 1

(1).__add__(1)
2

7.1.3 객체 비교

==

  • 동등한 (equal)

  • 변수가 참조하는 객체가 동등한 (내용이 같은) 경우 True

  • 두 객체가 같아 보이지만 실제로 동일한 대상을 가리키고 있다고 확인해 준 것은 아님

is

  • 동일한 (identical)

  • 두 변수가 동일한 객체를 가리키는 경우 True


7.2 객체지향 프로그래밍

객체지향 프로그래밍 (Object-Oriented Programming) : Object가 중심(oriented)이 되는 프로그래밍

wikipedia - 객체지향 프로그래밍

객체 지향 프로그래밍(영어: Object-Oriented Programming, OOP)은 컴퓨터 프로그래밍의 패러다임의 하나입니다.

객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 “객체”들의 모임으로 파악하고자 하는 것입니다.

7.2.1 절차 중심 vs. Object 중심

프로그래밍 패러다임: 어떻게 프로그램을 작성할 것인가

7.2.2 Object 중심의 장점

wikipedia - 객체지향 프로그래밍

객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용됩니다.

또한 프로그래밍을 더 배우기 쉽게 하고 소프트웨어 개발과 보수를 간편하게 하며, 보다 직관적인 코드 분석을 가능하게 하는 장점을 갖고 있습니다.

  • 코드의 직관성

  • 활용의 용이성

  • 변경의 유연성


7.3 클래스와 인스턴스

클래스 (class) : 객체들의 분류(class)를 정의할 때 쓰이는 키워드

인스턴스 (instance) : 객체의 실체와 예, 인스턴스(instance)를 활용

7.3.1 클래스 (Class) 생성

  • 클래스 생성은 class 키워드와 정의하고자 하는 <클래스의 이름>으로 가능합니다.

  • <클래스의 이름>PascalCase로 정의합니다.

  • 클래스 내부에는 데이터와 함수를 정의할 수 있고, 이때 데이터는 속성(attribute) 정의된 함수는 **메서드(method)**로 부릅니다.


활용법

class <클래스이름>:
    <statement>
class ClassName:
    statement
# Person 이라는 이름의 class를 정의해봅시다. 
# 아래와 같이 """(docstring)을 통해 설명(doc)도 함께 정의해 봅시다.
class ClassName:
    """
    This is DOCSTRING
    """
class Person:
    """
    이것은 Person 클래스(class) 입니다.
    """
print(Person.__doc__)
    이것은 Person 클래스(class) 입니다.
    
print(dir(Person))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
# 정의한 Person 클래스의 type을 출력해 봅시다

type(Person)
type

7.3.2 인스턴스 (Instance) 생성

  • 정의된 클래스(class)에 속하는 객체를 해당 클래스의 인스턴스(instance)라고 합니다.

  • Person 클래스의 인스턴스는 Person()을 호출함으로써 생성됩니다.

  • type() 함수를 통해 생성된 객체의 클래스를 확인할 수 있습니다.

활용법

# 인스턴스 = 클래스()
person1 = Person()
  • person1은 사용자가 정의한(user-defined) Person이라는 데이터 타입(data type)의 인스턴스입니다.

# Person 클래스의 인스턴스 p1과 p2를 생성해봅시다.
# 두 인스턴스의 type을 출력하고
# 우리가 위에서 정의한 doc를 p1.__doc__, p2.__doc__ 속성을 통해 출력해봅시다.

p1 = Person()
p2 = Person()

print(type(p1), type(p2))
print(p1.__doc__, p2.__doc__)
<class '__main__.Person'> <class '__main__.Person'>

    이것은 Person 클래스(class) 입니다.
     
    이것은 Person 클래스(class) 입니다.
    

7.3.3 메서드 (Method) 정의

특정 데이터 타입(또는 클래스)의 객체에 공통적으로 적용 가능한 행위(behavior)들을 의미합니다.


활용법

class Person:
    # 메서드(method)
    def talk(self):    # 인자로 self를 정의해봅시다.
        print('안녕')
# 위와 똑같이 Person 클래스와 talk() 메서드를 정의해봅시다.

class Person:
    # 메서드(method)
    def talk(self):    # 인자로 self를 정의해봅시다.
        print('안녕')
# Person 클래스의 인스턴스 p1, p2를 생성하고 각각 talk 메서드를 실행해봅시다

p1, p2 = Person(), Person()
p1.talk()
p2.talk()
안녕
안녕
# 메서드도 함수이기 때문에 추가적인 인자를 받을 수 있습니다.
# Person 클래스를 재정의 하며 talk() 메서드를 정의하고, eat() 메서드를 추가로 정의해봅시다.
# eat() 메서드는 food 라는 이름의 인자를 받아서 출력합니다.
# 추가적인 인자를 받기 위해서는 아래와 같은 모습으로 코드가 작성되어야 합니다.
class MyClass:
    def method1(self):
        print('Hi')
    
    def method2(self, arg):
        print(arg)
# 기본 인자, 가변 인자 리스트 등 함수의 인자와 동일하게 매개변수를 정의할 수 있습니다.
# Person 클래스를 재정의하며, eat() 메서드를 정의할 때 food 인자의 기본 값을 원하는 음식으로 설정해봅시다.

class Person:
    # 메서드(method)
    def talk(self):    # 인자로 self를 정의해봅시다.
        print('안녕')
        
    def eat(self, food = '치킨 먹고싶다..'):
        print (f'{food} 냠냠')
# Person 클래스의 인스턴스 p1을 생성하고 eat() 메서드를 인자 있이/없이 두번 호출해봅시다.

p1 = Person()
p1.eat()
p1.eat('치킨')
치킨 먹고싶다.. 냠냠
치킨 냠냠

self

인스턴스 자신(self)

  • Python에서 인스턴스 메서드는 호출 시 첫번째 인자로 인스턴스 자신이 전달되게 설계되었습니다.

  • 보통 매개변수명으로 self를 첫번째 인자로 정의 (다른 이름도 가능하지만 추천하지는 않습니다.)

# 아래의 Person 클래스를 통해서 self가 무엇인지 확인해봅시다.

class Person:
    
    # self => 인스턴스 메서드의 1번 인자
    def test(self):
        print(self)
# Person 클래스의 인스턴스 p1을 생성하고 test() 메서드를 호출하여 self를 확인해봅시다.

p1 = Person()
p1.test()
p1
<__main__.Person object at 0x000001E400E77670>
<__main__.Person at 0x1e400e77670>
# p1을 출력하여 비교해봅시다.

p2 = Person()
p2.test()
p2
<__main__.Person object at 0x000001E400E774C0>
<__main__.Person at 0x1e400e774c0>

생성자 (constructor) 메서드

인스턴스 객체가 생성될 때 호출되는 함수. 반드시 __init__ 이라는 이름으로 정의.


활용법

class MyClass:
    def __init__(self):
        print('생성될 때 자동으로 호출되는 메서드입니다.')
  • 생성자를 활용하면 인스턴스가 생성될 때 인스턴스의 속성을 정의할 수 있습니다.

class Person:
    def __init__(self, name):
        self.name = name
    
    def talk(self):
        print(f'I am {self.name}')
# 자신의 이름을 인자로 넘기며 Person의 인스턴스 p1를 생성해봅시다.

p1 = Person('john')
# talk() 메서드를 호출해 봅시다.

p1.talk()
I am john
# talk 메서드를 클래스에서 메서드를 실행해봅시다. self는 정말 빈자리일까요?

Person.talk(p1) # 확인용 코드입니다. 이렇게 작성하지 않습니다.
I am john

소멸자 (destructor) 메서드

  • 인스턴스 객체가 소멸(파괴)되기 직전에 호출되는 함수. 반드시 __del__ 이라는 이름으로 정의.


활용법

def __del__(self):
    print('소멸될 때 자동으로 호출되는 메서드입니다.')
# 생성자와 소멸자를 만들어봅시다.
# 생성자 메서드는 __init__으로, 소멸자 메서드는 __del__라는 이름으로 정의합니다.

class Person:
    def __init__(self):
        print('응애!')
        
    def __del__(self):
        print('떠날게..')
p3 = Person()
del p3  # 변수 삭제
응애!
떠날게..
p4 = Person()
p4 = list()  # 변수 재할당
응애!
떠날게..
p5 = Person()
p6 = p5  # 인스턴스가 어딘가에 남겨져 있을 경우 호출되지 않습니다.
del p5
응애!
p6 = 1
떠날게..

7.3.4 속성 (Attribute) 정의

특정 데이터 타입(또는 클래스)의 객체들이 가지게 될 상태/데이터를 의미합니다.

self.<속성명> = <값> 혹은 <인스턴스>.<속성명> = <값>으로 설정합니다


활용법

class Person:
    def __init__(self, name):
        self.name = name
        
    def talk(self):
        print(f'안녕, 나는 {self.name}')
# 인스턴스의 속성, 즉 개별 인스턴스들이 사용할 데이터를 정의해봅시다.

class Person:
    def __init__(self, name):
        self.name = name  # 일반적으로 초기화할 때 설정합니다.
        
    def talk(self):
        print(f'안녕, 나는 {self.name}')
# 속성 정의는 꼭 생성자(__init__) 메서드에 작성해야 하지는 않지만, 일반적으로 생성자 메서드에 많이 작성합니다.
# 생성자 메서드를 통해 생성과 동시에 인스턴스 속성에 값을 할당할 수 있기 때문입니다.
# 새로운 Person의 인스턴스 p1을 이름과 함께 초기화 하고, 이름을 출력해봅시다.

p1 = Person('아이유')
p1.talk()
안녕, 나는 아이유
# 인스턴스 속성의 값을 변경할 수도 있습니다.
# 위에서 생성한 p1 인스턴스의 name을 다른 값으로 할당해보고, talk 메서드를 실행해봅시다.

p1.name = '이지은'
p1.talk()
안녕, 나는 이지은
# 생성자 메서드도 함수이기 때문에, 인자의 개수가 맞지 않으면 에러가 발생합니다.
# name인자 없이 새로운 Person의 인스턴스 p1을 생성해봅시다.

p1 = Person()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_9912/1046008949.py in <module>
      2 # name인자 없이 새로운 Person의 인스턴스 p1을 생성해봅시다.
      3 
----> 4 p1 = Person()

TypeError: __init__() missing 1 required positional argument: 'name'
# 위에서 생성한 인스턴스 p1을 del 연산자를 사용해 소멸시켜봅시다.

del p1

7.3.5 매직 (스페셜) 메서드

  • 더블언더스코어(__)가 있는 메서드는 특별한 일을 하기 위해 만들어진 메서드이기 때문에 스페셜 메서드 혹은 매직 메서드라고 불립니다.

  • 매직(스페셜) 메서드 형태: __someting__

 '__str__(self)',
 '__len__(self)',
 '__repr__(self)',
 '__lt__(self, other)',
 '__le__(self, other)',
 '__eq__(self, other)',
 '__ne__(self, other)',
 '__gt__(self, other)',
 '__ge__(self, other)',

__str__(self)

class Person:
    def __str__(self):
        return '객체 출력(print)시 보여줄 내용'
  • 특정 객체를 출력(print()) 할 때 보여줄 내용을 정의할 수 있습니다.

# dir() 함수를 통해 문자열 인스턴스가 활용 가능한 메서드를 확인해봅시다.

dir('')
['__add__',
 '__class__',
 '__contains__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getitem__',
 '__getnewargs__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__iter__',
 '__le__',
 '__len__',
 '__lt__',
 '__mod__',
 '__mul__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__rmod__',
 '__rmul__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 'capitalize',
 'casefold',
 'center',
 'count',
 'encode',
 'endswith',
 'expandtabs',
 'find',
 'format',
 'format_map',
 'index',
 'isalnum',
 'isalpha',
 'isascii',
 'isdecimal',
 'isdigit',
 'isidentifier',
 'islower',
 'isnumeric',
 'isprintable',
 'isspace',
 'istitle',
 'isupper',
 'join',
 'ljust',
 'lower',
 'lstrip',
 'maketrans',
 'partition',
 'removeprefix',
 'removesuffix',
 'replace',
 'rfind',
 'rindex',
 'rjust',
 'rpartition',
 'rsplit',
 'rstrip',
 'split',
 'splitlines',
 'startswith',
 'strip',
 'swapcase',
 'title',
 'translate',
 'upper',
 'zfill']
# Person 클래스를 정의해봅시다.

class Person:
    def __init__(self, name):
        self.name = name
# Person의 인스턴스 p1을 생성 후 출력해봅시다.

p1 = Person('john')
print(p1)
<__main__.Person object at 0x000001E400E77DC0>
# __str__() 매직메서드는 아래와 같이 정의합니다.

class Person:
    def __init__(self, name):
        self.name = name
    
    def __str__(self):  # __str__은 기억해두세요.
        return f'나는 {self.name}'
    
    def __repr__(self):
        return f'I\'m {self.name}'
# 새로운 인스턴스 p2를 생성후 p2를 출력해봅시다.

p2 = Person('jane')
print(p2)
p2
나는 jane
I'm jane

실습

매직메서드를 활용하여 인스턴스간의 비교연산(>, ==)이 가능하도록 매직메서드를 정의해봅시다.

Person 클래스를 정의합니다. 인스턴스 속성은 nameage를 가지며, 인스턴스간 age 비교가 가능해야합니다.

__gt__메서드와 __eq__메서드를 활용합니다.

# 실습
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __str__(self):
        return f'나는 {self.name}'
    
    def __gt__(self, other):
        return self.age > other.age
    
    def __eq__(self, other):
        return self.age == other.age
# 아래 코드가 정상 동작하도록 해봅시다.

p1 = Person('1', 1)
p2 = Person('2', 2)
p3 = Person('3', 1)

print(p1 > p2)
print(p1 == p3)
print(p1 == p2)
False
True
False

정리

  • 객체 (Object) : 객체는 자신 고유의 속성(attribute)을 가지며 클래스에서 정의한 행위(behavior) 를 수행

  • 클래스 (Class) : 공통된 속성(attribute)과 행위(behavior)를 정의한 것으로 객체지향 프로그램의 기본적인 사용자 정의 데이터형(user-defined data type)

  • 인스턴스 (Instance) : 특정 class로부터 생성된 해당 클래스의 실체/예시(instance)

  • 속성 (Attribute) : 클래스/인스턴스가 가지는 속성(값/데이터)

  • 메서드 (Method) :클래스/인스턴스에 적용 가능한 조작법(method) & 클래스/인스턴스가 할 수 있는 행위(함수)

7.4 변수, 이름공간, 메서드

  • 인스턴스 & 클래스 변수

  • 인스턴스 & 클래스간의 이름공간

  • 인스턴스 & 클래스 메서드(+ 스태틱 메서드)

7.4.1 인스턴스 & 클래스 변수

인스턴스 변수

  • 인스턴스의 속성(attribute)

  • 각 인스턴스들의 고유한 변수

  • 생성자 메서드에서 self.변수명로 정의

  • 인스턴스가 생성된 이후 인스턴스.변수명로 접근 및 할당


활용법

class Person:

    def __init__(self, name):    # 인스턴스 메서드 (생성자) 
        self.name = name         # 인스턴스 변수
# 위와 같은 Person 클래스를 똑같이 정의해봅시다.

class Person:

    def __init__(self, name):    # 인스턴스 메서드 (생성자) 
        self.name = name 
    
    def hi(self):
        return 'hi'

p1 = Person('아이유')
p1.hi()
'hi'
# Person 클래스의 인스턴스 me, you를 각각 이름과 함께 생성하고, name 속성을 출력해봅시다.

me = Person('연희')
you = Person('지은')

me.name, you.name
('연희', '지은')

클래스 변수

  • 클래스의 속성(attribute)

  • 모든 인스턴스가 공유

  • 클래스 선언 내부에서 정의

  • 클래스.변수명으로 접근 및 할당


활용법

class Circle:
    pi = 3.14
    
print(Circle.pi)
# 위의 예시 코드대로 Circle 클래스를 생성해봅시다.

class Circle:
    pi = 3.14
    
print(Circle.pi)
3.14
# Circle 클래스의 인스턴스 c1, c2를 생성해봅시다.

c1 = Circle()
c2 = Circle()
# 클래스 변수 pi에 접근해봅시다.

Circle.pi
3.14
# 인스턴스 c1, c2에서 pi 값을 출력해봅시다.

c1.pi, c2.pi
(3.14, 3.14)
# c1의 pi 값을 3.141592로 변경하여 봅시다.

c1.pi = 3.141592
# c1, c2에서의 pi값을 각각 출력해봅시다.

c1.pi, c2.pi
(3.141592, 3.14)
# Circle의 pi를 출력해봅시다.

print(Circle.pi)
3.14

비교

  • 클래스 변수

    • 클래스 정의 안에 (+ 인스턴스 메서드 밖에) 선언

    • 특정 클래스 인스턴스에 묶여 있지 않음

    • 클래스 자체의 내용을 저장

    • 같은 클래스에서 생성된 모든 객체는 동일한 클래스 변수를 공유

  • 인스턴스 변수

    • 항상 특정 인스턴스에 묶여 있음

    • 클래스에 저장되지 않고 클래스에서 생성된 개별 객체에 저장

    • 인스턴스 마다 완전히 독립적이므로 변수의 값을 수정하면 오로지 해당 객체에만 영향을 미침

클래스 변수와 인스턴스 변수의 함정
  • 새 Cat 인스턴스를 만들고 각 인스턴스는 name이라는 인스턴스 변수를 얻음

7.4.2 인스턴스 & 클래스간의 이름공간

이름공간 탐색 순서

  • 클래스를 정의하면, 클래스가 생성됨과 동시에 이름 공간(namespace)이 생성됩니다.

  • 인스턴스를 만들게 되면, 인스턴스 객체가 생성되고 해당되는 이름 공간이 생성됩니다.

  • 인스턴스의 어트리뷰트가 변경되면, 변경된 데이터를 인스턴스 객체 이름 공간에 저장합니다.

  • 즉, 인스턴스에서 특정한 어트리뷰트에 접근하게 되면 인스턴스 => 클래스 순으로 탐색을 합니다.

class Person:
    name = 'unknown'
# Person 클래스의 인스턴스 p1을 생성하고 name을 확인해봅시다.

p1 = Person()
p1.name
'unknown'
# p1의 name 속성을 직접 변경하고 확인해봅시다.

p1.name = 'Jack'
print(p1.name) #=> 인스턴스의 name
print(Person.name) #=> 클래스의 name
Jack
unknown
  • class와 instance는 서로 다른 namespace를 가지고 있습니다.

  • 인스턴스에 해당 이름이 없으면 클래스에서 탐색을 합니다.

%%html
<iframe width="800" height="500" frameborder="0" src="https://pythontutor.com/iframe-embed.html#code=class%20Person%3A%0A%20%20%20%20name%20%3D%20'unknown'%0A%0Ap2%20%3D%20Person%28%29%0Aprint%28p2.name%29%0Ap2.name%20%3D%20'Jack'%0Aprint%28p2.name%29%0Aprint%28Person.name%29&codeDivHeight=400&codeDivWidth=350&cumulative=false&curInstr=0&heapPrimitives=nevernest&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe>

7.4.3 메서드의 종류

인스턴스 메서드 (instance method)

  • 인스턴스가 사용할 메서드

  • 클래스 내부에 정의되는 메서드의 기본값은 인스턴스 메서드

  • 호출시, 첫번째 인자로 인스턴스 자기자신 self가 전달됩니다


활용법

class MyClass:
    def instance_method(self, arg1, arg2, ...):
        ...

my_instance = MyClass()
# 인스턴스 생성 후 메서드를 호출하면 자동으로 첫 번째 인자로 인스턴스(my_instance)가 들어갑니다.
my_instance.instance_method(.., ..)  

클래스 메서드 (class method)

  • 클래스가 사용할 메서드

  • @classmethod 데코레이터를 사용하여 정의

  • 호출시, 첫 번째 인자로 클래스 cls가 전달됩니다


활용법

class MyClass:
    @classmethod
    def class_method(cls, arg1, arg2, ...):
        ...

# 자동으로 첫 번째 인자로 클래스(MyClass)가 들어갑니다.
MyClass.class_method(.., ..)  

스태틱 메서드 (static method)

  • 클래스가 사용할 메서드

  • @staticmethod 데코레이터를 사용하여 정의

  • 호출시, 어떠한 인자도 전달되지 않습니다


활용법

class MyClass:
    @staticmethod
    def static_method(arg1, arg2, ...):
        ...

# 아무런 일도 자동으로 일어나지 않습니다.
MyClass.static_method(.., ..)
# MyClass 클래스를 정의해 두었습니다.
class MyClass:
    def instance_method(self):
        return self
    
    @classmethod
    def class_method(cls):
        return cls
    
    @staticmethod
    def static_method(arg):
        return arg
# MyClass 클래스의 인스턴스 mc를 생성해봅시다.

mc = MyClass()
mc.instance_method()
<__main__.MyClass at 0x1e400f6d610>
# 인스턴스 메서드를 호출하여 반환된 결과(self)와 인스턴스(mc)를 비교해봅시다.
# 1. id를 출력해 보고, 같은 id인지 확인
# 2. == 연산자를 확인해 비교

print(id(mc.instance_method()), id(mc))
print(mc.instance_method() == mc)
2078780347920 2078780347920
True
# 클래스 메서드를 호출하여 반환된 결과(cls)와 인스턴스(mc)를 비교해봅시다.
# 1. id를 출력해 보고, 같은 id인지 확인
# 2. == 연산자를 확인해 비교

print(id(MyClass.class_method()), id(MyClass))
print(MyClass.class_method() == MyClass)
2078768354784 2078768354784
True
# 스태틱 메서드를 호출하고 반환된 결과(arg)를 확인해봅시다.
# 어떠한 인자도 전달되는 것이 없습니다.

print(MyClass.static_method(1))
1

7.4.4 비교 정리

인스턴스와 메서드

  • 인스턴스는 3가지 메서드 모두에 접근할 수 있습니다.

  • 하지만, 인스턴스가 할 행동은 모두 인스턴스 메서드로 한정 지어서 설계합니다.

  • 인스턴스에서 클래스 메서드와 스태틱 메서드는 되도록 호출하지 않아야 합니다. (가능하다 != 사용한다)

# 위의 MyClass를 활용하여 클래스 입장에서 확인해봅시다.
# 클래스 메서드를 호출해봅시다.

MyClass.class_method()
__main__.MyClass
# 스태틱 메서드를 호출해봅시다.

MyClass.static_method(1)
1
# 클래스로 인스턴스 메서드를 호출해봅시다.
# MyClass.instance_method(mc)는 mc.instance_method()와 같습니다.

print(MyClass.instance_method())  # Error => 첫 번째 인자인 인스턴스 객체가 없습니다.
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_9912/3496252957.py in <module>
      2 # MyClass.instance_method(mc)는 mc.instance_method()와 같습니다.
      3 
----> 4 print(MyClass.instance_method())  # Error => 첫 번째 인자인 인스턴스 객체가 없습니다.

TypeError: instance_method() missing 1 required positional argument: 'self'
print(MyClass.instance_method(mc))  # mc.instance_method()와 같습니다.
<__main__.MyClass object at 0x000001E400F6D610>

클래스와 메서드

  • 클래스가 할 행동은 다음 원칙에 따라 설계합니다. (클래스 메서드와 정적 메서드)

    • 클래스 자체(cls)와 그 속성에 접근할 필요가 있다면 클래스 메서드로 정의합니다.

    • 클래스와 클래스 속성에 접근할 필요가 없다면 정적 메서드로 정의합니다.

      • 정적 메서드는 cls, self와 같이 묵시적인 첫번째 인자를 받지 않기 때문

[코드예시] Puppy

  • Puppy 클래스의 속성에 접근하는 클래스 메서드를 생성해 봅시다.

  • 클래스 변수 population를 통해 강아지가 생길 때마다 증가 시키도록 하겠습니다.

  • 강아지들은 각자의 이름(name)과 종(breed)을 갖고 있습니다.

  • bark() 메서드를 호출하면 짖을 수 있습니다.

class Puppy:
    population = 0                        # class 변수
    
    def __init__(self, name, breed):      # 생성자 함수 (constructor)
        self.name = name                  # instance 변수 초기화
        self.breed = breed
        
        Puppy.population += 1             # class 변수 값 변경
    
    def __del__(self):                    # 소멸자
        Puppy.population -= 1
    
    def bark(self):                       # 인스턴스 메서드
        print(f'왈왈! 나는 {self.name}, {self.breed}야')  # 인스턴스 변수에 접근
    
    @classmethod                          # 클래스 메서드
    def get_population(cls):
        print(f'현재 강아지 마리수: {cls.population}')  # 클래스 변수에 접근
        return cls.population   
# Puppy 클래스의 인스턴스 p1, p2, p3를 생성해봅시다.
# name과 breed는 자유롭게 설정합니다.

p1 = Puppy('봄이', '사모예드')
p2 = Puppy('코코', '폼피츠')
p3 = Puppy('스노이', '말티즈')
# 각 인스턴스마다 bark 메서드를 실행해봅시다.

p1.bark()
p2.bark()
p3.bark()
왈왈! 나는 봄이, 사모예드야
왈왈! 나는 코코, 폼피츠야
왈왈! 나는 스노이, 말티즈야
# 클래스 메서드 get_population을 호출해봅시다.

Puppy.get_population()
현재 강아지 마리수: 3
3
# 위의 Puppy 클래스를 처음부터 정의해봅시다.
# 추가로 스태틱 메서드 info를 정의하고 호출해봅시다.
# info 메서드는 '이것은 Puppy 클래스입니다!' 라는 문자열을 return 합니다.

class Puppy:
    population = 0
    
    def __init__(self, name, breed):
        self.name = name
        self.breed = breed
        Puppy.population += 1
        
    def __del__(self):
        Puppy.population -= 1
    
    def bark(self):
        print(f'왈왈! 나는 {self.name}, {self.breed}(이)야')
    
    @classmethod
    def get_population(cls):
        print(f'현재 강아지 마리수: {cls.population}')
        return cls.population
    
    @staticmethod
    def info():
        return '이것은 Puppy 클래스입니다!'
d = Puppy('초코', '푸들')

# instance method
d.bark()

# static method
print(d.info(), Puppy.info())

# class method
print(Puppy.get_population())
왈왈! 나는 초코, 푸들(이)야
이것은 Puppy 클래스입니다! 이것은 Puppy 클래스입니다!
현재 강아지 마리수: 1
1

7.5 상속

상속 (Inheritance)

  • 상속(Inheritance)

  • 메서드 오버라이딩(Method Overriding)

  • 다중 상속(Multiple Inheritance)

7.5.1 상속 (Inheritance) 이란?

클래스에서 가장 큰 특징은 상속이 가능하다는 것입니다.

부모 클래스의 모든 속성이 자식 클래스에게 상속 되므로 코드 재사용성이 높아집니다.


활용법

class ChildClass(ParentClass):
    <code block>
# Person 클래스를 정의해 보겠습니다.

class Person:
    population = 0
    
    def __init__(self, name='사람'):
        self.name = name
        Person.population += 1
        
    def talk(self):
        print(f'반갑습니다. {self.name}입니다.')
# Person 클래스의 인스턴스 p1을 생성해봅시다.
# name 속성은 자유롭게 설정합니다.

p1 = Person('아이유')
p1.talk()
print(Person.population)
반갑습니다. 아이유입니다.
1
# Person 클래스를 상속받아 Student 클래스를 만들어 보겠습니다.

class Student(Person):
    
    def __init__(self, student_id, name='학생'):
        self.name = name
        self.student_id = student_id  
        Person.population += 1
# Student 클래스의 객체 s1을 만들어봅시다.

s1 = Student(1001, '배수지')
# s1의 name과 student_id를 확인해봅시다.

s1.name, s1.student_id
('배수지', 1001)
# 자식 클래스의 인스턴스는 부모 클래스에 정의된 메서드를 호출 할 수 있습니다.
# talk 메서드를 호출해봅시다.

s1.talk()
반갑습니다. 배수지입니다.

이처럼 상속은 공통된 속성이나 메서드를 부모 클래스에 정의하고, 이를 상속받아 다양한 형태의 사람들을 만들 수 있습니다.

issubclass(class, classinfo)

  • class가 classinfo의 subclass면 True

isinstance(object, classinfo)

  • object가 classinfo의 인스턴스거나 subclass인 경우 True

# issubclass 함수를 통해 Student 클래스와 Person 클래스가 상속관계인지 확인해봅시다. (클래스 상속 검사)
# issubclass(자식클래스, 부모클래스)

issubclass(Student, Person)
True
# isinstance 함수를 통해
# s1이 Student 클래스의 인스턴스인지,
# s1이 Person 클래스의 인스턴스인지 모두 확인해봅시다.
# isinstance(인스턴스, 클래스)

isinstance(s1, Student), isinstance(s1, Person)
(True, True)
# 내장 자료형들도 아래와 같이 상속 관계가 있습니다.

print(issubclass(bool, int)) # True
print(issubclass(float, int)) # False
True
False

super()

  • 자식 클래스에 메서드를 추가로 구현할 수 있습니다.

  • 부모 클래스의 내용을 사용하고자 할 때, super()를 사용할 수 있습니다.


활용법

class ChildClass(ParentClass):
    def method(self, arg):
        super().method(arg) 
# Person 클래스와 Student 클래스를 함께 정의해 보겠습니다.

class Person:
    def __init__(self, name, age, number, email):
        self.name = name
        self.age = age
        self.number = number
        self.email = email 
        
    def greeting(self):
        print(f'안녕, {self.name}')
      
    
class Student(Person):
    def __init__(self, name, age, number, email, student_id):
        self.name = name
        self.age = age
        self.number = number
        self.email = email 
        self.student_id = student_id
        
p1 = Person('홍교수', 200, '0101231234', 'hong@gildong')
s1 = Student('학생', 20, '12312312', 'student@naver.com', '190000')
# p1과 s1 모두 greeting 메서드를 호출해봅시다.

p1.greeting()
s1.greeting()
안녕, 홍교수
안녕, 학생

위의 코드는 상속을 했음에도 불구하고 초기화(__init__)에서 동일한 코드가 반복됩니다.

초기화의 중복을 super() 함수를 통해 제거해봅시다.

class Person:
    def __init__(self, name, age, number, email):
        self.name = name
        self.age = age
        self.number = number
        self.email = email 
        
    def greeting(self):
        print(f'안녕, {self.name}')
        
        
class Student(Person):
    def __init__(self, name, age, number, email, student_id):
        # Person 클래스
        super().__init__(name, age, number, email)
        self.student_id = student_id
        
        
p1 = Person('홍교수', 200, '0101231234', 'hong@gildong')
s1 = Student('학생', 20, '12312312', 'student@naver.com', '190000')

p1.greeting()
s1.greeting()
안녕, 홍교수
안녕, 학생

[실습] Rectangle & Square

아래의 조건에 만족하는 클래스 Rentangle 을 작성하세요.


Rectangle 클래스는 아래와 같은 속성과 메서드를 갖습니다.

  • 인스턴스 속성

    • width : 가로 길이

    • height : 세로 길이

  • 인스턴스 메서드

    • area: 직사각형의 넓이를 리턴

    • perimeter: 직사각형의 둘레의 길이를 리턴

# Rectangle 클래스를 정의해봅시다.

class Rectangle:
    
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    @property      # 추가적인 인자가 없는 method에만 property를 달 수 있습니다.
    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)
# Rectangle 클래스의 인스턴스 rectangle을 가로 길이 4, 세로 길이 8로 초기화하고
# area 메서드와 perimeter 메서드를 통해 넓이와 둘레를 구해봅시다.

rectangle = Rectangle(4, 8)
rectangle.area(), rectangle.perimeter()
(32, 24)
  • Rectangle 클래스를 상속받아 Sqaure 클래스를 만들어 주세요.

    • Square 클래스는 Rectangle 클래스에서 상속받은 속성 외 추가 속성을 가지고 있지 않습니다.

    • 단, 정사각형이므로 인스턴스 생성시 인자로 한 변의 길이만 받습니다.

# Square 클래스를 정의해봅시다.

class Square(Rectangle):
    def __init__(self, length):
        super().__init__(length, length)
# Square 클래스의 인스턴스 square를 길이 4로 초기화하고
# area 메서드와 perimeter 메서드를 통해 넓이와 둘레를 구해봅시다.

square = Square(4)
square.area(), square.perimeter()
(16, 16)

7.5.2 메서드 오버라이딩

Method Overriding(메서드 오버라이딩): 자식 클래스에서 부모 클래스의 메서드를 재정의하는 것

  • 상속 받은 메서드를 재정의할 수도 있습니다.

  • 상속 받은 클래스에서 같은 이름의 메서드로 덮어씁니다.

class Person:
    def __init__(self, name, age, number, email):
        self.name = name
        self.age = age
        self.number = number
        self.email = email 
        
    def talk(self):
        print(f'안녕, {self.name}')
# 위의 Person 클래스를 상속 받아 군인답게 말하는 Soldier 클래스를 만들어 보겠습니다.
# talk 메서드를 재정의(override) 하겠습니다.

class Soldier(Person):
    def __init__(self, name, age, number, email, army):
        super().__init__(name, age, number, email)
        self.army = army
        
    # method overriding    
    def talk(self):
        print(f'충성! {self.army} {self.name}')
p = Person('일반인', 10, '010123', '1banin@gmail.com')
p.talk()
안녕, 일반인
s = Soldier('굳건이', 25, '0101234', 'soldier@roka.kr', '하사')
s.talk()
충성! 하사 굳건이

7.5.3 상속관계에서의 이름공간

  • 기존의 인스턴스 -> 클래스 순으로 이름 공간을 탐색해나가는 과정에서 상속관계에 있으면 아래와 같이 확장됩니다.

    • 인스턴스 -> 자식 클래스 -> 부모 클래스

[연습] Person & Animal (메서드 오버라이딩)

사실 사람은 포유류입니다.

Animal Class를 만들고, Person Class 가 상속받도록 구성해봅시다.

(변수나, 메서드는 자유롭게 만들어보세요.)

예시) 
모든 동물은 이름이 있고, 사람은 이름과 이메일이 있습니다.
모든 동물은 talk 메서드가 있습니다. 
동물은 '으르렁'하고, 사람은 '안녕'합니다.
# 아래에 코드를 작성해주세요.

class Animal:
    def __init__(self, name):
        self.name = name
    
    def talk(self):
        print('grr')
            

class Person(Animal):
    def __init__(self, name, email):
        super().__init__(name)
        self.email = email
    
    def talk(self):
        print('안녕')
            

animal = Animal('곰')
animal.talk()

person = Person('김싸피', 'hello@ssafy.com')
person.talk()
grr
안녕

7.5.4 다중 상속

  • 두개 이상의 클래스를 상속받는 경우, 다중 상속이 됩니다.

    • 상속 받은 모든 클래스의 요소를 활용 가능

    • 중복된 속성이나 메서드가 있는 경우 상속 순서에 의해 결정

# Person 클래스를 정의합니다.
# Person 클래스는 생성자에서 인스턴스 변수로 name을 설정합니다.

class Person:
    def __init__(self, name):
        self.name = name

    def greeting(self):
        return f'안녕, {self.name}'
# Mom 클래스를 정의합니다.
# Mom 클래스는 Person 클래스를 상속받으며, 클래스 변수로 gene을 갖습니다. 값은 'XX'입니다.
# Dad 클래스만의 인스턴스 메서드 swim을 자유롭게 정의해봅시다.

class Mom(Person):
    gene = 'XX'
    
    def swim(self):
        return '첨벙첨벙'
# Dad 클래스를 정의합니다.
# Dad 클래스는 Person 클래스를 상속받으며, 클래스 변수로 gene을 갖습니다. 값은 'XY'입니다.
# Dad 클래스만의 인스턴스 메서드 walk를 자유롭게 정의해봅시다.

class Dad(Person):
    gene = 'XY'
    
    def walk(self):
        return '성큼성큼'
# FirstChild 클래스를 정의합니다. 
# 상속의 순서가 중요합니다.(Dad, Mom) 순서로 상속받아봅시다.
# 상속받은 swim 메서드를 재정의(override)해봅시다.
# FirstChild 클래스만의 인스턴스 메서드 cry를 자유롭게 정의해봅시다.

class FirstChild(Dad, Mom): 
    def swim(self):  # Mom의 swim 메서드를 오버라이딩 합니다.
        return '챱챱'
    
    def cry(self):  # Child 만이 가지는 인스턴스 메서드 입니다.
        return '응애'
# FirstChild 클래스의 인스턴스 baby1을 생성해봅시다.

baby1 = FirstChild('첫째')
# baby1의 cry 메서드를 실행해봅시다.

baby1.cry()
'응애'
# baby1의 swim 메서드를 실행해봅시다.

baby1.swim()
'챱챱'
# baby1의 walk 메서드를 실행해봅시다.

baby1.walk()
'성큼성큼'
# baby1의 gene 속성은 어떤 부모클래스의 속성값을 상속받는지 확인해봅시다.

baby1.gene
'XY'
# 이번에는 SecondChild 클래스를 만들어 상속 순서를 바꿔봅시다.
# (Mom, Dad) 순서로 상속받아봅시다.
# 상속받은 walk 메서드를 재정의(override)해봅시다.
# SecondChild 클래스만의 인스턴스 메서드 cry를 자유롭게 정의해봅시다.

class SecondChild(Mom, Dad):  
    def walk(self):  # Dad 의 walk 메서드를 오버라이딩 합니다.
        return '아장아장'
    
    def cry(self):  
        return '응애'
# SecondChild의 인스턴스 baby2를 생성합니다.

baby2 = SecondChild('둘째')
# baby2의 cry 메서드를 실행합니다.

baby2.cry()
'응애'
# baby2의 walk 메서드를 실행합니다.

baby2.walk()
'아장아장'
# baby2의 swim 메서드를 실행합니다.

baby2.swim()
'첨벙첨벙'
# baby2의 gene 속성은 어떤 부모클래스의 속성값을 상속받는지 확인해봅시다.

baby2.gene
'XX'