객체를 이용한 프로그램을 객체는 속성과 기능으로 구성된다.
ex) 객체(자동차) = 속성(색상,길이,가격), 기능(전진,후진,정지 ...)
객체는 클래스에서 생성된다.
객체 사용의 장점 : 코드 재사용, 모듈화에 좋다
# 클래스 선언, 함수와 다르게 첫글자 대문자로 하는게 관례
class Car:
    
    # 생성자, 속성
    # init : 객체를 초기화해주는 생성자
    # self.color 여기에 있는 color는 self의 속성
    def __init__(self, color, length):
        self.color = color
        self.length = length
    # 기능
    def doStop(self):
        print("STOP!")
        
    # 객체 생성    
    car1 = Car('balck', 200)
class NewGenerationPC:
    
    def __init__(self, name, cpu, memory, ssd):
        self.name = name
        self.cpu = cpu
        self.memory =memory
        self.ssd = ssd
        
    def doExcel(self):
        print("EXCEL RUN!!")
        
    def doPhotoshop(self):
        print("PHOTOSHOP RUN!!")
    
    def printPCInfo(self):
        print(f"self.name : {self.name}")
        
myPC = NewGenerationPC('myPC', 'i5', '16G', '256G')
myPC.printPCInfo()
fPC = NewGenerationPC('fPC', 'i7', '32G', '512G')
# 객체의 속성 변경, 아래처럼 변경가능  
myPC.cpu = 'i9'
myPC.memory = '64G'
# 아래처럼 생성자에 파라미터 초기화 안하고 생성해도 사용가능
class Cal:
    
    def __init__(self):
        self.number1 = 0
        self.number2 = 0
        self.result = 0
        
    def add(self):
        self.result = self.number1 + self.number2
        return self.result
    
cal1 = Cal()
cal1.number1, cal1.number2 = 1, 2
cal1.cad()
    
=> 메모리 주소를 참조하기 때문에 shallow copy 문제가 발생한다.
class Robot:
    
    def __init__(self, color, height, weight):
        self.color = color
        self.height = height
        self.weight = weight
        
    def printRobotInfo(self):
        print(f"color : {self.color}")
        
rb1 = Robot('red', 200, 80)
rb2 = Robot('blue', 300, 120)
rb3 = rb1
class TemCls:
    
    def __init__(self, n ,s):
        self.number = n
        self.str = s
    
    def printClsInfo(self):
        print(f"self.number : {self.number}")
# 얕은 복사        
tc1 = TemCls(10, 'Hello')
tc2 = tc1
# 깊은 복사
import copy
tc1 = TemcCls(10, "hello")
tc2 = copy.copy(tc1)
import copy
scores = [9, 8, 7, 6, 5]
scoresCopy = []
scoresCopy = scores
# id() 함수 : 주소값 알려줌
print(f"id(scores) : {id(scores)}")
# 깊은 복사
scoresCopy = scores.copy()
class NormalCar:
    
    def drive(self):
        print('[NormalrCar] drive() called!')
        
    def back(self):
        print('[NormalCar] back() called!')
# 상속받을 클래스를 괄호() 안에 명시하면 상속 됨        
class TurboCar(NormalCar):
    
    def turbo(self):
        print('[TurboCar] turbo() called!')
        
myTurboCar = TurboCar()
myTurboCar.turbo()
myTurboCar.back()
cal = Calculator() => Calculator()생성자 호출 => Calculator__init__() 호출
class Calculator:
    
    def __init__(self, n1, n2):
        print('[Calclator] __init() called!!')
        self.num1 = n1
        self.num2 = n2
        
cal = Calculator(10, 20)
# 또는 아래와 같이 고정해서 초기화 가능
class Calculator:
    
    def __init__(self):
        print('[Calclator] __init() called!!')
        self.num1 = 10
        self.num2 = 100
class P_class:
    
    def __init__(self, pNum1, pNum2):
        print('[P_class] __init__() called')
        self.pNum1 = pNum1
        self.pNum2 = pNum2
        
class C_class(P_class):
    
    def __init__(self, cNum1, cNum2):
        print('[C_class] __init__()called')
        self.cNum1 = cNum1
        self.cNum2 = cNum2
        
# 방법 1
class C_class(P_class):
    
    def __init__(self, cNum1, cNum2):
        print('[C_class] __init__()called')
        # 강제로 호출
        P_class.__init__(self, cNum1, cNum2)
        
        self.cNum1 = cNum1
        self.cNum2 = cNum2
        
# 방법 2
class C_class(P_class):
    
    def __init__(self, cNum1, cNum2):
        print('[C_class] __init__()called')
        # super() 사용
        super().__init__(cNum1, cNum2)
        
        self.cNum1 = cNum1
        self.cNum2 = cNum2
2개 이상의 클래스를 상속
ex) Car가 car01, car02, car03 여러개를 상속받을 수 있음
class Car01:
    
    def drive(sef):
        print('drive() method called!')
        
class Car02:
    
    def turbo(sef):
        print('turbo() method called!')
class Car03:
    
    def fly(sef):
        print('fly() method called!')
        
        
class Car(Car01, Car02, Car03):
    
    def __init__(self):
        pass
    
myCar = Car()
myCar.drive()
class Robot:
    
    def __init__(self, c):
        self.color = c
    
    def fire(self):
        print("총알 발사!")
        
class NewRobot(Robot):
    
    def __init__(self, c):
        super().__init__(c)
    
    def fire(self):
        print("레이저 발사!!")
        
new = NewRobot('red')
new.fire()
# ABCMeta : 강제성을 띄게하는 상위클래스
# abstractedmethod : 강제로 구현하게 하는 메소드 데코레이션(@abstractmethod)을 위한 모듈
from abc import ABCMeta
from abc import abstractmethod
class AirPlane(metaclass=ABCMeta):
    
    @abstractmethod
    def flight(self):
        pass
    
class AirLiner(AirPlane):
    
    def flight(self):
        print('난다난나나닫다')