class FourCal:
    pass

a = FourCal()
print(a)

class Student:
    def __init__(self, name, age):
        print("객체가 생성되었습니다")

        self.name = name
        self.age = age

    def printf(self):
        print(self.name, self.age)

stu = Student("김진환 님", 53)
stu.printf()

# student = Student()

# 클래스는 변수와 함수를 만들어놓은 틀이다. 
# 클래스(틀) : 학생은 이름이라는 속성을 갖고 있어!
# 객체(실체화 된 것) : 학생 이름은 "윤인성"이야
# 실체화 한 객체 = "인스턴스"

#생성자는 __init__ 을 이용해서 만든다. 반드시 self를 매개변수로 가져야 한다. 

# 이전에 계산한 결괏값을 유지하기 위해서 result 전역 변수(global)를 사용한다. 
# 프로그램을 실행하면 예상한 대로 다음과 같은 결괏값이 출력된다.


Calculator 클래스로 만든 별개의 계산기 cal1, cal2(파이썬에서는 이것을 객체라고 부른다)가 각각의 
역할을 수행한다. 그리고 계산기(cal1, cal2)의 결괏값 역시 다른 계산기의 결괏값과 상관없이 독립적인 값을 
유지한다. 클래스를 사용하면 계산기 대수가 늘어나더라도 객체를 생성만 하면 되기 때문에 
함수를 사용하는 경우와 달리 매우 간단해진다. 

class Calculator:
    def __init__(self):
        self.result = 0

    def add(self, num):
        self.result += num
        return self.result

cal1 = Calculator()
cal2 = Calculator()

print(cal1.add(3))
print(cal1.add(4))
print(cal2.add(3))
print(cal2.add(7))

과자 틀 → 클래스 (class)
과자 틀에 의해서 만들어진 과자 → 객체 (object)
여기에서 설명할 클래스는 과자 틀과 비슷하다. 클래스(class)란 똑같은 무엇인가를 계속해서 
만들어 낼 수 있는 설계 도면이고(과자 틀), 객체(object)란 클래스로 만든 피조물(과자 틀을 사용해 만든 과자)을 
뜻한다.

클래스로 만든 객체에는 중요한 특징이 있다. 
바로 객체마다 고유한 성격을 가진다는 것이다. 
과자 틀로 만든 과자에 구멍을 뚫거나 조금 베어 먹더라도 다른 과자에는 아무 영향이 없는 것과 
마찬가지로 동일한 클래스로 만든 객체들은 서로 전혀 영향을 주지 않는다.

다음은 파이썬 클래스의 가장 간단한 예이다.

class Cookie:
    pass

위의 클래스는 아무 기능도 갖고 있지 않은 껍질뿐인 클래스이다. 
하지만 이렇게 껍질뿐인 클래스도 객체를 생성하는 기능이 있다. "과자 틀"로 "과자"를 만드는 것처럼 말이다.

객체는 클래스로 만들며 1개의 클래스는 무수히 많은 객체를 만들어 낼 수 있다. 
위에서 만든 Cookie 클래스의 객체를 만드는 방법은 다음과 같다.

a = Cookie()
b = Cookie()

Cookie()의 결괏값을 돌려받은 a와 b가 바로 객체이다. 
마치 함수를 사용해서 그 결괏값을 돌려받는 모습과 비슷하다.

[객체와 인스턴스의 차이]

클래스로 만든 객체를 인스턴스라고도 한다. 그렇다면 객체와 인스턴스의 차이는 무엇일까? 이렇게 생각해 보자. 
a = Cookie() 이렇게 만든 a는 객체이다. 그리고 a 객체는 Cookie의 인스턴스이다. 
즉 인스턴스라는 말은 특정 객체(a)가 어떤 클래스(Cookie)의 객체인지를 관계 위주로 설명할 때 사용한다. 
"a는 인스턴스"보다는 "a는 객체"라는 표현이 어울리며 "a는 Cookie의 객체"보다는 "a는 Cookie의 인스턴스"라는 표현이 훨씬 잘 어울린다.

이렇게 객체에 초깃값을 설정해야 할 필요가 있을 때는 setdata와 같은 메서드를 호출하여 초깃값을 설정하기보다는 생성자를 구현하는 것이 안전한 방법이다. 
생성자(Constructor)란 객체가 생성될 때 자동으로 호출되는 메서드를 의미한다.
파이썬 메서드 이름으로 __init__를 사용하면 이 메서드는 생성자가 된다. 
다음과 같이 FourCal 클래스에 생성자를 추가해 보자.
※ __init__ 메서드의 init 앞뒤로 붙은 __는 언더스코어(_) 두 개를 붙여 쓴 것이다.

새롭게 추가된 생성자 __init__ 메서드만 따로 떼어 내서 살펴보자.

def __init__(self, first, second):
    self.first = first
    self.second = second

__init__ 메서드는 setdata 메서드와 이름만 다르고 모든 게 동일하다. 

단 메서드 이름을 __init__으로 했기 때문에 생성자로 인식되어 객체가 생성되는 시점에 
자동으로 호출 되는 차이가 있다.

__init__ 메서드도 다른 메서드와 마찬가지로 첫 번째 매개변수 self에 생성되는 객체가 자동으로 전달된다는 점을 기억하자.
따라서 __init__ 메서드가 호출되면 setdata 메서드를 호출했을 때와 마찬가지로 first와 second라는 객체변수가 생성될 것이다.

################################################

왜 상속을 해야 할까?

보통 상속은 기존 클래스를 변경하지 않고 기능을 추가하거나 기존 기능을 변경하려고 할 때 사용한다.
"클래스에 기능을 추가하고 싶으면 기존 클래스를 수정하면 되는데 왜 굳이 상속을 받아서 처리해야 하지?" 라는 의문이 들 수도 있다. 
하지만 기존 클래스가 라이브러리 형태로 제공되거나 수정이 허용되지 않는 상황이라면 상속을 사용해야 한다.
