배꼽파지 않도록 잘 개발해요

[코드잇] 객체 지향 프로그래밍 in Python - 객체 지향 프로그래밍의 4개의 기둥 본문

코드잇 Codeit/Python / ML / DL

[코드잇] 객체 지향 프로그래밍 in Python - 객체 지향 프로그래밍의 4개의 기둥

꼽파 2023. 9. 14. 13:14


  • 1. 추상화

  • 2. 캡슐화

  • 3. 상속

  • 4. 다형성

  • 1. 추상화

    추상화

    · 프로그래머들이 특정 코드를 사용할 때 필수적인 정보를 제외한 세부사항을 가리는 것
    · 변수, 함수, 클래스도 추상화의 예시임.
    - 변수의 값을 한번 설정하면 그 이후에 값을 몰라도 변수 이름만 알면 되기 떄문임.

    - 함수는 구현내용을 알지 못해도 파라미터만 잘 넣어주면 호출해서 사용할 수 있음.
    - 클래스 내부 내용을 몰라도 사용방법만 알면 사용할 수 있음.


    · 추상화 잘하는 방법
    - 클래스, 변수, 메소드 이름을 그 의미가 잘 담기도록 지어라.

    반복적으로 사용되는 코드는 최대한 변수, 함수, 또는 클래스로 만들어서 효율성을 높인다.


    문서화(docstring)

    문서화 문자열(documentation string)
    클래스나 메소드의 정보를 docsting으로 기록할 수 있음.
    클래스 및 메소드 바로 아래 설명 : """ 설명 """

    class BankAccount:
        """ 은행 계좌 클래스 """
        interest = 0.02
         
        def __init__(self, owner_name, balance):
            """ 인스턴스 변수: name(문자열), balance(실수형) """
            self.owner_name = owner_name
            self.balance = balance
            
        def deposit(self, amount):
            """ 잔액 인스턴스 변수 balance를 파라미터 amount만큼 늘려주는 메소드 """
            self.balance += amount
            
        def withdraw(self, amount):
            """ 잔액 인스턴스 변수 balance를 파라미터 amount만큼 줄여주는 메소드"""
            if self.balance < amount:
                print("Insufficient balance!")
            else:
                self.balance -= amount
                
        def add_interest(self):
            """ 잔액 인스턴스 변수 balance를 이자율만큼 늘려주는 메소드"""
            self.balance *= 1 + BankAccount.interest
    
    # 계좌 생성성
    example_account = BankAccount("Hong Gil dong", 1000)
    
    # 이자 계산
    example_account.add_interest() 
    print(example_account.balance)  # 1020.0
    
    # 입금금
    example_account.deposit(500)
    print(example_account.balance)  # 1520.0
    
    # 출금
    example_account.withdraw(2000)  # Insufficient balance!
    example_account.withdraw(1000)  # 520.0
    print(example_account.balance)

    help(BankAccount)

    클래스의 코드를 직접 보기 전에 Docstring만 보고 싶으면 help함수 사용


    타입 힌팅(Type Hinting)

    · 버전 3.5부터 파이썬에 도입된 타입을 표시할 수 있는 기능
    · 파이썬은 동적 타입 언어라서 변수 이름 앞에 자료형 표시할 필요가 없음.
    → x, y 파라미터에 어떤 것을 넣어야 할지 모르는 문제가 발생함.
    · 사용방법 : 

    # 변수
    변수 이름 : 타입
    interest : float = 0.02
    
    # 함수의 리턴값
    화살표(->) 자료형
    리턴값 없는 경우 None
    def fuction_name(self, amount: float) -> float

     

    파이썬과 자바 코드 비교

    ### Python
    
    i = 0
    j = 1
    
    def add(x, y):
        # some code
        # ---> x와 y에 무엇을 써야할지 모르겠음
    ### JAVA
    
    int i = 0;
    int j = 1;
    
    public int add(int x, int y) {
        // some code
    }

     

    사용 예시

    class BankAccount:
        """ 은행 계좌 클래스 """
        interest: float = 0.02
         
        def __init__(self, name: str, balance: float) -> None:
            """ 인스턴스 변수: name(문자열), balance(실수형) """
            self.owner_name = name
            self.balance = balance
            
        def deposit(self, amount: float) -> None:
            """ 잔액 인스턴스 변수 balance를 파라미터 amount만큼 늘려주는 메소드 """
            self.balance += amount
            
        def withdraw(self, amount: float) -> None:
            """ 잔액 인스턴스 변수 balance를 파라미터 amount만큼 줄여주는 메소드"""
            if self.balance < amount:
                print("Insufficient balance!")
            else:
                self.balance -= amount
                
        def add_interest(self) -> None:
            """ 잔액 인스턴스 변수 balance를 이자율만큼 늘려주는 메소드"""
            self.balance *= 1 + BankAccount.interest
            
    ### 적용해보면 잘 동작하는 걸 알 수 있음
    # 계좌 생성
    example_account = BankAccount("Hong Gil dong", "1000")
    
    # 입금
    example_account.deposit("500")
    print(example_account.balance)  # 1000500 (문자열 더하기)
    
    # 타입힌팅을 지키지 않아도 에러는 나지 않을 수 있으나,
    # 올바르지 않은 결과가 출력됨.

    2. 캡슐화

    캡슐화

    1) 객체의 일부 구현 내용에 대한 외부로부터의 직접적인 액세스를 차단하는 것

    2) 객체의 속성과 그것을 사용하는 행동을 하나로 묶는 것

    class Citizen:
        """주민 클래스"""
        drinking_age = 19  # 음주 가능 나이
        
        def __init__(self, name, age, resident_id):
            """이름, 나이, 주민등록번호"""
            self.name = name
            self.age = age
            self.resident_id = resident_id
            
        def authenticate(self, ide_field):
            """본인이 맞는지 확인하는 메소드"""
            return self.resident_id == id_field
            
        def can_drink(self):
            """음주 가능 나이인지 확인하는 메소드"""
            return self.age >= Citizen.drinking_age
        
        def __str__(self):
            """주민 정보를 문자열로 리턴하는 메소드"""
            return self.name + "씨는 " + str(self.age) + "살입니다!"
    
    
    # 인스턴스 생성
    child = Citizen("어린이", 4, "12345678")
    youth = Citizen("청청년", 28, "98765432")
    
    print(youth.resident_id)  # 출력: 98765432
    youth.age = -300  # 나이가 음수
    print(youth)  # 청청년씨는 -300살입니다!
    
    child.age = 35
    print(child.can_drink())  # True (음주 가능)

    캡슐화의 정의에 맞게 위 예시코드를 수정

    1) 객체의 일부 구현 내용에 대한 외부로부터의 직접적인 액세스를 차단하는 것

    → resident_id와 age 앞에 언더바 2개(__)를 붙인다.

    class Citizen:
        """주민 클래스"""
        drinking_age = 19  # 음주 가능 나이
        
        def __init__(self, name, age, resident_id):
            """이름, 나이, 주민등록번호"""
            self.name = name
            self.__age = age
            self.__resident_id = resident_id
            
        def __authenticate(self, ide_field):
            """본인이 맞는지 확인하는 메소드"""
            return self.__resident_id == id_field
            
        def can_drink(self):
            """음주 가능 나이인지 확인하는 메소드"""
            return self.__age >= Citizen.drinking_age
        
        def __str__(self):
            """주민 정보를 문자열로 리턴하는 메소드"""
            return self.name + "씨는 " + str(self.__age) + "살입니다!"
    
    
    # 인스턴스 생성
    child = Citizen("어린이", 4, "12345678")
    youth = Citizen("청청년", 28, "98765432")
    
    # 클래스 외부에서 접근
    print(youth.resident_id)
    # AttributeError: 'Citizen' object has no attribute 'age'
    
    print(youth.age)
    # AttributeError: 'Citizen' object has no attribute 'age'
    
    print(youth.can_drink())  
    # True
    
    youth.__authenticate("241215")
    # AttributeError: 'Citizen' object has no attribute '__authenticate

     

    2) 객체의 속성과 그것을 사용하는 행동을 하나로 묶는 것

    변수에 접근하는 통로를 메소드로 따로 만들어서 제한함.
    → __age 변수에 접근 가능한 메소드 : can_drink, get_age, set_age 

    class Citizen:
        """주민 클래스"""
        drinking_age = 19  # 음주 가능 나이
        
        def __init__(self, name, age, resident_id):
            """이름, 나이, 주민등록번호"""
            self.name = name
            self.__age = age
            self.__resident_id = resident_id
            
        def __authenticate(self, ide_field):
            """본인이 맞는지 확인하는 메소드"""
            return self.__resident_id == id_field
            
        def can_drink(self):
            """음주 가능 나이인지 확인하는 메소드"""
            return self.__age >= Citizen.drinking_age
        
        def __str__(self):
            """주민 정보를 문자열로 리턴하는 메소드"""
            return self.name + "씨는 " + str(self.__age) + "살입니다!"
            
        def get_age(self):
            """age 값을 읽는 메소드"""
            return self.__age
        
        def set_age(self, value):
            """age 값을 설정하는 메소드"""
            self.__age = value
    
    
    # 인스턴스 생성
    child = Citizen("어린이", 4, "12345678")
    youth = Citizen("청청년", 28, "98765432")
    
    print(youth.get_age())  # 28
    
    youth.set_age(25)
    print(youth.get_age())  # 25
    
    """ 
    현재 __age 변수에 바로 접근 불가능함.
    can_drink, get_age, set_age 메소드를 통해서 접근 가능함.
    
    외부에서 직접 접근이 불가능한 변수에 대해 접근할 수 있는 메소드를 만듦.
    """

    • __resident_id는 getter, setter 메소드를 안 만드는 이유
    → 주민등록번호는 함부로 읽거나 변경하면 안되는 민감한 정보

    • def authenticate 같은 경우에는 주민번호를 몰라도 본인 인증 가능함.
    → 변수를 숨겨도 꼭 getter/setter 메소드를 만들 필요가 없음.

     


    캡슐화 요약
    1. 클래스 밖에서 접근하지 못하게 할 변수, 메소드 정하기
    2. 변수나 메소드 이름 앞에 언더바 2개 붙이기
    (양 옆에 붙이면 __init__, __str__ 같이 특수 메소드처럼 그냥 접근/실행 가능)
    3. 변수에 간접 접근할 수 있게 메소드 추가하기
    (getter/setter or 다른 용도의 메소드)

     

     

    3) set_age 메소드 자체에서 __age에 음수가 들어가지 않도록 함.

       def set_age(self, value):
            """age 값을 설정하는 메소드"""
            if value < 0:
                print("나이는 0보다 작을 수 없습니다. 기본 값 0으로 나이를 설정하겠습니다.")
                self.__age = 0
            else:
                self.__age = value
                
                
        def __init__(self, name, age, resident_id):
            """이름, 나이, 주민등록번호"""
            self.name = name
            self.set_age(age)  # set_age 메서드가 실행되도록 함
            self.__resident_id = resident_id
    class Citizen:
        """주민 클래스"""
        drinking_age = 19  # 음주 가능 나이
        
        def __init__(self, name, age, resident_id):
            """이름, 나이, 주민등록번호"""
            self.name = name
            self.set_age(age)
            self.__resident_id = resident_id
            
        def __authenticate(self, ide_field):
            """본인이 맞는지 확인하는 메소드"""
            return self.__resident_id == id_field
            
        def can_drink(self):
            """음주 가능 나이인지 확인하는 메소드"""
            return self.__age >= Citizen.drinking_age
        
        def __str__(self):
            """주민 정보를 문자열로 리턴하는 메소드"""
            return self.name + "씨는 " + str(self.__age) + "살입니다!"
            
        def get_age(self):
            """age 값을 읽는 메소드"""
            return self.__age
        
        def set_age(self, value):
            """age 값을 설정하는 메소드"""
            if value < 0:
                print("나이는 0보다 작을 수 없습니다. 기본 값 0으로 나이를 설정하겠습니다.")
                self.__age = 0
            else:
                self.__age = value
    
    
    child = Citizen("어린이", -100, "12345678")  # 나이에  음수를 넣음
    # --> 나이는 0보다 작을 수 없습니다. 기본 값 0으로 나이를 설정하겠습니다.
    
    print(child.get_age())  # 기본값인 0으로 설정됨
    # --> 0
    
    child.set_age(-1000)  # set_age 메소드에 value값으로 음수를 넣음
    # --> 나이는 0보다 작을 수 없습니다. 기본 값 0으로 나이를 설정하겠습니다.
    
    print(child.get_age())  # 기본값인 0으로 설정됨
    # --> 0
    
    youth = Citizen("나청년", 21, "12323512")
    print(youth.get_age())  # 21

     

    네임 맹글링(name mangling)

    클래스 안에서 이름앞에 밑줄 2개(__)를 변수나 메소드는 네임 맹글링(name mangling)되어 새로운 이름을 갖게 됨.
    이 새로운 이름을 dir로 찾은 후접근 가능함.
    파이썬은 언어차원에서 캡슐화를 지원하지 않음.

    human = Citizen("Gildong", 48, "35233512")
    print(dir(human))
    
    '''
    ['_Citizen__age', '_Citizen__authenticate', '_Citizen__resident_id', 
    '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', 
    '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', 
    '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', 
    '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', 
    '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'can_drink', '
    drinking_age', 'get_age', 'name', 'set_age']
    '''
    human = Citizen("Gildong", 48, "35233512")
    print(dir(human))
    
    human._Citizen__resident_id = "0010101010"
    human._Citizen__age = "-50000"
    
    print(f'{human.name}씨의 나이는 {human._Citizen__resident_id}이지렁!')
    # Gildong씨의 나이는 0010101010이지렁!
    
    print(human)
    # Gildong씨는 -50000살입니다!

    dir로 찾은 네임 맹글링된 새 이름으로 접근 가능한 것을 확인할 수 있음.

    파이썬이 언어 자체에서 캡슐화를 지원하지 않는 것은 파이썬의 문화 때문임.


    캡슐화와 파이썬의 문화

    파이썬에서는 변수/메소드는 클래스 외부에서 직접 접근하지 말라는 표시로 언더바 1개를 씀.

    citizen 클래스의 언더바 2개를 언더바 1개로 바꾸기

    class Citizen:
        """주민 클래스"""
        drinking_age = 19  # 음주 가능 나이
        
        def __init__(self, name, age, resident_id):
            """이름, 나이, 주민등록번호"""
            self.name = name
            self.set_age(age)
            self._resident_id = resident_id
            
        def __authenticate(self, ide_field):
            """본인이 맞는지 확인하는 메소드"""
            return self._resident_id == id_field
            
        def can_drink(self):
            """음주 가능 나이인지 확인하는 메소드"""
            return self._age >= Citizen.drinking_age
        
        def __str__(self):
            """주민 정보를 문자열로 리턴하는 메소드"""
            return self.name + "씨는 " + str(self._age) + "살입니다!"
            
        def get_age(self):
            """age 값을 읽는 메소드"""
            return self._age
        
        def set_age(self, value):
            """age 값을 설정하는 메소드"""
            if value < 0:
                print("나이는 0보다 작을 수 없습니다. 기본 값 0으로 나이를 설정하겠습니다.")
                self._age = 0
            else:
                self._age = value
                
                
    youth = Citizen("나청년", 25, 1236215)
    print(youth._age)  # 25
    print(youth._resident_id)  # 1236215

    데코레이터를 사용한 캡슐화

    youth = Citizen("나청년", 25, 1236215)
    print(youth.get_age())  # 25
    youth.set_age(30)
    print(youth.get_age())  # 30

    이렇게 getter메소드와 setter메소드에 각각 접근하면 쓰기도 번거롭고 귀찮음.

     

    property라는 데코레이터 함수 사용

        ### _age변수의 "getter"메소드
        @property    
        def age(self):
            """age 값을 읽는 메소드"""
            print("나이를 리턴합니다.")
            return self._age
            
        ### _age변수의 "setter"메소드
        @age.setter
        def age(self, value):
            """age 값을 설정하는 메소드"""
            print("나이를 설정합니다.")
            if value < 0:
                print("나이는 0보다 작을 수 없습니다. 기본 값 0으로 나이를 설정하겠습니다.")
                self._age = 0
            else:
                self._age = value
                
                
    youth = Citizen("나청년", 25, 1236215)
    print(youth.age)  # 25
    ### ---> _age변수의 "getter" 메소드가 자동실행됨.
    
    youth.age = 30
    ### ---> _age변수의 "setter" 메소드가 자동실행됨.
    ### value 파라미터로 30이 자동전달됨.
    print(youth.age)  # 30

    변수의 값을 읽거나 설정하는 구문이 아예 다른 의미로 실행됨.
    캡슐화 전 사용하던 코드를 캡슐화 후 사용하지 않아도 됨.

    정리하면:
    • '.age'를 입력하므로서 '_age'변수getter와 setter변수에 모두 접근할 수 있음.

    • 변수를 직접 가져다쓰는 것보다 메소드를 가져다가 쓰는 것이 유지보수할 때 더 좋음.


    3. 상속

    상속

    두 클래스 사이에 부모-자식 관계를 설정하는 것
    A는 B다
    - 클래스 B(부모 클래스)가 클래스 A(자식 클래스)를 포함함.
    - 자식 클래스는 모든 변수와 메소드를 물려 받음.


    상속을 하려면 공통 클래스로만 이루어진 부모클래스를 하나 만들어야 함.
    → 이 공통 클래스로 부모를 의미하는 employee 클래스 생성

     

    직원이 배달원, 계산원, 청소원 등 여러명인데,
    현재 각 클래스마다 클래스 변수, 인스턴스 변수, 인스턴스 메소드가 중복되는 것이 많음.

     

    class Cashier:
    	"""계산대 직원 클래스"""
    	company_name = "코드잇 버거"  # 가게 이름
    	raise_percentage = 1.03  # 시급 인상률
    	burger_price = 4000  # 햄버거 가격
    
    	def __init__(self, name, wage, number_sold=0):
    		self.name = name
    		self.wage = wage
    		self.number_sold = number_sold  # 하루 판매량
    
    	def raise_pay(self):
    		"시급을 인상한다"""
    		self.wage *= self.raise_percentage
    
    	def take_order(self, money_received):
    		"주문과 돈을 받고 거스름돈을 리턴한다"""
    		if Cashier.burger_price > money_received:
    			print("돈이 충분하지 않습니다.")
    			return money_received
    		else:
    			self.number_sold += 1
    			change = money_received - Cashier.burger_price
    			return change
    
    	def __str__(self):
    		return Cashier.company_name + " 계산대 직원: " + self.name
    
    
    # 인스턴스 생성
    alba = Cashier("나계산", 8900, 0)
    
    alba.raise_pay()
    print(alba.wage)  # 9167.0
    
    print(alba.take_order(7000))  # 3000
    
    print(alba.take_order(3000))
    # 돈이 충분하지 않습니다.
    # 3000
    
    print(alba.burger_price)  # 4000
    
    print(alba.number_sold)  # 1
    
    print(alba)  # 코드잇 버거 계산대 직원: 나계산
    class DeliveryMan:
        """배달 직원 클래스"""
        company_name = "코드잇 버거"  # 가게 이름
        raise_percentage = 1.03  # 시급 인상률
    
        def __init__(self, name, wage, on_standby):
           self.name = name
           self.wage = wage
           self.on_standby = on_standby # 하루 판매량
    
        def raise_pay(self):
           """시급을 인상한다"""
           self.wage *= self.raise_percentage
    
        def deliver(self, address):
           """배달원이 대기 중이면 주어진 주소로 배달을 보내고 아니면 설명 메시지를 출력한다"""
           if self.on_standby:
              print(address + "로 배달 나갑니다!")
              self.on_standby = False
           else:
              print("이미 배달하러 나갔습니다!")
    
        def back(self):
           """배달원을 복귀 처리한다"""
           self.on_standby = True
    
        def __str__(self):
           return DeliveryMan.company_name + " 배달원: " + self.name
    
    
    # 인스턴스 생성
    baedal = DeliveryMan("나배달", 7000, True)
    
    baedal.raise_pay()  # 7210.0
    print(baedal.wage)
    # 7210.0
    
    baedal.deliver("서울시 여러분 여기로 51 1004호")
    # 서울시 여러분 여기로 51 1004호로 배달 나갑니다!
    
    baedal.deliver("서울시 여러분 여기로 51 1010호")
    # 이미 배달하러 나갔습니다!
    class Employee:
    	"""직원 클래스(부모 클래스)"""
    	company_name = "코드잇 버거"  # 가게 이름
    	raise_percentage = 1.03  # 시급 인상률
    
    	def __init__(self, name, wage):
    		self.name = name  # 이름
    		self.wage = wage  # 시급
    
    	def raise_pay(self):
    		"""시급을 인상하는 메소드"""
    		self.wage *= self.raise_percentage
    
    	def __str__(self):
    		"""직원 정보를 문자열로 리턴하는 메소드"""
    		return Employee.company_name + " 직원: " + self.name
    
    
    '''
    부모 Employee
    자식 Cashier DeliveryMan
    '''
    
    class Cashier(Employee):
    	pass
    
    # Cashier 클래스의 인스턴스 생성
    workingman = Cashier("나계산", 9200)
    workingman.raise_pay()  
    print(workingman.wage)  # 9476.0
    print(workingman)  # 코드잇 버거 직원: 나계산
    
    
    class DeliveryMan(Employee):
    	pass
    
    baedal = DeliveryMan("나배달", 8700)
    baedal.raise_pay()
    print(baedal.wage)  # 8961.0
    print(baedal)  # 코드잇 버거 직원: 나배달

    모든 Class는 자동으로 Builtins.object Class를 상속받음.

     


    오버라이딩

    자식클래스가 부모클래스에서 물려받은 내용을 수정함.
    Employee클래스에서 상속받은 내용을 각 자식 클래스에 맞게 수정해야 함.

    · 자식 클래스에서 물려받은 메소와 같은 이름의 메소드를 내용을 바꿔 정의
    → 똑같은 __init__ 메소드를 사용하는데 조금 다른 내용을 적어주면 됨.

    · 자식 클래스만의 고유한 메소드 및 변수도 생성 가능함.

    class Cashier(Employee):
    	# 클래스 변수 오버라이딩
    	raise_percentage = 1.05
    
    	# __init__, __str__ 메소드 오버라이딩
    	def __init__(self, name, wage, number_sold):
        		# number_sold 파라미터를 넣어서 메소드 재정의함
    		super().__init__(name, wage)
    		self.number_sold = number_sold
    
    	def __str__(self):
    		return Cashier.company_name + " 계산대 직원: " + self.name
    
    		'''
    		super()함수를 쓸 때는 
    		- 부모클래스의 메소드를 호출할 수 있음
    		- self 파라미터를 작성하지 않아도 됨.
    		'''
    
    
    # Cashier 클래스의 인스턴스 생성
    workingman = Cashier("나계산", 9200, 4)
    workingman.raise_pay()
    print(workingman.wage)  # 9476.0
    print(workingman)  # 코드잇 버거 직원: 나계산
    print(workingman.raise_percentage)  # 1.05 (오버라이딩한 변수값 출력)
    class Cashier(Employee):
    	# 클래스 변수 오버라이딩
    	raise_percentage = 1.5
    	burger_price = 4000
    
    	# __init__ 메소드 오버라이딩 (상속받은 메소드 수정)
    	def __init__(self, name, wage, number_sold=0):
    		super().__init__(name, wage)
    		self.number_sold = number_sold
    	
        # 기능추가 (Cashier 클래스에만 있는 부분)
    	def take_order(self, money_received):
    		"""주문과 돈을 받고 거스름돈을 리턴한다."""
    		if Cashier.burger_price > money_received:
    			print("돈이 충분하지 않습니다. 돈을 다시 계산해서 주세요!")
    			return money_received
    		else:
    			self.number_sold += 1
    			change = money_received - Cashier.burger_price
    			return change
    
    	# __str__ 메소드 오버라이딩 (상속받은 메소드 수정)
    	def __str__(self):
    		return Cashier.company_name + " 계산대 직원: " + self.name
    
    
    # Cashier 클래스의 인스턴스 생성
    workingman = Cashier("나계산", 8700, 50)
    workingman.raise_pay()
    
    print(workingman.wage)  # 13050.0
    print(workingman)  # 코드잇 버거 직원: 나계산
    print(workingman.raise_percentage)  # 1.5 (오버라이딩한 변수값 출력)
        def __init__(self, name, wage, on_standby): # --> 파라미터 다 써주기
            super().__init__(name, wage)  # ---> super() (괄호 쓰기!), self 파라미터 X
            self.on_standby = on_standby

    다중상속

    Python에서는 지원하지만 Java에서는 지원하지 않는 기능임.

    다중상속을 받은 클래스의 mro는 상속받은 순서대로(파라미터 입력 순서대로 상속받음.

    다중상속의 문제점 해결하기
    - 부모클래스끼리 같은 이름의 메소드로 지정하지 않기
    - 같은 이름의 메소드는 자식클래스에서 오버라이딩하기

    # 엔지니어 클래스
    class Engineer:
        def __init__(self, favorite_language):
            self.favorite_language = favorite_language
    
        def program(self):
            print("{}(으)로 프로그래밍합니다.".format(self.favorite_language))
    
    # 테니스 선수 클래스
    class TennisPlayer:
        def __init__(self, tennis_level):
            self.tennis_level = tennis_level
    
        def play_tennis(self):
            print("{} 반에서 테니스를 칩니다.".format(self.tennis_level))
    
    class EngineerTennisPlayer(Engineer, TennisPlayer):
        """엔지니어와 테니스 클래스를 모두 상속받는 클래스"""
        def __init__(self, favorite_language, tennis_level):
            # 다중상속 시 super함수 - 어떤 함수를 상속하나?
            Engineer.__init__(self, favorite_language)
            TennisPlayer.__init__(self, tennis_level)
    
    # 다중 상속을 받는 클래스의 인스턴스 생성
    human = EngineerTennisPlayer("Python", "햇")
    # 두 부모 클래스의 메소드들을 잘 물려받았는지 확인
    human.program()  # Python(으)로 프로그래밍합니다.
    human.play_tennis()  # 햇 반에서 테니스를 칩니다.
    
    # --> 두 클래스의 메소드를 모두 상속받은 것을 확인할 수 있다.
    class CDMan(DeliveryMan, Cashier):
    	def __init__(self, name, wage, on_standby, number_sold=0):
    		Employee.__init__(self, name, wage)
    		self.on_standby = on_standby
    		self.number_sold = number_sold
    
    human1 = CDMan("나사람", 8000, True, 50)
    human1.take_order(3000)
    
    print(human1)
    print(CDMan.mro())

    CDMan의 mro함수의 출력결과는 다음과 같음.

    [<class '__main__.CDMan'>, <class '__main__.DeliveryMan'>, <class '__main__.Cashier'>, <class '__main__.Employee'>, <class 'object'>]

     

    DeliveryMan의 우선순위가 높기 때문에 print(human1)을 하였을 경우

    출력값은 DeliveryMan 클래스의 메소드가 적용되어 나옴.

    → 파라미터 순서를 변경하면 상속의 우선순위가 변경됨.


    4. 다형성

    다형성

    하나의 변수가 서로 다른 클래스의 인스턴스가 되는 경우

    class Shape:
    	"""도형의 면적을 구하는 클래스"""
        def area(self):
            pass
    
    class Circle(Shape):
    	"""원 클래스"""
        def __init__(self, radius):
            self.radius = radius
        
        def area(self):
        	"""원의 면적 구하기"""
            return 3.14 * self.radius**2
    
    class Rectangle(Shape):
    	"""직사각형 클래스"""
        def __init__(self, length, width):
            self.length = length
            self.width = width
        
        def area(self):
            """직사각형의 면적 구하기"""
            return self.length * self.width
    
    # 사용 예시
    circle = Circle(5)  # 지름이 5인 원 인스턴스 생성
    rectangle = Rectangle(3, 4)  # 가로세로 길이가 각각 3, 4인 직사각형 인스턴스 생성
    
    print(circle.area())     # 원의 면적: 78.5
    print(rectangle.area())  # 직사각형의 면적: 12

    Circle과 Rectangle 클래스는 각각 Shape 클래스를 상속받고 있음.

    둘 다 Shape 클래스에 정의된 area 메서드오버라이딩(override)하여 각자의 방식으로 넓이를 계산함.

    → 동일한 메서드 area를 호출하더라도 각 객체는 자신의 클래스에 정의된 메서드를 실행하여 다르게 동작함.


    일반 상속

    class Rectangle:
        """직사각형 클래스"""
        def __init__(self, width, height):
            self.width = width
            self.height = height
    
        def area(self):
            """직사각형의 넓이를 리턴한다"""
            return self.width * self.height
    
        def perimeter(self):
            """직사각형의 둘레를 리턴한다"""
            return 2*self.width + 2*self.height
    
        def __str__(self):
            """직사각형의 정보를 문자열로 리턴한다"""
            return "밑변 {}, 높이{}인 직사각형".format(self.width, self.height)
    
    
    from math import pi
    
    class Circle:
        """원 클래스"""
        def __init__(self, radius):
            self.radius = radius
    
        def area(self):
            """원의 넓이를 리턴한다"""
            return pi * self.radius * self.radius
    
        def perimeter(self):
            """원의 둘레를 리턴한다"""
            return 2 * pi * self.radius
    
        def __str__(self):
            """원의 정보를 문자열로 리턴한다"""
            return "반지름 {}인 원".format(self.radius)
    
    class Cylinder:
        """원통 클래스"""
        def __init__(self, radius, height):
            self.radius = radius
            self.height = height
    
        def __str__(self):
            """원통의 정보를 문자열로 리턴하는 메소드"""
            return "밑면 반지름 {}, 높이 {}인 원기둥".format(self.radius, self.height)
    
    class Paint:
        """그림판 프로그램 클래스"""
        def __init__(self):
            self.shapes = []
            # shapes는 Rectangle 인스턴스 or Circle 인스턴스 상황마다 둘 다 가능
    
        def add_shape(self, shape):
            """그림판에 도형을 추가한다"""
            self.shapes.append(shape)
    
        def total_area_of_shapes(self):
            """그림판에 있는 모든 도형의 넓이의 합을 구한다"""
            return sum([shape.area() for shape in self.shapes])
    
        def total_perimeter_of_shapes(self):
            """그림판에 있는 모든 도형의 넓이의 합을 구한다"""
            return sum([shape.perimeter() for shape in self.shapes])
    
        def __str__(self):
            """그림판에 있는 각 도형들의 정보를 출력한다"""
            res_str = "그림판 안에 있는 도형들:\n\n"
            for shape in self.shapes:
                res_str += str(shape) + "\n"
            return res_str
    
    
    cylinder = Cylinder(7, 4)
    rectangle = Rectangle(3, 7)
    circle = Circle(4)
    
    paint_program = Paint()
    paint_program.add_shape(cylinder)
    paint_program.add_shape(rectangle)
    paint_program.add_shape(circle)
    
    print(paint_program.total_area_of_shapes())
    print(paint_program.total_perimeter_of_shapes())

    현재 Cylinder에는 area, perimeter 메소드가 없어서 
    인스턴스 생성후 메소드를 실행하면 에러가 남.

     

    # Rectangle과 Circle의 상위 클래스 생성
    class Shape:
        """도형 클래스 (추상 클래스)"""
        def area(self):
            """도형의 넓이를 리턴한다: 자식 클래스가 오버라이딩함"""
            pass
    
    """
    Rectangle, Circle 클래스에 Shape를 상속받도록 함.
    왜냐면 Shape의 인스턴스인지 확인하는 구문을 추가하려고.
    """
    
    class Rectangle(Shape):
        """직사각형 클래스"""
    
    class Circle(Shape):
        """원 클래스"""
                
    '''
    class Paint에 add_shape 메소드를 변경함
    추상클래스 Shape를 상속받는 인스턴스인지 isinstance함수로 확인
    → else문으로 cylinder 같은 도형이 걸러짐
    '''
    
    class Paint:
    	def add_shape(self, shape):
            """그림판에 도형을 추가한다"""
            # shape으로 넘겨받는 도형이 Shape클래스의 인스턴스인지 확인
            if isinstance(shape, Shape):
                self.shapes.append(shape)
            else:
                print("넓이, 둘레를 구하는 메소드가 없는 도형은 추가할 수 없습니다.")

    일반 상속의 문제점

    정삼각형 클래스는 Shape 클래스를 상속받기는 하지만, area와 perimeter 메소드를 오버라이딩 하지는 않음.

    class Shape:
        """도형 클래스 (추상 클래스)"""
        def area(self):
            """도형의 넓이를 리턴한다: 자식 클래스가 오버라이딩함"""
            pass
    
        def perimeter(self):
            """도형의 둘레를 리턴한다: 자식 클래스가 오버라이딩함"""
            pass
    class EquilateralTriangle(Shape):
        """정삼각형 클래스"""
        def __init__(self, side):
            self.side = side
    # 정삼각형 인스턴스 생성
    triangle = EquilateralTriangle(4)
    paint_program = Paint()
    paint_program.add_shape(triangle)
    
    print(paint_program.total_area_of_shapes())  # 에러
    print(paint_program.total_perimeter_of_shapes())  # 에러

    정삼각형 클래스는 Shape 클래스를 상속받기는 하지만, area와 perimeter 메소드를 오버라이딩 하지는 않음.
    그래도 if isinstance(shape, Shape) 구문에서 true가 나와서 shape 클래스에 추가될 수 있음.
    정삼각형 클래스에 area, perimeter 메소드가 없어도 그림판에 추가될 수 있다는 것임.

    그림판에 추가될 도형들은 Shape 클래스를 상속받아야할 뿐만 아니라,
    area,perimeter 메소드도 오버라이딩하게 만들어야 함.


    추상클래스

    여러 클래스들의 공통점을 추상화해서 모아놓은 클래스
    특정 클래스를 추상클래스로 정의한 후 자식 클래스들이 상속받는 메소드를 오버라이딩하도록 강제할 수 있음.

    대문자 ABC를 상속받고, 추상메소드1개 이상 있는 클래스 (일반 메소드도 추가 가능함.)

    추상클래스로는 인스턴스를 만들 수 없음.

     

    1. ABC 모듈 불러오기

    from abc import ABC, abstractmethod

    class 이름(ABC):

     

    2. 추상메소드 적용하기
    자식 클래스가 반드시 오버라이딩해야되는 메소드
    추상메소드가 최소 1개 이상 있어야 추상클래스임.
    @abstractmethod로 표시

     

    from abc import ABC, abstractmethod  # ABC 모듈 임포트
    
    class Shape(ABC):  # ABC 클래스를 상속받도록 함
        """도형 클래스 (추상 클래스)"""
        @abstractmethod  # 추상메소드임을 표시 (1개 이상)
        def area(self):
            """도형의 넓이를 리턴한다: 자식 클래스가 오버라이딩함"""
            pass
        
        @abstractmethod
        def perimeter(self):
            """도형의 둘레를 리턴한다: 자식 클래스가 오버라이딩함"""
            pass
            
        def larger_than(self, shape):
            """해당 인스턴스의 넓이가 파라미터 인스턴스의 넓이보다 큰지를 불린으로 나타낸다"""
            return self.area() > shape.area()
            # 추상클래스에도 일반 메소드가 추가 가능하다
    # 추상클래스 인스턴스 생성
    shape = Shape()
    
    '''
    TypeError: Can't instantiate abstract class EquilateralTriangle 
    with abstract methods area, perimeter
    '''

     

    정삼각형 클래스 추가

    class EquilateralTriangle(Shape):
        """정삼각형 클래스"""
        def __init__(self, side):
            self.side = side
    
        def area(self):
            """정삼각형의 넓이를 리턴한다"""
            return sqrt(3) * self.side * self.side / 4
    
        def perimeter(self):
            """정삼각형의 둘레를 리턴한다"""
            return 3 * self.side

    정삼각형 클래스 →  Shape 클래스 (부모) → ABC 클래스 (부모)

    정삼각형 클래스는 

    1) ABC를 상속 받고 있고,

    2) 추상메소드를 하나 갖고 있음 (----> 오버라이딩하면 일반클래스로 전환)
    → 추상클래스인 상태

    물려받은 추상메소드를 오버라이딩하면 일반클래스로 만들 수 있음.
    오버라이딩 방향을 제시하기 위해 추상메소드는 타입힌팅해주는 것이 좋음.

    def add_shape(self, shape: Shape):
    '''
    타입힌팅 도입
    shape: Shape
    → add_shape 메소드에 Shape 타입을 가지는 인스턴스가 shape 파라미터로 들어와야 한다
    '''

     

    특정 변수를 갖도록 강제할 수 있음. 

    """
    Shape 클래스에서 
    @property(getter메소드) & @abstractmethod(추상메소드)
    """
    
        @property
        @abstractmethod
        def x(self):
            """도형의 x 좌표 getter 메소드"""
            pass
    
        @property
        @abstractmethod
        def y(self):
            """도형의 y 좌표 getter 메소드"""
            pass
    # getter와 setter메소드를 갖도록 강제하여 오버라이딩하지 않으면 오류남
    
    @property
        def x(self):
            """_x getter 메소드"""
            return self._x
    
    @x.setter
        def x(self, value):
            """_x setter 메소드"""
            self._x = value

    추상클래스 다중상속

    from abc import ABC, abstractmethod
    
    # 추상클래스 Animal 정의
    class Animal(ABC):
        @abstractmethod
        def sound(self):
            """동물별로 다른 울음소리"""
            pass
        
    # Animal을 상속받는 Bird 클래스 정의
    class Bird(Animal):
        """새의 울음소리리"""
        def sound(self):
            return "Tweet"
            
    # Animal을 상속받는 Mammal 클래스 정의
    class Mammal(Animal):
        """포유류의 울음소리"""
        def sound(self):
            return "Growl"
            
    # Bird와 Mammal을 다중 상속받는 클래스 정의
    class Bat(Mammal, Bird):
        """박쥐의 울음소리"""
        def __str__(self):
            return "박쥐는 새처럼 날아다니지만 포유류입니다!\n박쥐의 울음소리는 {}입니다.".format(self.sound())
    
    
    # 박쥐 클래스의 인스턴스 생성
    bat = Bat()
    print(bat)
    
    
    """
    박쥐는 새처럼 날아다니지만 포유류입니다!
    박쥐의 울음소리는 Growl입니다.
    """

    추상 클래스 다중 상속은 일반적으로 많이 사용함.
    다중 상속받는 부모 추상 클래스들이 추상 메소드로만 이뤄져 있으면 아무 문제 없이 다중 상속받을 수 있음.
    다중 상속받는 부모 추상 클래스들 간에 이름이 겹치는 일반 메소드가 있으면 일반 클래스를 다중 상속받을 때와 동일한 문제가 생길 수 있음.


    함수/메소드 다형성

    보통은 클래스의 다형성이지만 함수의 다형성 존재함.

    • 옵셔널 파라미터
    • 파라미터 이름 명시
    • 개수가 확정되지 않은 파라미터

     

    1. 옵셔널(optional) 파라미터

    기본값을 미리 지정해준 파라미터
    파라미터 중 가장 뒤에 정의해야됨.

    None = 아무값도 없다
    함수 호출 시 어떤 파라미터에 값을 전달하지 않은 경우

    def new_print(value_1, value_2=None, value_3=None):
        if value_3 is None:
            if value_2 is None:
                print(value_1)
            else:
                print("{} {}".format(value_1, value_2))
                
        else:
            print("{} {} {}".format(value_1, value_2, value_3))
            
    
    new_print("this")  # this
    new_print(0.1245)  # 0.1245
    new_print("this", "that")  # this that
    new_print("this", 'that', 200)  # this that 200

     

    위 코드에서 옵셔널 파라미터를 변경해보면 다음과 같다.

    def new_print(value_1=None, value_2, value_3=None):
        if value_3 is None:
    
    # SyntaxError: non-default argument follows default argument
    def new_print(value_1=None, value_2=None, value_3=None):
        if value_3 is None:
    
    """
    this
    0.1245
    this that
    this that 200
    """

    옵셔널 파라미터를 쓸 때는 맨 뒤에 몰아서 써야함을 알 수 있음.

     

    2. 파라미터 이름 명시

    함수를 호출할 때 파라미터 이름 표시

    def printing(first_name, last_name, email=""):
    	"""성, 이름, 이메일(옵셔널)을 순서대로 출력하는 함수"""
        print("{}{} {}".format(last_name, first_name, email))
    
    	# email은 지정하지 않을 경우 빈문자열로 출력됨 (안 나옴)
        
        
    # 파라미터 순서대로 입력
    printing("졸려", "아", "zolzol@website.com")
    # 결과 : 아졸려 zolzol@website.com
    
    # 파라미터 이름 명시
    printing(first_name="졸려", last_name="아", email="zolzol@website.com")
    # 결과 : 아졸려 zolzol@website.com
    
    # 파라미터 이름 명시 (순서 랜덤)
    printing(email="zolzol@website.com", first_name="졸려", last_name="아")
    # 결과 : 아졸려 zolzol@website.com
    
    # 옵셔널 파라미터 제외하고 이름 명시
    printing(last_name="아", first_name="졸려")
    # 결과 : 아졸려

     

    3. 개수가 확정되지 않은 파라미터

    파라미터에 *가 붙은 경우

    def printing(message, *numbers):
        print(message)
        return sum(numbers)
        
    print(printing("test1", 2,5,9))
    print(printing("test2", 1, 6, 2, 9, 13))
    print(printing("test3", 4, 3, 7, 10))
    print(printing("test4", 4))
    print(printing("test5", 9, 2))
    
    '''
    test1
    16
    test2
    31
    test3
    24
    test4
    4
    test5
    11
    '''
    def printing(*numbers, message):
        print(message)
        return sum(numbers)
        
    print(printing("test1", 2,5,9))
    print(printing("test2", 1, 6, 2, 9, 13))
    print(printing("test3", 4, 3, 7, 10))
    print(printing("test4", 4))
    print(printing("test5", 9, 2))
    
    '''
    TypeError: printing() missing 1 required keyword-only argument: 'message'
    '''
    def printing(*numbers, message):
        print(message)
        return sum(numbers)
        
    print(printing(2,5,9, "test1"))
    print(printing(1, 6, 2, 9, 13, "test2"))
    print(printing(4, 3, 7, 10, "test3"))
    print(printing(4, "test4"))
    print(printing(9, 2, "test5"))
    
    '''
    TypeError: printing() missing 1 required keyword-only argument: 'message'
    '''
    def printing(*numbers, message):
        print(message)
        return sum(numbers)
        
    print(printing(2,5,9, message="test1"))
    print(printing(1, 6, 2, 9, 13, message="test2"))
    print(printing(4, 3, 7, 10, message="test3"))
    print(printing(4, message="test4"))
    print(printing(9, 2, message="test5"))
    
    '''
    test1
    16
    test2
    31
    test3
    24
    test4
    4
    test5
    11
    '''
    def printing(*numbers, message):
        print(message)
        return sum(numbers)
        
    print(printing(*numbers=[2,5,9], message="test1"))
    print(printing(*numbers=[1, 6, 2, 9, 13], message="test2"))
    print(printing(*numbers=[4, 3, 7, 10], message="test3"))
    print(printing(*numbers=[4], message="test4"))
    print(printing(*numbers=[9, 2], message="test5"))
    
    '''
    SyntaxError: invalid syntax
    *numbers는 가변인자를 받아옴.
    '''

    주행 시뮬레이터 만들기

    다른 클래스의 메소드 불러오는 방법

    class A:
        def method(self):
            print("Method in class A")
    
    class B:
        def method(self):
            print("Method in class B")
    
    class C:
        def method(self):
            print("Method in class C")
    
    class D:
        def another_method(self):
            a = A()
            b = B()
            c = C()
    
            a.method()
            b.method()
            c.method()
    
    
    # class D의 인스턴스 생성 후 another_method 실행
    d = D()
    d.another_method()
    
    
    '''
    Method in class A
    Method in class B
    Method in class C
    '''

    실행할 클래스 안에서 다른 클래스에 접근할 수 있는 인스턴스를 생성하여 메소드를 사용하면 됨.

     

    DrivingSimulator 코드 살펴보기

    class DrivingSimulator:
        def __init__(self):
            """교통 수단 인스턴스들을 담을 리스트를 변수로 갖는다"""
            self.driving_simulator = []
    
        def add_vehicle(self, new_vehicle):
            """교통 수단 인스턴스들만 시뮬레이터에 추가될 수 있게 한다"""
            if isinstance(new_vehicle, Vehicle):
                self.driving_simulator.append(new_vehicle)
            else:
                print("{}은 교통 수단이 아니기 때문에 추가할 수 없습니다.".format(new_vehicle))
    
        def start_all_vehicles(self):
            """모든 교통 수단을 주행 시작시킨다"""
            print("모든 교통 수단을 주행 시작시킵니다!\n")
            for vehicle in self.driving_simulator:
                vehicle.start()
    
        def stop_all_vehicles(self):
            """모든 교통 수단을 주행 정지시킨다"""
            print("모든 교통 수단을 주행 정지시킵니다!\n")
            for vehicle in self.driving_simulator:
                vehicle.stop()
    
        def __str__(self):
            """갖고 있는 교통 수단들의 현재 속도를 문자열로 리턴한다"""
            result = "" 
            for vehicle in self.driving_simulator:  
                result += str(vehicle) + "\n"
            return result

     

    __str__ 메소드 관련

        def __str__(self):
            """갖고 있는 교통 수단들의 현재 속도를 문자열로 리턴한다"""
            result = ""
            
            """
            self.driving_simulator이라는 리스트 안 원소들은
            Vehicle 클래스의 하위 클래스(Bicycle, NormalCar, SportsCar)의 인스턴스임.
            문자열로 출력하기 위해서는 str()로 형변환 해주어야 함.
            """
            
            for vehicle in self.driving_simulator:
                # print(type(vehicle)) ---> <class '__main__.Bicycle'> (클래스 Bicycle 사용중)
                result += str(vehicle) + "\n"
                # print(type(result))  ---> str로 형변환해서 str로 바뀜.
            return result
            
    """
    __str__메소드는 반드시 문자열을 출력해야 함.
    → 마지막에 'return 문자열'
    """
    # 클래스 정의
    class Bicycle:
        pass
    
    # 인스턴스 생성
    bike1 = Bicycle()
    bike2 = Bicycle()
    
    
    print(Bicycle)  # <class '__main__.Bicycle'> → __main__ 모듈에서 Bicycle 클래스를 사용하고 있음
    print(type(Bicycle))  # <class 'type'>
    
    print(bike1)  # <__main__.Bicycle object at 0x000001F12D524910>
    print(type(bike1))  # <class '__main__.Bicycle'>
    
    print(bike2)  # <class '__main__.Bicycle'>
    print(type(bike2))  # <class '__main__.Bicycle'>

     

    문자열 출력 방법 비교

    '''
    001-010
    011-020
    .
    .
    .
    281-290
    291-300
    '''
    
    # 1부터 291까지 10씩 반복
    
    # format 함수 활용
    for i in range(1, 292, 10):
        print("{}-{}".format(str(i).zfill(3), str(i+9).zfill(3)))
    
    # f-string, str()로 형변환
    for i in range(1, 292, 10):
        print(f"{str(i).zfill(3)}-{str(i+9).zfill(3)}")

    파이썬 EAFP 코딩 스타일과 다형성

    파이썬의 코딩 스타일

    LBYL EAFP
    Look Befor You Leap
    뛰기 전에 살펴보라!

    어떤 작업을 수행하기 에 그 작업을 수행해도 되는지 확인
    Easier to Ask for Forgiveness than Permission
    허락보다 용서가 쉽다!
    일단 먼저 빨리 실행하고, 문제가 생기면 처리한다
    예상치 못한 예외 상황을 방지할 수 있음.
    추가적인 코드가 필요할 수 있어 코드가 더 길어질 수 있음.
    코드가 간결하고 직관적이며, 예외 상황을 다루기 쉬움.
    예외 처리는 상대적으로 오버헤드가 있을 수 있음.

     

    LBYL 예시

    import os
    
    file_path = 'Myfile.txt'
    
    if os.path.exists(file_path):
        with open(file_path, 'r') as file:
            content = file.read()  # 파일 전체를 문자열로
            print(content)
    
    else:
        print(f'파일 {file_path}가 존재하지 않습니다.')

    os.path.exists() 함수를 사용하여 파일이 존재하는지 먼저 확인한 후, 파일이 존재할 때에만 읽기 작업을 수행함.

     

    EAFP 예시

    import os
    
    file_path = 'Myfile.txt'
    
    try:
        with open(file_path, 'r') as file:
            content = file.read()
            print(content)
    except FileNotFoundError:
        print(f'파일 {file_path}가 존재하지 않습니다.')

     

    우선 파일을 열어보고(try 블록), 만약 파일이 없다면(FileNotFoundError) 예외를 처리함.

    파이썬 커뮤니티에서 주로 선호하는 방식이 EAFP 스타일임.

     

        def add_shape(self, shape: Shape):
            """그림판에 도형 인스턴스 shape을 추가한다.
            단 shape은  추상클래스 Shape의 인스턴스여야 한다."""
            # shape으로 넘겨받는 도형이 Shape클래스의 인스턴스인지 확인
            self.shapes.append(shape)
    
        def total_area_of_shapes(self):
            """그림판에 있는 모든 도형의 넓이의 합을 구한다"""
            total_area = 0
    
            for shape in self.shapes:
                try:
                    total_area += shape.area()
                except (AttributeError, TypeError):
                    print("그림판에 area 메소드가 없거나 "
                          "잘못 정의되어 있는 인스턴스 {}가 있습니다.".format(shape))
    
            return total_area
    
        def total_perimeter_of_shapes(self):
            """그림판에 있는 모든 도형의 넓이의 합을 구한다"""
            total_perimeter = 0
    
            for shape in self.shapes:
                try:
                    total_perimeter += shape.perimeter()
                except (AttributeError, TypeError):
                    print("그림판에 area 메소드가 없거나 "
                          "잘못 정의되어 있는 인스턴스 {}가 있습니다.".format(shape))
    
            return total_perimeter
    ### 둘레와 넓이를 구하는 부분에서는 예외처리로 빠짐.
    그림판에 area 메소드가 없거나 잘못 정의되어 있는 인스턴스 밑면 반지름 4, 높이 3인 원기둥가 있습니다.
    66.16259677446666
    그림판에 area 메소드가 없거나 잘못 정의되어 있는 인스턴스 밑면 반지름 4, 높이 3인 원기둥가 있습니다.
    48.132741228718345
    그림판 안에 있는 도형들:
    
    밑면 반지름 4, 높이 3인 원기둥  ### 원기둥이 일단 인스턴스로 추가됨.
    <__main__.EquilateralTriangle object at 0x000001DD6CCFDF50>
    반지름 4인 원
    밑변 3, 높이4인 직사각형

     

    728x90