Python 정리 - OOP와 클래스(Class)

Kyung Jae, Cheong·2023년 3월 23일
0
post-thumbnail

OOP와 클래스(Class)

  • 프로그램이 복잡해지는 경우, 프로그램을 효율적으로 작성할 필요가 있으며, 이때 이용되는 개념들이 OOP와 Class라는 개념임
  • Class는 객체 지향 프로그래밍(OOP, Object Oriented Programming)을 위해 사용되는 것으로, 데이터(속성)와 메서드(처리)를 함께 사용할 수 있는 구조로 이루어져 있음.
    • 즉, 변수와 함수들을 하나의 객체(Object)로써 묶어서 관리할 수 있다는 의미!

1. OOP (객체지향 프로그래밍)

  • OOP (Object Oriented Programming)란, 실체가 있는 모든 물체를 클래스와 인스턴스, 함수, 변수라는 object로 변화시켜서 프로그램을 구성하는 개발방식을 의미함
    • 최소비용으로 최대효율을 얻기위해 개발되었고, 속성과 기능을 object라는 최소단위로 분리하는 프로그래밍의 패러다임을 의미함
  • 하나의 패러다임일 뿐이기 때문에 기존의 프로그래밍 패러다임(Procedural Programming, Functional Programming)과의 우열을 가릴 수는 없지만, 속성과 기능이 증가할 때마다 배열과 함수를 계속 생성해야 하는 경우에 소스코드를 보다 효율적으로 관리할 수 있음

OOP 이전의 프로그래밍 패러다임과 OOP

  • Procedural Programming (절차형, 순서형 프로그래밍)
    • 조건, 기능, 변수를 순서대로 정의하고 실행하는 방법
    • 간단한 프로그래밍에서 이용되는 일반적인 방법을 의미
    • 조건 또는 기능이 증가할 때마다 함수와 변수등 정의해야할 요소들이 계속 증가하기 때문에 프로그램이 복잡해질수록 비효율적임
# 변수활용
speed = 50
color = 'black'
model = 'CarModel'

# 해당 변수를 각각 명시해주어야 한다.
print("Car Attribute: ", speed, color, model)
'''
Car Attribute:  50 black CarModel
'''
  • Functional Programming (함수형 프로그래밍)
    • 함수형은 함수의 사용을 극대화시켜서 코드의 가독성을 높여주는 형태
    • 특정 상황에 특정 함수를 재사용하여 프로그래밍 효율을 높이기위해 고안된 방법
    • 일반적으로 많이 쓰이고 있지만 이 역시도 프로그램이 복잡해지는 경우 비효율이 발생할 수 있어 절대적으로 좋은 프로그래밍 방식이라고는 볼 수 없음
# 함수활용
def carAttribute():
    speed = 50
    color = 'black'
    model = 'CarModel'
    return speed,color,model

# return값을 통해 함수 요소값을 확인할 수 있다.
print("Car Attribute: ", carAttribute())
'''
Car Attribute:  (50, 'black', 'CarModel')
'''
  • Object Oriented Programming (객체지향 프로그래밍)
    • 절차 프로그래밍과 다르게 기능별로 수행하며, 데이터(속성)와 메서드(처리)를 함께 사용할 수 있는 구조로 프로그래밍 하는 방법
    • 주로 Class를 통해 구현되고, 클래스 선언 순서에 관계없이 실행할 수 있음
      • Python의 자료형(int, float, bool, list, tuple ... 등)은 class의 형태로 구현된 것이며, 따라서 파이썬의 자료구조 및 자료형은 객체지향 프로그래밍이 적용된 것이라 볼 수 있음
    • 다만, 무분별하게 활용하면 유지보수가 어려워질 수도 있기때문에 설계방향 및 서비스기능에 따라 사용하는 것이 좋으며, 기능별로 개체가 효율적으로(재사용가능하도록) 분리되어야 하기 때문에 효율적인 설계가 매우 중요함
# OOP (Class)
class Car:
    def __init__(self, speed, color, model):
        self.speed = speed
        self.color = color
        self.model = model

    def drive(self):
        self.speed = 50

myCar = Car(0, "green", "testCar")
print("--------Car Object Create Complete--------")
print("Car Speed: ", myCar.speed)
print("Car Color: ", myCar.color)
print("Car Model: ", myCar.model)

'''
--------Car Object Create Complete--------
Car Speed:  0
Car Color:  green
Car Model:  testCar
'''

# 운전 중이 아니므로 speed는 0을 출력한다.
print("Car Speed by drive: ", myCar.speed)
'''
Car Speed by drive:  0
'''

# Car object method Call (메서드 호출)
myCar.drive()
print("Car Speed by drive: ", myCar.speed)
'''
Car Speed by drive:  50
'''

2. Class (클래스)

  • Class에서는 변수, 함수 등을 함께 기술해야 하며, 이를 클래스 정의라고 함
  • Class명은 맘대로 지어도 상관은 없지만, 일반적으로 변수이름과 구분하기 위해 첫문자는 대문자로 설정하는 것이 일종의 암묵적인 약속임 (변수의 첫문자는 클래스와 구분 짓기 위해 소문자로 설정하는 것이 국룰..!)
# Class의 정의
class 클래스명(상속받는 경우의 상위클래스):
  
  # 클래스 내부 메서드(함수) 정의
  def 메서드명(self, 인수 목록):
    # 데이터 속성(변수)
    self.변수 = ...
    ...
    return ...
  • 클래스 정의 예시
    • 여기서 self의 의미에 대해선 인스턴스의 개념과 클래스 변수의 개념을 함께 알아야 설명이 가능해서 이에 관련해선 2-4 파트에서 다시 다룰 것이고, 일단은 변수와 메서드에 self를 붙여야 객체로써 관리할 수 있다는 정도로 이해하고 넘어가도록 하자
# 클래스
class Person:
  
  # 메서드 1
  def getName(self):
    return self.name	# 속성 1
  
  # 메서드 2
  def getAge(self):
    return self.age		# 속성 2
  • 인스턴스(instance)를 작성
    • 클래스로부터 생성되는 각각의 존재를 인스턴스(instance)라 부름
    • 예를 들면, Person 클래스로부터 작성된 개개인의 사람들이 각각 인스턴스에 해당함을 의미함
    • 인스턴스는 클래스명()을 통해 정의할 수 있고, 인스턴스를 나타내는 변수(인스턴스명)에 대입하여 변수를 정의할 수 있음
      • 이때 클래스의 메소드나 속성을 이용하기 위해 .을 통해 접근할 수 있음
# 인스턴스 정의 
인스턴스명 = 클래스명()
# 예시 
person1 = Person()

# 속성 접근
인스턴스명.속성
# 예시
person1.name

# 메서드 접근
인스턴스명.메서드(인수목록)
person1.getName()
  • 클래스를 이용한 인스턴스 작성 및 호출
# 클래스 정의
class Person:
	# 메소드 1
    def getName(self):
        return self.name	# 속성 1
	# 메소드 2
    def getAge(self):
        return self.age		# 속성 2

# 인스턴스 1 정의
person_1 = Person()
person_1.name = "홍길동"		# 속성 1 정의
person_1.age = 24			# 속성 2 정의

# 인스턴스 1 메소드 호출
name_1, age_1 = person_1.getName(), person_1.getAge()
print(f"{name_1}님은 {age_1}세 입니다.")
'''
홍길동님은 24세 입니다.
'''

# 인스턴스 2 정의
person_2 = Person()
person_2.name = "임꺽정"		# 속성 1 정의
person_2.age = 33			# 속성 2 정의

# 인스턴스 2 메소드 호출
name_2, age_2 = person_2.getName(), person_2.getAge()
print(f"{name_2}님은 {age_2}세 입니다.")
'''
임꺽정님은 33세 입니다.
'''

3. 클래스 생성자(constructor)

  • 인스턴스를 작성할 때 초기 값을 주거나, 초기화처리를 하고자 하는 경우 등 인스턴스가 작성될 때 처음에 반드시 처리되도록 하는 메서드생성자(constructor)라 부름
  • 생성자는 __init__이라는 이름을 가지는 메서드로써 정의하고, 양옆의 2개의 언더바_는 반드시 지켜줘야함
# 생성자
def __init__(self, 인수목록):
  ...
  • 생성자를 활용한 클래스 선언 예시
# 클래스 정의
class Person:
    
    # 생성자 메서드
    def __init__(self, name, age):
        self.name = name    # 속성 1 초기값 = name
        self.age = age      # 속성 2 초기값 = age
    
	# 메소드 1
    def getName(self):
        return self.name	# 속성 1
	# 메소드 2
    def getAge(self):
        return self.age		# 속성 2

# 리스트로 인스턴스를 정의할 수도 있음
persons = [Person("홍길동", 24), Person("임꺽정", 33)]

# for문으로 각 인스턴스 별로 메소드를 실행하고 결과를 출력
for person in persons:
    name = person.getName()
    age = person.getAge()
    print(f"{name}님은 {age}세 입니다.") 
'''
홍길동님은 24세 입니다.
임꺽정님은 33세 입니다.
'''

4. 클래스 변수와 클래스 메서드

  • 현재까지 살펴본 메서드와 변수들은 self구문을 통해 정의되었고, 이는 각각의 인스턴스에 관련되어있는 변수(속성) 혹은 함수(메서드)였음
    • 즉, self가 붙어있는 변수는 인스턴스마다 다르게 저장되는 변수이기 때문에 이러한 변수를 인스턴스 변수라 부름
    • 또한, self를 인수로 가지는 메서드는 인스턴스 자기자신을 의미하는 것으로써 이러한 메서드를 인스턴스 메서드라 부름
  • 하지만 각각의 인스턴스가 아닌 클래스 전체(전체 인스턴스)에서 공유하여 값을 저장하거나 처리해야하는 경우가 있을 수 있고, 이러한 경우엔 속성이나 메서드는 클래스 전체에 관련되어 따로 정의하거나 처리해주어야함
    • 예를 들어 클래스를 통해 몇개의 인스턴스가 생성되었는지를 기억하고자 하는 경우에는 클래스 전체에서 값을 공유하여 저장해야 하고, 이처럼 클래스 전체에 관련된 속성과 메서드를 클래스 변수, 클래스 메서드라 부름

클래스 변수와 인스턴스 변수

# 클래스 정의
class Person:

  # 생성된 인스턴스 수를 담을 변수
  count = 0		# 클래스 전체에서 공유하는 속성(클래스 변수)
  
  # 메서드 정의 (인스턴스 메서드)
  def getName(self):
    return self.name	# 인스턴스마다 값이 존재하는 속성(인스턴스 변수)
  • 위 예시처럼 클래스 아래에서 정의되어 클래스에 하나의 값만 존재하는 변수 혹은 속성을 클래스변수 혹은 정적변수라 부름
  • self가 붙어있는 데이터 속성은 인스턴스마다 값이 존재하기 때문에, 이러한 속성은 클래스 변수와 구분하기 위해 인스턴스 변수라 부름

클래스 메서드와 인스턴스 메서드

  • 클래스 전체에 관련된 메서드를 클래스 메서드라 부르며, 클래스 메서드는 클래스 내부에서 정의할 메서드 위에 데코레이터(@classmethod)를 통해 정의해 주어야 함
# 클래스 내부
@classmethod	# 클래스 메서드 데코레이터
def getCount(변수명):	# 변수명은 맘대로 정해도 됨
  return 변수명.count	# 인자로 받은 변수명의 클래스 변수를 반환함

# 클래스 외부
클래스명.getCount()		# 인자 변수명에 관계없이 클래스 변수를 불러오게 됨 
  • self를 인수로서 가지는 메서드는 클래스 메서드와 구분짓기 위해 인스턴스 메서드라 부르고, 이때 self는 인스턴스 자신(자기자신)을 의미함
# 클래스 내부
def getName(self):	# 인스턴스 메서드 정의
  return self.name	# 인스턴스 속성을 반환함

# 클래스 외부
name = Person('이름').getName()	# 인스턴스 메서드로 인스턴스 변수를 반환
  • 예시를 통해 알아보는 클래스 변수, 클래스 메서드
# 클래스 정의
class Person:

    inst_count = 0	# 클래스 변수
    
    def __init__(self, name, age):
        Person.inst_count += 1	# 인스턴스를 생성할 때마다 1씩 증가
        self.name = name	# 인스턴스 변수
        self.age = age		# 인스턴스 변수
    
    # 인스턴스 메서드
    def getName(self):
        return self.name	# 인스턴스 변수
    
    # 인스턴스 메서드
    def getAge(self):
        return self.age		# 인스턴스 변수
    
    # 클래스 메서드
    @classmethod
    def getCount(c):		# 인수명은 맘대로
        return c.inst_count	# 클래스 변수

# 리스트로 인스턴스를 정의
persons = [Person("홍길동", 24), Person("임꺽정", 33), Person("무야호", 40)]

# for문으로 각 인스턴스 별로 메소드를 실행하고 결과를 출력
for person in persons:
    name = person.getName()
    age = person.getAge()
    print(f"{name}님은 {age}세 입니다.") 

# 클래스 메서드를 통해 클래스 변수를 출력
print(f"\n총 인원수는 {Person.getCount()}명 입니다.")

'''
홍길동님은 24세 입니다.
임꺽정님은 33세 입니다.
무야호님은 40세 입니다.

총 인원수는 3명 입니다.
'''

5. 클래스의 캡슐화(capsulization)

  • 객체지향 프로그래밍(OOP)에서는 본래 사물의 데이터를 속성으로 인스턴스화하여 정보를 처리하는데, 이러한 객체지향에서는 데이터 속성을 마음대로 변경 할 수 없도록 처리를 해주어야하고, 이러한 처리를 통해 잘못된 값이 설정되지 않도록 처리할 수 있음
  • 일반적인 객체 지향에 의한 프로그래밍 언어에서는 클래스의 외부로부터 object 속성에 접근할 수 없도록 하는 구조를 사용하여, 프로그램에서 발생 가능한 문제를 미연에 방지하게 됨
    • 하지만, Python에서는 언어의 간단한 사용을 중요시하다보니 object에 대한 접근제어를 위한 접근제어자를 제공하지 않기 때문에, 객체의 변수, 메서드, 함수에 직접적으로 접근할 수 있음
    • 이러한 한계를 극복하기위해 파이썬에서는 직접 접근을 허용하지 않도록 규칙을 만들어 두었는데, 접근 정도와 단계를 나타내는 Notation이라는 개념을 통해 접근 가능한 정도를 설정할 수 있음
  • 파이썬의 Notation에는 다음과 순서로 코드 접근을 어렵게 설정할 수 있음
    • (접근 어려움) Private -> Protected -> Public (접근 쉬움)
    • Public : 내부와 외부에서 쉽게 접근할수 있고 수정할 수도 있음
    • Protected : 언더바 1개 _를 이름 앞에 붙여 외부에서 쉽게 접근하지 못하도록 함
      • 실제로는 접근이 되지만 개발자간의 암묵적인 약속으로 정한 것이라 보면 됨
    • Private : 언더바 2개__를 이름 앞에 붙여 외부에서의 접근을 수정을 강제로 금지시킴
      • TMI : 사실 아무리 Private으로 설정하더라도 _클래스이름__메서드이름 혹은 _클래스이름__변수이름형태로 접근하면 강제적으로 접근 할 수 있어서 파이썬 코드상으로 접근을 강제적으로 막을 방법이 없긴 해서, Python에서는 접근을 어렵게 하기위한 최선의 방법이라 생각하면 될 듯함
  • 이처럼 데이터를 안전하게 유지하고, 외부로부터 마음대로 접근할 수 없도록 하는 것을 객체지향에서는 캡슐화(capsulization)라고 부름
class Encap:
    def __init__(self,value):
        self.value = value
        print('init:', self.value)
	
    # protected
    def _set(self):
        print('set:', self.value)

    def printTest(self):
        print('printTest:', self.value)
	
    # private
    def __printTest2(self):
        print('printTest:', self.value)

# object 생성
e = Encap(10)
'''
init: 10
'''

# object 실행 
# 케이스1
e.__init__(20)
e._set()
e.printTest()
'''
init: 20
set: 20
printTest: 20
'''

# private에 직접 접근하면 오류 발생함
e.__printTest2()
'''
AttributeError: 'Encap' object has no attribute '__printTest2'
'''

# 케이스2
e.__init__(30)
e._set()
e.printTest()
'''
init: 30
set: 30
printTest: 30
'''
  • 특히 프로그램이 길어지고 다양한 변수를 선언하는 경우 클래스의 속성이 충돌(변수의 중복)할 수 있는데, 이럴땐 속성명을 다르게 해줘도 되지만 파이썬에서 활용할 수 있는 '비공개 속성'을 활용하여 충돌을 방지할 수 있음
# 속성충돌
class parent_class:
    def __init__(self):
        self.value = 30     # public
        self._value = 40    # protected
        self.__value = 50   # private

    def get(self):
        return self.value

class sub_class(parent_class):
    def __init__(self):
        super().__init__()
        self.__value = 20    # 위의 parent_class의 _value와 충돌(값의 중복)발생

s = sub_class()
print(s.value) # public
print(s._value) # protected
'''
30
40
'''

# '_클래스이름__private변수' (강제접근)
print('----- 어떤 클래스에서 값을 받아오냐에 따라 값의 정보가 바뀜 -----')
print(s._parent_class__value)
print(s._sub_class__value)
print('parent_class value:', s.get(), ', sub_class value:', s.value)
'''
----- 어떤 클래스에서 값을 받아오냐에 따라 값의 정보가 바뀜 -----
50
20
parent_class value: 30 , sub_class value: 30
'''

6. 클래스의 상속, 포함, 오버라이딩

상속(inheritance)

  • 이미 정의한 클래스를 바탕으로 새로운 클래스를 정의할 수 있는데, 이처럼 새로운 클래스를 확장하여 정의하는 것을 클래스를 확장(Extension)한다고 표현함
    • 이러한 클래스 확장은 기존 클래스의 속성과 메서드를 상속받는 형태로 이루어지며, 이때는 기존의 클래스의 속성과 메서드를 다시 적을 필요가 없이 새로운 속성과 메서드를 추가하여 코드를 작성할 수 있음
  • 이처럼 새롭게 확장한 클래스가 기존의 클래스의 속성과 메서드를 물려받는 것을 상속(inheritance)이라 부름
    • 속성과 메서드를 물려주는 기존 클래스를 기저클래스(base class)혹은부모클래스라 부름
    • 속성과 메서드를 상속받는 새로운 클래스를 파생클래스(derived class)혹은자식클래스라 부름
# 파생클래스의 정의 (상속)
class 파생클래스명(기저클래스명):
	...
    def 추가메서드명(self, 인수목록):
    	self.추가속성 =...
  • 상속 예시 코드
# 기저클래스(base)
class Person:
    def __init__(self, name):
        self.name = name
 
# 파생클래스(derived) 1
class Student(Person):		# Person 클래스를 상속받음
    def study(self):
        print (self.name + " studies hard")	# name 변수를 파라미터로 재사용

# 파생클래스(derived) 2
class Employee(Person):		# Person 클래스를 상속받음
    def work(self):
        print (self.name + " works hard")	# name 변수를 파라미터로 재사용

# object 생성
s = Student("Dave")
e = Employee("David")

# object 실행
s.study()
e.work()
'''
Dave studies hard
David works hard
'''

포함(Composition)

  • 속성과 메서드를 모두 받는 상속과 달리 클래스의 일부 변수와 기능만 가져오는 경우는 포함(Composition)이라 부르며, 이때는 기저클래스나 파생클래스로 구분을 할 수 없고, 새롭게 정의하는 클래스 내부에서 클래스를 호출함으로써 가져올 수 있음
# 포함 코드 예시
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def printPerson(self, name, age):
        print('Person_printPerson', name, age)

# 새로운 클래스
class Student:	# 상속받지 않는다!
    def __init__(self, name, age, id):
        self.person = Person(name,age)		# Person 클래스를 호출
        Person.__init__(self, name, age)	# Person의 생성자를 실행함
        self.id = id	# 새로운 변수를 저장

    def test(self, score):
        if score > 80:	# 인스턴스를 통해 외부메서드를 실행함
            return (self.person.printPerson(self.name, self.age))
        else:	# 생성자 실행으로 저장했던 변수를 이용함
            print (self.name + " needs supplementary lessons")

# 인스턴스 1
s = Student("Dave", 20, 1)
print("s.age:", s.name)
print("s.age:", s.age)
print("s.id:", s.id)
'''
s.age: Dave
s.age: 20
s.id: 1
'''

# 인스턴스 2
s2 = Student("Jamie", 25, 2)
print("s2.age:", s2.name)
print("s2.age:", s2.age)
print("s2.id:", s2.id)
'''
s2.age: Jamie
s2.age: 25
s2.id: 2
'''

# test 함수를 실행
print('-- test score result --')
s.test(90)
s2.test(11)
'''
-- test score result --
Person_printPerson Dave 20
Jamie needs supplementary lessons
'''

오버라이딩(overriding)

  • 부모클래스를 상속받는 파생클래스에서는 부모클래스와 동일한 이름의 메서드를 가지게 할 수 있는데, 이러한 경우엔 메서드간의 충돌이 발생하는 것이 아닌 파생클래스에서 새롭게 정의된 메서드가 부모클래스의 메서드를 대신하여 이용되며, 이러한 처리방식을 오버라이딩(overriding)이라 부름
    • 즉, 인스턴스가 파생클래스에서 정의된 인스턴스인 경우, 동일한 이름의 메서드 중 파생클래스에서 정의된 메서드가 실행된다는 뜻
    • 이러한 오버라이딩은 생성자인 __init__ 함수를 파생클래스에서 다시 정의하는 경우에도 적용되고, 그외에 부모클래스와 동일한 이름의 함수로 새롭게 정의하면 새롭게 정의된 함수가 실행됨을 의미함
# 부모클래스
class Person:
    def __init__(self, name):
        self.name = name
    def getName(self):
        return self.name

# 파생클래스
class Customer(Person):
    def getName(self):	# 오버라이딩
        return "고객명 : " + self.name

# 인스턴스 생성(부모클래스)
person_1 = Person("홍길동")
print(person_1.getName())	# 부모클래스의 메서드가 실행됨
'''
홍길동
'''

# 인스턴스 생성(파생클래스)
customer_1 = Customer("홍길동")
print(customer_1.getName())	# 파생클래스의 메서드가 실행됨
'''
고객명 : 홍길동
'''

super()함수

  • 부모클래스에서 정의한 함수와 변수를 파생클래스에서 정의한 것처럼 사용할 수 있는데, 이러한 경우 두가지 방법 중 하나를 사용해야함
    • 부모클래스명(생성자인수).함수명()
    • super().함수명()
  • super() 함수는 부모클래스의 인스턴스를 얻는 함수로써 super().함수명()을 통해서 부모클래스에서 정의된 해당 함수명에 정의된 변수와 기능들을 그대로 가져와서 실행시켜줄 수 있음
    • 간혹 super() 함수를 부모클래스의 함수명과 파생클래스의 함수명이 중복된 경우에 사용할 수 있는 메서드라 설명하는 자료들이 있지만, 이는 정확한 표현이 아니라 생각함
    • 왜냐하면 super() 뒤에 붙는 함수로 파생클래스와 중복되지 않는 다른 함수를 불러와서 사용할 수도 있기 때문에 super() 메서드는 함수를 덮어쓰는 오버라이딩(overriding)의 개념 보다는 일부 기능을 가져오는 포함(Composition)의 개념에 더 가깝다 볼 수 있음
    • 쉽게 생각해서 super() 함수는 부모클래스로부터 코드를 그대로 복사해와서 코드를 간결하게 해주는 역할이라 보면 됨
    • 즉, super()는 메서드 단위에서의 상속이며, 이때 상속받는 메서드의 모든 매개변수(인자)를 그대로 가져와 사용하게 되는 것!
# 부모클래스
class Person:
    def __init__(self, name):
        self.name = name
    
    def getName(self):
        return self.name
    
    def printName(self):
        return print(f'고객명 : {self.name}')

# 파생클래스
class Customer(Person):		# 상속 받음
    def __init__(self, name, age):	# 생성자 오버라이딩 (변수추가)
        super().__init__(name)		# 부모클래스의 생성자를 가져와 실행
        self.age = age				# 추가된 변수를 인스턴스 변수로 저장
    
    def getName(self):				# getName함수 오버라이딩 (출력수정)
        return "고객님의 이름 : " + self.name
    
    def getAge(self):				# 새로운 메서드를 정의
        name = super().getName()	# 부모클래스의 getName()함수를 가져와 실행
        return f"{name}님 나이 : {self.age}"
    
    def printAge(self):				# 새로운 메서드를 정의
        user_name = Person(self.name).getName()	# 인스턴스를 생성해 함수를 실행함
        # 위 코드는 "user_name = super().getName()" 으로 대체 가능함
        super().printName()			# 부모클래스의 printName()함수를 가져와 실행
        return print(f'{user_name}님의 나이 : {self.age}')

# 인스턴스 생성(부모클래스)
person_1 = Person("홍길동")
print(person_1.getName())
person_1.printName()
'''
홍길동
고객명 : 홍길동
'''

# 인스턴스 생성(파생클래스)
customer_1 = Customer("임꺽정", 23)
print(customer_1.getName())
print(customer_1.getAge())
'''
고객님의 이름 : 임꺽정
임꺽정님 나이 : 23
'''

# 상속 받은 코드를 실행해보기
customer_1.printName()
'''
고객명 : 임꺽정
'''

# 새로 정의한 메서드를 실행해보기
customer_1.printAge()
'''
고객명 : 임꺽정
임꺽정님의 나이 : 23
'''

7. 클래스의 특수 메서드

  • 이번엔 고수준의 클래스 기능으로써, 클래스를 정의할 때 이미 정해져 있는 이름의 메서드인 특수메서드들을 간략하게 정리해보겠음
  • 생성자인 __init__ 처럼 양쪽에 2개씩의 언더바__ 로 정의된 메서드이며, 이를 통해 다양한 방식으로 출력을 조절하거나 연산을 진행할 수 있으며, 기본적인 사용법은 생성자인 __init__ 메서드처럼 사용하면 됨

출력 관련 특수 메서드

  • __str__(self) : 내장함수 str()에 대응함, 문자열을 반환
  • __format__() : 내장함수 format()에 대응함, 문자형의 서식을 지정하여 반환
  • __int__(self) : 내장함수 int()에 대응함, 정수를 반환
  • __float__(self) : 내장함수 float()에 대응함, 부동 소수점을 반환
  • __repr__(self) : 내장함수 repr()에 대응함, 식의 평가(eval())가 되는 문자열 반환
    • 문자열로 객체를 다시 생성하는 함수로 쉽게 말해서 str()과의 큰 차이점은 repr()은 따옴표가 달려서 출력된다는 점
# __str__ 사용예시
class Person:
    def __init__(self, name):
        self.name = name
        
    def __str__(self):
        str_ = self.name + "님"
        return str_

pr = Person('임꺽정')
print(pr.name)
print(str(pr))
print(pr.__str__())
'''
임꺽정
임꺽정님
임꺽정님
'''

연산차 처리 관련 특수 메서드

  • self는 왼쪽 오퍼랜드, other는 오른쪽 오퍼랜드
  • __add__(self, other) : 연산자 +에 대응함, 덧셈연산
  • __sub__(self, other) : 연산자 -에 대응함, 뺄셈연산
  • __mul__(self, other) : 연산자 *에 대응함, 곱셈연산
  • __truediv__(self, other) : 연산자 /에 대응함, 나눗셈연산
  • __mod__(self, other) : 연산자 %에 대응함, 나머지연산

컬렉션 자료형 관련 특수 메서드

  • __len__(self) : len(인스턴스)에 대응함, 길이를 반환함
  • __getitem__(self, 인덱스 또는 키) : 인스턴스[인덱스 또는 키]에 대응함, 요소 얻기
  • __setitem__(self, 인덱스 또는 키, 값) : 인스턴스[인덱스 또는 키] = 값에 대응함, 요소를 대입(할당)함
  • __delitem__(self, 인덱스 또는 키) : del 인스턴스[인덱스 또는 키]에 대응함, 요소를 삭제함
  • __iter__(self) : iter(인스턴스)에 대응함, 이터레이터를 반환함
  • __reversed__(self) : reversed(인스턴스)에 대응함, 역순의 이터레이터를 반환함

요약 및 마무리

  • 이 글에서는 객체지향프로그래밍의 개념과 클래스에대해서 다루어보았음

OOP

  • 프로그래밍의 3가지 패러다임
    • Procedural Programming (절차형, 순서형 프로그래밍)
    • Functional Programming (함수형 프로그래밍)
    • Object Oriented Programming (객체지향 프로그래밍)
  • OOP : 기능별로 수행하며, 데이터(속성)와 메서드(처리)를 함께 사용할 수 있는 구조로 프로그래밍 하는 방법, 주로 Class를 통해 구현되고, 클래스 선언 순서에 관계없이 실행할 수 있음

Class

  • Class (클래스) 정의하기
    • 변수, 함수 등을 함께 기술할 수 있음
    • Class명의 첫문자는 대문자로 설정하는 것이 국룰
    • self의 의미는 인스턴스와 관련되어 있음을 의미함
  • 인스턴스(instance) : 클래스로부터 생성되는 각각의 존재를 의미, 메소드나 속성을 이용하려면 .을 통해 접근
    • 인스턴스 변수 : self.변수
    • 인스턴스 메서드 : def 메서드(self):
  • 생성자(__init__) : 초기값 및 초기화 메서드, 인스턴스 생성시 실행됨
  • 클래스 메서드는 데코레이터(@classmethod)를 통해 정의
  • 캡슐화(capsulization) : 데이터를 안전하게 유지하고, 외부로부터 마음대로 접근할 수 없도록 하는 것
  • 상속(inheritance) : 새롭게 확장한 클래스가 기존의 클래스의 속성과 메서드를 물려받는 것
    • 속성과 메서드를 물려주는 기존 클래스 : 기저클래스혹은부모클래스
    • 속성과 메서드를 상속받는 새로운 클래스 : 파생클래스혹은자식클래스
  • 포함(Composition) : 클래스의 일부 변수와 기능만 가져오는 것
  • 오버라이딩(overriding) : 부모클래스와 동일한 이름의 메서드를 가지는 경우 새롭게 정의된 메서드가 부모클래스의 메서드를 대신하여 이용되는 것
  • super() 함수 : 부모클래스로부터 메서드 단위로 상속을 받는 것, 이때 상속받는 메서드의 모든 매개변수(인자)를 그대로 가져와 사용함
    • 오버라이딩(overriding)의 개념 보다 포함(Composition)의 개념에 더 가까움

마무리

  • 다소 내용이 많아지고 복잡해졌지만, 클래스는 많이 써보고 익숙해지는 것이 가장 중요하다 생각함
  • 이후에 추가적인 학습을 통해 보완하거나 추가할 내용이 생기면, 업데이트를 지속적으로 해나갈 예정임
  • 화이팅!
profile
Machine Learning (AI) Engineer & BackEnd Engineer (Entry)

0개의 댓글

관련 채용 정보