Learn & Record

Python (정적메소드, 메소드, 클래스, 상속, 생성자, 소멸자, 연습문제) 본문

Dev/Python

Python (정적메소드, 메소드, 클래스, 상속, 생성자, 소멸자, 연습문제)

Walker_ 2024. 2. 29. 16:08

1. 클래스

# 3. 클래스 정의
# 클래스 정의 방법
# 1) class 키워드로 클래스를 정의
# 2) 클래스 이름은 Upper Camel Case 규칙을 따름
# 파이썬은 변수나 함수의 이름을 네이밍할 때 언더바 (_)를 이용해 단어를 연결하는 Snake Case 방식을 사용하지만
# 클래스는 Upper Camel Case 규칙을 따름
# print + member : printmember 1) print_member 2) printMember 3) PrintMember

# 클래스는 다음과 같은 형식으로 정의
# class 클래스 :
#     본문

# 4. 객체 생성
# 클래스가 정의되었다면 다음과 같은 형식으로 객체를 생성
# 객체 = 클래스()

# 2개의 객체를 만들고 싶으면
# 객체1 = 클래스()
# 객체2 = 클래스()

# 5. 클래스 정의와 객체 생성
class WaffleMachine: # WaffleMachine 이라는 클래스 정의
    pass

waffle1 = WaffleMachine() # WaffleMachine 이라는 클래스를 이용해 waffle이라는 객체 생성
waffle2 = WaffleMachine()

print(waffle1) # <__main__. .. ~ 주솟값>
# 메모리의 주소 번지에 저장된 WaffleMachine의 객체라는 의미
print(waffle2)

# 클래스의 구성
# 1. 클래스의 기본 구성
# 객체를 만들어내는 클래스는 객체가 가져야 할 '값과 기능'을 가지고 있어야 함
# 같은 변수, 기능은 함수
# 정리하면 클래스는 변수와 함수로 구성된다고 볼 수 있음

# 클래스를 구성하는 변수는 1) 클래스를 기반으로 생성된 모든 인스턴스들이 공유하는 변수인 '클래스 변수'와
# 2) 모든 인스턴스들이 개별적으로 가지는 변수인 '인스턴스 변수'로 분리됨

# 클래스를 구성하는 함수는 메소드 method라고 하고
# 1) 클래스 메소드 2) 정적 메소드 3) 인스턴스 메소드로 분리

# 2. 인스턴스 변수와 인스턴스 메소드
# 인스턴스 변수란 클래스를 기반으로 만들어지는 모든 인스턴스들이 각각 따로 저장하는 변수
# 모든 인스턴스 변수는 self 라는 키워드를 앞에 붙여줌
# 인스터스 메소드란 인스턴스 변수를 사용하는 메소드
# 인스턴스 메소드는 반드시 첫 번째 매개변수로 self를 추가해야 함

class Person: # Person 클래스를 정의
    
    # 첫 번째 매개변수가 self이므로 인스턴스 메소드
    # 모든 인스턴스는 who_am_i() 메소드를 호출할 수 있음
    # 매개변수 self에는 메서드를 호출하는 인스턴스가 전달
    # self를 제외한 나머지 매개변수의 실제로 사용될 데이터가 전달
    def who_am_i(self, name, age, tel, address):
        #인스턴스 변수 = 매개변수. 모든 인스턴스 변수는 최초에 값이 대입되는 시점에 알아서 생성
        self.name = name
        self.age = age
        self.tel = tel
        self.address = address

boy = Person() # 인스턴스 boy가 생성. 클래스의 생성자가 호출
# print(boy.name) # AttributeError: 'Person' object has no attribute 'name'
boy.who_am_i('john', 15,'123-1234', 'toronto') # 인스턴스 메서드 호출
print(boy.name) # john
print(boy.age) # 15
print(boy.tel) # 123-1234
print(boy.address) # toronto

# 클래스에 없는 속성도 추가가 가능함. 다른 언어의 객체와는 다른 방식
boy.email = 'test@test.com'
print(boy.email) # test@test.com

class Computer:
    def set_spec(self, CPU, RAM, VGA, SSD):
        self.CPU = CPU
        self.RAM = RAM
        self.VGA = VGA
        self.SSD = SSD

    def hardware_info(self):
        # print(self.CPU, self.RAM, self.VGA, self.SSD)>
        print(f'CPU = {self.CPU}')
        print(f'RAM = {self.RAM}')
        print(f'VGA = {self.VGA}')
        print(f'SSD = {self.SSD}')



computer1 = Computer()
computer1.set_spec('cpu', 32,16,512)
computer1.hardware_info()
print(computer1.CPU)

 

2. 클래스 연습문제

# 1. 다음 지시사항을 읽고 책 제목과 저자 정보를 저장할 수 있는 Book 클래스를 생성하세요.

# 지시사항
# 1. 책 제목과 책 저자 정보를 출력하는 print_info() 메서드를 구현하세요.
# 2. 다음과 같은 방법으로 book1과 book2 인스턴스를 생성하세요.
#
# # book1, book2 인스턴스의 생성
# book1 = Book()
# book2 = Book()
#
# # book1, book2 제목과 저자 정보 저장
# book1.set_info('파이썬', '민경태')
# book2.set_info('어린왕자', '생텍쥐페리')
#
# 실행 예)
# 책 제목: 파이썬
# 책 저자: 민경태
# 책 제목: 어린왕자
# 책 저자: 생텍쥐페리

# class Book:
#     def set_info(self, name, author):
#         self.name = name
#         self. author = author
#
#     def print_info(self):
#         print(f'책 제목: {self.name}')
#         print(f'책 저자: {self.author}')
#
# book1 = Book()
# book2 = Book()
#
# book1.set_info('파이썬', '민경태')
# book2.set_info('어린왕자', '생텍쥐페리')
#
# book1.print_info()
# book2.print_info()


# 2
# class Watch:
#
#     def watch(self,hour, minute, second):
#         hour.self = hour
#         minute.self = minute
#         second.self = second
#
#
#     def add_second(self, second):
#         self.second = self.second + second
#         if self.second > 59:
#             self.second -= 60
#             self.minute = self.minute + 1
#
#
#     def add_minute(self, minute):
#         self.minute = self.minute + minute
#         if self.minute > 59:
#             self.minute -= 60
#             self.hour = self.hour + 1
#
#
#     def add_hour(self, hour):
#         self.hour = self.hour + hour
#         if self.hour > 23:
#             self.hour -= 60
#
#     def print_time(self):
#         print(f'계산된 시간은 {self.hour}시 {self.minute}분 {self.second}초입니다')
#
# watch1 = Watch()
#
# time = input('시간을 입력하세요 >>> ')
# time = time.split(':')
# watch1.watch(int(time[0]), int(time[1]), int(time[2]))
# hour = int(input('계산할 시간를 입력하세요 >>> '))
# minute = int(input('계산할 분를 입력하세요 >>> '))
# second = int(input('계산할 초를 입력하세요 >>> '))
#
# watch1.add_second()
# watch1.add_minute()
# watch1.add_hour()
# watch1.print_time()

class Watch:
    def set_time(self) -> None:
        time : str = input("시간을 입력하세요 >>> ")
        self.hour : int = int(time[0:2])
        self.minute : int = int(time[3:5])
        self.second : int = int(time[6:8])

    def add_hour(self) -> None:
        hour: str = input('계산할 시간을 입력하세요 >>> ')
        self.hour += int(hour)

    def add_minute(self) -> None:
        minute = input('계산할 분을 입력하세요 >>> ')
        self.hour += int(minute) // 60
        self.minute += int(minute) % 60

    def add_second(self) -> None:
        second = input('계산할 초를 입력하세요 >>> ')
        self.hour += int(second) // 3600
        self.minute += int(second) % 3600 // 60
        self.second += int(second) & 60

    def print_time(self) -> None:
        print(f'계산된 시간은 {self.hour}시, {self.minute}분, {self.second}초입니다.')


# 3
class Song:
    def set_song(self, title, category):
        self.title = title
        self.category = category

    def print_song(self):
        print(f'노래제목: {self.title}({self.category})')

class Singer:
    def set_singer(self, name):
        self.name = name

    def hit_song(self, song):
        self.song = song

    def print_singer(self) -> None:
        print(f'가수이름 : {self.name}')
        self.song.print_song()

 

3. 생성자

# 생성자와 소멸자
# 1. 생성자

# 값을 가진 인스턴스를 생성하기 위해 생성자를 이용
# 생성자는 인스턴스를 생성할 때 자동으로 호출되는 특별한 메소드
# 모든 클래스는 __init__() 이라는 이름을 가진 생성자를 가지고 있음
# __init__() 메소드(생성자)는 인스턴스가 생성될 때 자동으로 호출되기 때문에 인스턴스 변수의 초기화 용도로 사용

# ** __init__
# 새로운 인스턴스가 만들어질 때 자동으로 호출되는 메서드
# 생성자는 아님. 생성자는 '__new__'이고, 생성자가 객체를 메모리에 할당해서 리턴을 하는데,
# 리턴을 하기 전에 __init__ 메서드를 호출
# __init__ 에서는 '새로운 속성을 추가하는 일'일 해야 함.

class Candy:
    def __init__(self, shape, color):
        print('실행이 될까?')
        self.shape = shape
        self.color = color

satang = Candy('circle', 'brown')
print(satang.color)

 

4. 소멸자

# 소멸자
# 인스턴스가 생성될 때 자동으로 호출되는 생성자와 마찬가지로
# 인스턴스가 소멸될 때 자동으로 호출되는 메서드
# 소멸자는 __del__()

class Sample:
    def __del__(self):
        print('인스턴스가 소멸됩니다')

sample = Sample()
del sample # 인스턴스가 소멸됩니다

# 매직 메서드 샘플
class Sample:
    def __int__(self):
        pass

    def __len__(self):
        return 6

    def __str__(self):
        return 'what?'

li = [1,2,3]
print(len(li)) # 3

sample = Sample()
print(len(sample)) # 6
print(sample) # what?

 

5. 클래스메소드

# 클래스 메소드와 정적 메소드
# 1. 클래스 변수
# 클래스를 구현할 때 인스턴스마다 서로 다른 값을 가지는 경우에는 인스턴스 변수를 사용
# 모든 인스턴스 변수들은 self 키워드를 붙여서 사용
# 모든 인스턴스가 동일한 값을 사용할 때는 클래스 변수로 처리해서 모든 인스턴스들이 공유하도록 처리하는 것이 좋음
# 인스턴스 변수는 인스턴스마다 값을 별도로 저장해야 하지만 클래스 변수는 하나의 값을 모든 인스턴스가
# 공유하기 때문에 메모리 공간의 낭비를 막을 수 있음

# * self
# 관례적으로 모든 메서드의 첫 번째 매개변수 이름을 self로 지정
# self가 파이썬의 예약어는 아님
# self라는 명칭은 관례적으로 사용하는 단어일 뿐

class Korean:
    country = '한국' # 클래스 변수 country

    def __init__(self, name, age, address):
        self.name = name # 인스턴스 변수
        self.age = age
        self.address = address

print(Korean.country) # 객체 생성 전에도 사용 가능

man = Korean('홍길동', 35, '서울')
print(man.name)

print(man.country) # 인스턴스 man을 통한 클래스 변수 접근
print(Korean.country) # 클래스 Korean을 통한 클래스 변수 접근

man.country = '중국'
print(man.country) # 인스턴스 man을 통한 클래스 변수 접근
print(Korean.country) # 클래스 Korean을 통한 클래스 변수 접근

print('객체 프로퍼티 생성과 동일한 이름의 클래스 변수')
man.country = '중국' # 객체의 인스턴스 변수 생성
print(man.country) # 중국. 클래스 변수가 아니라 인스턴스 변수를 불러옴.
print(man.__class__.country) # 한국. 객체를 사용해서 클래스 변수 불러오기
print(Korean.country) # 한국

print()
print('객체를 이용해서 클래스 변수 값 변경')
man.__class__.country = '영국'
print(Korean.country) # 영국

print()
print('클래스를 이용해서 클래스 변수 값 변경')
Korean.country = '미국' # 클래스 변수를 변경
print(Korean.country) # 미국

man2 = Korean('홍길동2', 35, '서울')
print(man2.country) # 미국
print(Korean.country) # 미국

# 클래스 변수는 클래스를 통해서 접근하는 것이 권장사항

# 2. 클래스 메소드
# 클래스 변수를 사용하는 메소드를 의미
# 주요 특징
# 1. 인스턴스 혹은 클래스로 호출
# 2. 생성된 인스턴스가 없어도 호출할 수 있음
# 3. @classmethod 데코레이터 decorator를 표시하고 작성
# 4. 매개변수 self를 사용하지 않고 cls를 사용
# 클래스 메소드는 self를 사용하지 않기 때문에 인스턴스 변수에 접근할 수 없지만,
# cls를 통해서 클래스 변수에 접근할 수 있음

print()

class Korean:
    country = '한국' # 클래스 변수 country

    @classmethod
    def trip(cls, country):
        if cls.country == country:
            print('국내여행입니다.')
        else:
            print('해외여행입니다.')

Korean.trip('한국')
Korean.trip('미국')

# 3. 정적 메소드
# 1) 인스턴스 또는 클래스로 호출
# 2) 생성된 인스턴스가 없어도 호출 가능
# 3) @staticmethod 데코레이터 decorator를 표시하고 작성
# 4) 반드시 작성해야 할 매개변수가 없음

# 정적 메소드는 self와 cls를 모두 사용하지 않기 때문에 인스턴스 변수와 클래스 변수를 모두 사용하지 않는 메소드를 
# 정의하는 경우에 적절
# 정적 메소드는 클래스에 소속이 되어 있지만, 인스턴스에는 영향을 주지 않고 또 인스턴스로 부터 영향을 받지도 않음

class Korean:
    country = '한국' # 클래스 변수 country

    @staticmethod
    def slogan():
        print('Imagine your Korea')

Korean.slogan() # Imagine your Korea

 

6. 정적메소드

# 1. 정적 메소드
# 1) 인스턴스 또는 클래스로 호출
# 2) 생성된 인스턴스가 없어도 호출 가능
# 3) @staticmethod 데코레이터 decorator를 표시하고 작성
# 4) 반드시 작성해야 할 매개변수가 없음

# 정적 메소드는 self와 cls를 모두 사용하지 않기 때문에 인스턴스 변수와 클래스 변수를 모두 사용하지 않는 메소드를
# 정의하는 경우에 적절
# 정적 메소드는 클래스에 소속이 되어 있지만, 인스턴스에는 영향을 주지 않고 또 인스턴스로 부터 영향을 받지도 않음

class Korean:
    country = '한국' # 클래스 변수 country

    @staticmethod
    def slogan():
        print('Imagine your Korea')

Korean.slogan() # Imagine your Korea

 

7. 클래스_예제

 - sample_class 파일 생성 후 코드 작성

class printTxt:
    @staticmethod
    def print_greet():
        print('hello class')

 - sample_module 파일 생성 후 코드 작성

def print_greet():
    print('hello module')

 - import 파일 생성 후 코드 작성

# 클래스의 메서드 사용
import sample_class
sample_class.printTxt.print_greet()

# 클래스의 메서드 사용
from sample_class import printTxt
printTxt.print_greet()

# 모듈의 함수 사용
import sample_module
sample_module.print_greet()

8. 상속

# 상속
# 1. 상속이란?
# 어떤 클래스가 가지고 있는 기능을 그대로 물려받아서 사용할 수 있는 클래스를 만들 수 있슴
# 다른 클래스의 기능을 물려받을 때 상속받는다는 표현을 사용
# 그래서 상속 관계에 있는 클래스를 표현할 때 부모 클래스와 자식 클래스하는 말을 사용

# 부모 클래스 : 상속해 주는 클래스 / 슈퍼 클래스 super class , 기반 클래스 base class
# 자식 클래스 : 상속 받는 클래스 / 서브 클래스 sub class, 파생 클래스 derived class

# 2. 상속 관계 구현
# 기본적으로 두 클래스가 상속 관계에 놓이려면 IS-A 관계가 성해야 함
# IS-A 관계란 '~은 ~ 이다.'로 해설할 수 있는 관계를 의미
# '학생은 사람이다 Student is a Person'처럼 해석되는 것이 IS-A 관계
# 이때 Student 는 서브 클래스가 되고, Person은 슈퍼 클래스가 됨

# 컴퓨터 과학에서 상속은 is-a 관계를 갖고 있을 때 사용.
# is-a 관계는 '직원은 사람이다 employee is-a person' 또는 '자동차는 차량이다 car is-a vehicle'이라고 부르며,
# has-a 관계는 컴퓨터 과학 용어도 '그릇이 스쿱을 갖는다 Bowl has-a Scoop'라고 부르고,
# has-a 관계는 상속이 아니라 구성 Composition으로 구현함.
#
# 객체 지향 프로그래밍을 처음 접하면 두 클래스가 어떤 관계를 갖고 있을 때, 반드시 상속 관계가 만들어져야 한다고 생각하는 경우가 많음.
# is-a 관계를 가질 경우에는 상속, has-a 관계를 가질 경우에는 구성을 사용한다고 구분해서 생각하면
# 어떤 경우에 상속등을 활용해야 하는 지 조금 더 쉽게 구분.


# 상속의 기본적인  형식
# calss 슈퍼 클래스:
#   본문
#
# calss 서브 클래스(슈퍼 클래스):
#   본문

# 서브 클래스를 구현할 때는 괄호 안에 어떤 슈퍼 클래스를 상속 받는 지 명시
# 상속 관계에 놓인 서브 클래스는 마치 자신의 것처럼 슈퍼 클래스의 기능을 사용할 수 있음

class Person: # 슈퍼 클래스
    def __init__(self, name: str) -> None:
        self.name = name

    def eat(self, food: str) -> None:
        print(f'{self.name}가 {food}를 먹습니다.')

class Student(Person): # 서브 클래스
    def __init__(self, name: str, school: str):
        super().__init__(name) # 슈퍼 클래스의 생성자 실행
        self.school = school

    def study(self) -> None:
        print(f'{self.name}는 {self.school}에서 공부를 합니다.')

potter = Student('해리포터', '호그와트')
potter.eat('감자') # 해리포터가 감자를 먹습니다.
potter.study() # 해리포터는 호그와트에서 공부를 합니다.

# 3. 서브 클래스의 __init__()
# 서브 클래스의 생성자를 구현할 때는 반드시 슈퍼 클래스의 생성자를 먼저 호출하는 코드를 작성해야 함
# super라는 키워드는 슈퍼 클래스를 의미

class Computer: # 슈퍼 클래스
    def __init__(self):
        print('슈퍼 클래스의 생성자가 실행되었습니다.')

class NoteBook(Computer): # 서브 클래스
    def __init__(self):
        super().__init__()
        print('서브 클래스의 생성자가 실행되었습니다.')

n = NoteBook()

# 4. 서브 클래스의 인스턴스 자료형
# 슈퍼 클래스 객체는 슈퍼 클래스의 인스턴스
# 그에 비해 서브 클래스 객체는 서브 클래스의 인스턴스이면서 동시에 슈퍼 클래스의 인스턴스
# 즉 서브 클래스 Student의 객체는 서브 클래스 Student의 인스턴스 이면서
# 동시에 슈퍼 클래스 Person의 인스턴스

# 어떤 객체가 어떤 클래스의 인스턴스인지 확인하기 위해서 isinstance() 함수를 사용
# 객체가 인스턴스일 경우에는 True 아니면 False 반환
# isinstance(객체, 클래스)
print(isinstance(potter, Student)) # True
print(isinstance(potter, Person)) # True

# 원두 (bean)를 저장하는 Coffee 클래스와 원두(Bean)에 물을 섞은 Espresso 클래스를 상속
class Coffee:
    def __init__(self, bean:str):
        self.bean = bean

    def coffee_info(self):
        print(f'원두: {self.bean}')

class Espresso(Coffee):
    def __init__(self, bean:str, water: str):
        super().__init__(bean) # 슈퍼클래스의 생성자 실행
        self.water = water

    def espresso_info(self):
        super().coffee_info() # 슈퍼클래스의 메서드 실행
        print(f'물: {self.water}ml')

class Americano(Espresso):
    def __init__(self, bean:str, water: str, more_water: str):
        super().__init__(bean, water)
        # Espreesso.__init__(self, bean, water) # 클래스 이름으로 접근도 가능. 객체를 매개 변수로 전달
        self.more_water = more_water

    def americano_info(self):
        super().espresso_info() # 슈퍼클래스의 메서드 실행
        # Espresso.espresso_info(self) # 클래스 이름으로 실행을 할 경우에는 객체를 매개 변수로 전달
        print(f'물 더 : {self.more_water}')

coffee = Espresso('콜롬비아', '30')
coffee.espresso_info()
# 원두: 콜롬비아
# 물: 30ml
print()

coffee = Americano('파나마','20','200')
coffee.americano_info()
# 원두 : 파나마
# 물 : 20ml
# 물 더 : 200ml
print()

Espresso.espresso_info(coffee) # 인스턴스 메서드이지만 클래스 이름으로 호출 가능. 대신 객체를 매개 변수 self로 전달해야 함

 

9. 메서드 연습문제

# 1. 다음 지시사항을 읽고 이름과 전체 인구수를 저장할 수 있는 Person 클래스를 생성하세요.
#
# 지시사항
# 1) 다음과 같은 방법으로 man과 woman 인스턴스를 생성하세요.
# man = Person('james')
# woman = Person('emily')
#
# 2) man과 woman 인스턴스가 생성되면 다음과 같은 메세지를 출력할 수 있도록 처리하세요.
# james is born.
# emily is born.
#
# 3) 다음 코드를 통해서 전체 인구수를 조회할 수 있도록 처리하세요.
# print('전체 인구수: {}명'.format(Person.get_population())) # 전체 인구수: 2명
#
# 4) 다음과 같은 방법으로 man 인스턴스를 삭제하세요.
# del man
#
# 5) man 인스턴스가 삭제되면 다음과 같은 메시지를 출력할 수 있도록 처리하세요.
# james is dead.


class Person:
    human = 0
    def __init__(self, name):
        self.name = name
        print(f'{name} is born')
        Person.human += 1

    def __del__(self):
        print(f'{self.name} is dead.')
        Person.human -= 1

    @staticmethod
    def get_population():
        print(f'전체 인구수: {Person.human}명')

    @classmethod
    def get_population(cls) -> int:
        return cls.human

man = Person('james')
woman = Person('emily')
Person.get_population()
del man
Person.get_population()
del woman

# 2. 다음 지시사항을 읽고 가게의 매출을 구할 수 있는 Shop 클래스를 구현하세요.
#
# 지시사항
# 1) Shop 클래스는 다음과 같은 클래스 변수를 가지고 있습니다.
# total은 전체 매출액을 의미하고 menu_list의 각 요소는 {메뉴명: 가격}으로 구성된 딕셔너리입니다.
#
# total = 0
# menu_list = [{'떡볶이': 3000}, {'순대': 3000}, {'튀김': 500}, {'김밥': 2000}]
#
# 2) 매출이 생기면 다음과 같은 방식으로 메뉴와 판매량을 작성합니다.
# Shop.sales('떡볶이', 1)  # 떡볶이를 1개 판매
# Shop.sales('김밥', 2)  # 김밥을 2개 판매
# Shop.sales('튀김', 5)  # 튀김을 5개 판매
#
# 3) 모든 매출을 작성한 뒤 다음과 같은 방식으로 전체 매출액을 확인합니다.
# 매출: 9500원

class Shop:
    total = 0
    menu_list = [{'떡볶이': 3000}, {'순대':3000}, {'튀김':500}, {'김밥': 2000}]

    @staticmethod
    def sales(name:str, count: int):
        for menu in Shop.menu_list:
            if menu.keys() == name:
                Shop.total += count * menu.values()

    @staticmethod
    def totalMoney():
        print(f'매출: {Shop.total}원')

Shop.sales('떡볶이',1)
Shop.totalMoney()

# 3. 다음 지시사항을 읽고 Hybrid 클래스를 구현하세요.
#
# 지시사항
# 1) 다음과 같은 슈퍼 클래스 Car를 가지고 있는 서브 클래스 Hybrid를 구현하세요.
# class Car:
#
#   max_oil = 50 # 최대 주유량
#
#   def __init__(self, oil):
#     self.oil = oil
#
#   def add_oil(self, oil):
#     if oil <= 0: # 0 이하의 주유는 진행하지 않음
#        return
#     self.oil += oil
#     if self.oil > Car.max_oil: # 주유 후 최대 주유량 초과 상태이면
#       self.oil = Car.max_oil # 현재 주유량을 최대 주유량으로 설정
#
#   def car_info(self):
#     print('현재 주유상태: {}'.format(self.oil))
#
# 2) 서브 클래스 Hybrid는 다음과 같은 특징을 가지고 있습니다.
# 최대 배터리 충전량은 30입니다.
# 배터리를 충전하는 charge() 메서드가 있습니다.
# 최대 충전량을 초과하도록 충전할 수 없고, 0보다 작은 값으로 충전할 수 없습니다.
# 현재 주유량과 충전량을 모두 확인할 수 있는 hybrid_info() 메서드가 있습니다.
#
# 3) 다음과 같은 방식으로 전체 동작을 확인할 수 있습니다.
# car = Hybrid(0, 0)
# car.add_oil(100)
# car.charge(50)
# car.hybrid_info() # 현재 주유상태: 50 # 현재 충전상태: 30
# 3
class Car:

  max_oil = 50 # 최대 주유량

  def __init__(self, oil):
    self.oil = oil

  def add_oil(self, oil):
    if oil <= 0: # 0 이하의 주유는 진행하지 않음
       return
    self.oil += oil
    if self.oil > Car.max_oil: # 주유 후 최대 주유량 초과 상태이면
      self.oil = Car.max_oil # 현재 주유량을 최대 주유량으로 설정

  def car_info(self):
    print('현재 주유상태: {}'.format(self.oil))

class Hybrid(Car):
    max_battery = 30

    def __init__(self, oil, battery):
        super().__init__(oil)
        self.battery = battery

    def add_charge(self, battery):
        if battery <= 0: # 0 이하의 주유는 진행하지 않음
            return
        self.battery += battery
        if self.battery > Hybrid.max_battery: # 주유 후 최대 주유량 초과 상태이면
            self.battery = Hybrid.max_battery # 현재 주유량을 최대 주유량으로 설정


    def hybrid_info(self):
        print(f'현재 주유상태 : {self.oil}, 현재 충전상태 : {self.battery}')