[Python] Python의 Class

객체지향#

class

  • 객체 모델링의 수단
  • 객체(instance)를 생성하기 위한 단위
  • ADT(Abstract Data Type)
class Car(object):
#   class variable
    example_var = 100

#     initializer
    def __init__(self, maker, cc, price):
        self.maker = maker # property(속성) - instance variable
        self.cc = cc
        self.price = price
        
#     instance method
    def get_car_info(self):
        print(Car.example_var)
        return self.maker + str(self.cc) + str(self.price)
    
car_1 = Car('kia', 1998, 10000)
car_2 = Car('Hyundai', 2399, 20000)

class variable#

class가 저장되는 공간에 저장되었다가 이 class를 기반으로 instance가 생성되고 이 variable을 호출할 때 class 저장 공간에 접근해서 변수를 가져온다

initializer#

instance가 생성될 때 초기화를 담당함

class를 기반으로 heap 영역에 메모리 공간을 할당할 수 있다. 이 확보된 메모리 공간 자체를 instance라고 한다

위의 예제에서 car_1과 car_2안에는 instance의 메모리 주소값이 들어있다. 이를 reference variable이라 한다.

. (dot operator)#

객체가 있다는 것 == class가 있다는 것 모든 객체는 기본적으로 변수(property)를 가지고 있고, 함수(method)를 가지고 있다. 이런 객체가 가지는 property와 method를 사용하기 위해서 이용하는 연산자 이다.

Python에서 Class의 특징#

python의 객체지향 특징 중 하나는 instance에 새로운 property나 method를 동적으로 추가하는게 가능하다.

car_1.color = 'Red'

아이러니하게 객체지향과는 거리가 있는 특징이다. 이런 특징은 프로그램을 상당히 유연하게 작성할 수 있게 만들어준다. 하지만 여기서 발생할 수 있는 문제가 있는데 바로 scope문제 이다. 변수를 찾는 순서는 instance namespace, class namespace, superclass namespace 순서이다.

#   결과가 어떻게 나올까요?
car_1.example_var = 300
car_2.get_car_info()

car_1.example_car가 동작하는 과정에서 instance namespace를 check하고 같은 공간에 example_var가 없다는 것을 확인 후 python의 특징에 따라 동적으로 새로운 instance variable을 생성해버려서 차이점이 생겨버린다. 직접 class variable을 변경하기 위해서는 class내의 제어하는 method를 통해 직접 접근하여 제어를 하면 된다.

예시#

class Employee(object):
    raise_rate = 1.1 # class variable(기본 연봉 인상률)
    
    def __init__(self, u_name, u_pay):
#         instance variable
        self.u_name = u_name
        self.u_pay = u_pay
        
#     business method(여기서는 사람마다 다르기 때문에 instance method로 만들 것임)
    def apply_raise(self):
        self.u_pay = self.u_pay * self.raise_rate
        
    def get_user_info(self):
        return "현재 {}의 연봉은 {}입니다.".format(self.u_name, self.u_pay)
    
#     decorator를 이용해서 class method를 정의해야 해요!
    @classmethod
    def change_raise_rate(cls, rate): # class method이기 때문에 instance를 지칭하는 self가 아닌 class를 지칭하는 cls가 나와야함
#         값이 들어올 때 class referance가 들어오기 때문에 바로 class variable로 지칭이 된다.
        cls.raise_rate = rate
        print('기본 연봉 인상률이 {}으로 조정되었습니다.'.format(cls.raise_rate))

#     보통 class안에서 instance method와 class method는 각각 역할이 있는데.. property를 제어하지 않고 단일 동작하는 method를 작성할때는 어떻게 할까??
#     decorator를 통해 static method로 작성하면 된다.
#     @staticmethod
#     def my_func():
        
emp_1 = Employee('Kim', 30000000)
emp_2 = Employee('Kang', 20000000)

# 연봉인상 전 두 사람의 정보를 출력
print(emp_1.get_user_info())
print(emp_2.get_user_info())

# 연봉인상률을 변경해요
Employee.change_raise_rate(1.5)
# emp_1.apply_raise()
# print(emp_1.get_user_info())
# emp_1.change_raise_rate(1.7)

# 연봉인상률을 적용해요
emp_1.apply_raise()
emp_2.apply_raise()

# 변경된 인상률을 출력해요
print(emp_1.get_user_info())
print(emp_2.get_user_info())

# emp_1.__u_mobile = '1234-5965'
# 이런식으로 direct access를 할 수 없도록 제한할 수 없나요?
# 이럴 때 property 앞에 __를 붙이면 java에서 사용되는 private 역할을 수행할 수 있습니다. 혹은 _를 붙이면 protected로 수행합니다. 이는 python 표준이지만 jupyter는 그렇지 않나봅니다..    

객체지향의 꽃 상속(Inheritance)#

class를 상속해서 다른 class를 확장시키는 기법이다.

부모 class => parent class, super class, upper class 자식 class => child class, sub class

# 부모 class
class Unit(object):
    def __init__(self, damage, life):
        self.damage = damage # 공격력
        self.life = life # 생명력
# 상속이 없다면??
# class Tank(object):
#     def __init__(self, damage, life, missile_ammo):
#         self.damage = damage # 공격력
#         self.life = life # 생명력
#         self.missile_ammo = missile_ammo # 미사일 탄창수?

# 상속을 사용하면?
class Tank(Unit):   # Inheritance
#     pass
    def __init__(self, damage, life, missile_ammo):
        super(Tank, self).__init__(damage, life)
        self.missile_ammo = missile_ammo

tank_1 = Tank(100, 50, 300)

tank_1.damage = 1000

magic method#

보통 method 이름 앞에 __가 붙은 method를 지칭하며 class 안에 미리 정의된 특수한 기능을 하고 있는 method이다 (ex. init())

class Part_time(Employee):
    
#     initializer역할(초기화 역할)
    def __init__(self, name, pay, part):
        super(Part_time, self).__init__(name, pay)
        self.u_part = part
        print('생성자가 호출되었어요!')
        
#     소멸자    
    def __del__(self):
        print('소멸자가 호출되었어요!')
        
#     instance를 문자열화 시킬때 호출되는 method
    def __str__(self):
        return '뿡뿡이'
        
    def get_part_time(self):
        return '파트는 {} 입니다.'.format(self.part)
    
emp_3 = Part_time('Kong', 10000000, '편의점')

print(emp_3)

first class(일급 클래스)#

first-class citizen : 프로그래밍 개체(함수, 객체, 변수, 등등)들이 다음의 조건을 충족하면 기본적으로 first-class citizen이라고 한다.

  1. 변수에 저장될 수 있다
  2. 함수의 인자로 전달될 수 있어야 한다
  3. 함수의 결과로 리턴될 수 있어야 한다.

앞에서 지금까지 설명한 class로 부터 파생된 instance는 1급 객체라고 불린다. functions도 first class 조건을 만족 == python의 함수는 1급 함수 단, 다른 언어에서는 아닐 수도 있다. (ex. C에서는 아님)


def my_add(x, y):
    return x + y

print(my_add(10, 20))   #   result : 30

# 1. 변수에 저장될 수 있어요!
f = my_add
print(f(100, 200))   #   result : 300

# 2. 함수의 인자로 전달될 수 있어야 해요!
def my_mul(x, y):
    return x * y

def my_operation(func, x, y):
    result = func(x, y)
    return result

print(my_operation(my_mul, 10, 20))   #   result : 200

# 3. 함수의 결과로 리턴될 수 있어야 해요!
def addMaker(x): # x는 지역변수
    def my_add_maker(y):
        return x + y
    return my_add_maker

add_5 = addMaker(5)
add_10 = addMaker(10)

print(add_5(1))    #   result : 6 # 이상한데?
print(add_10(1))    #   result : 11 # 머선129?
# => Closure 현상 = 1급 함수에서 나타나는 현상