[파이썬 중급] 얕은복사/깊은복사, 클래스 상속, 생성자, 다중 상속/오버라이딩, 추상클래스, 예외 처리 외

서대철·2023년 7월 11일
0
<오늘의 학습내용>
얕은복사/깊은복사
클래스 상속
생성자
다중 상속
오버라이딩
추상 클래스
예외: 예외처리; try-except-else (+finally)
Exception 클래스

21_얕은복사(shallow copy)와 깊은복사(deep copy)

  • 얕은복사란, 객체 주소를 복사하는 것. 객체 자체의 복사는 아님.
    • 어제 공부한 레퍼런스 변수; 2개의 변수는 같은 메모리 주소를 가지게 됨
  • 깊은복사란, 객체 자체를 복사하여 또 하나의 객체가 생성.
    • 2개의 변수는 각각 다른 메모리 주소를 가지게 됨
# 얕은 복사(메모리 주소만 복사)
obj1 = class_name(x, y)
obj2 = obj1

# 깊은 복사
import copy

obj3 = class_name(x, y)
obj4 = copy.copy(obj3)    # 새로운 객체 4 생성

다양한 방법으로 리스트 객체를 복사하기


listA = [1, 2, 3, 4, 5]
listB = listA    # 얕은복사
listC = []   # for loop을 이용한 깊은복사를 수행할 리스트
listD = []   # extend()를 이용한 깊은복사를 수행할 리스트 
listE = []   # copy()를 이용한 깊은복사를 수행할 리스트 
listF = []  # slicing을 이용한 깊은복사를 수행할 리스트

print(id(listA))
print(id(listB))     # listA와 같은 주소 출력

for i in listA:
	listC.append(i)    # for loop을 이용한 깊은복사

listD.extend(listA)   # extend()를 이용한 깊은복사
listE = listA.copy()   # copy()를 이용한 깊은복사 
listF = listA[:]   # slicing을 이용한 깊은복사

print()
print(id(listA))
print(id(listC))   # listA와 다른 주소 출력 
print(id(listD))   # listA와 다른 주소 출력 
print(id(listE))   # listA와 다른 주소 출력 
print(id(listF))   # listA와 다른 주소 출력 

(개인적으로는 .copy()가 제일 단순한 것 같다)

예제 - 깊은복사를 통해 독립적인 리스트를 생성하여 원본과 비교하기

# original score list 생성
# compute average
# create deep copy
# compute average excluding min/max scores
# compare average between two lists

original = [8.7, 9.1, 8.9, 9.0, 7.9, 9.5, 8.8, 8.3]
original.sort()    # sort ascending

copy = original.copy()   # deep copy

copy.pop(0)    # remove first element
copy.pop()    # remove last element

print(f'original: {original}')
print(f'copy: {copy}')

originalSum = round(sum(original), 2)
originalAvg = round(originalSum / len(original), 2)
print(f'original sum: {originalSum}')
print(f'original average: {originalAvg}')

copySum = round(sum(copy), 2)
copyAvg = round(copySum / len(copy), 2)
print(f'copy sum: {copySum}')
print(f'copy average: {copyAvg}')

print('original average - copy average: %.2f' % (originalAvg - copyAvg))

22_클래스 상속

  • 다른 클래스의 기능을 내 클래스에 가져와서 사용하는 것.
# class inheritance

class NormalCar:    # ParentClass/SuperClass
	def __init__(self):
		# attribute initiation
	
	def drive(self):
		# function 1

	def back(self):
		# function 2

class TurboCar(NormalCar):    # NormalCar의 기능을 상속한 하위 subclass/child class
	def __init__(self):
		# attribute

	def turbo(self):
		# turbo function

myTurbo = TurboCar()

myTurbo.turbo()   # 기본 기능
myTurbo.drive()   # 상속된 기능
myTurbo.back()   # 상속된 기능

23-24_생성자(constructor)

  • 생성자는 객체 생성 시점에 자동으로 호출된다.
  • 생성자는 init method를 호출하여 객체를 초기화한다.
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def greet(self):
        print(f"Hello, my name is {self.name} and I'm {self.age} years old.")

person1 = Person("Alice", 25)
person1.greet()  # Output: Hello, my name is Alice and I'm 25 years old.

만약 subclass가 parent class를 상속할 때, parent class는 super() 함수를 통해 초기화할 수 있다.

class Parent:
    def __init__(self, parent_attribute):
        self.parent_attribute = parent_attribute

    def parent_method(self):
        print("This is a method from the parent class.")

class Person(Parent):
    def __init__(self, parent_attribute, name, age):
        super().__init__(parent_attribute)  # Initializing the parent class
        self.name = name
        self.age = age

    def greet(self):
        print(f"Hello, my name is {self.name} and I'm {self.age} years old.")

person1 = Person("Parent Attribute Value", "Alice", 25)
person1.greet()  # Output: Hello, my name is Alice and I'm 25 years old.
person1.parent_method()  # Output: This is a method from the parent class.

25_다중상속

  • 2개 이상의 클래스를 상속받는 것
  • 너무 많아지면 복잡해짐
class grandSonClass(class1, class2, classN)
	def __init__(self)
		pass

26_Overriding: 하위 클래스에서 상위 클래스의 메서드를 재정의하는 것.

class Vehicle:
    def __init__(self, brand):
        self.brand = brand

    def start_engine(self):
        print("Engine started")

class Car(Vehicle):
    def __init__(self, brand, model):
        super().__init__(brand)
        self.model = model

    def start_engine(self):   # this overrides the parent class function
        super().start_engine()
        print(f"{self.brand} {self.model}'s engine is now running.")

car = Car("Toyota", "Camry")
car.start_engine()
  • 상속과 오버라이딩은 코드 중복을 방지할 수 있다.

추상 클래스

  • 상위 클래스(meta class)에서 하위 클래스에 메소드 구현을 강요
from abc import ABCMeta, abstractmethod

class Shape(metaclass=ABCMeta):
    
		@abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

class Rectangle(Shape):
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length * self.width

    def perimeter(self):
        return 2 * (self.length + self.width)

# Attempting to create an instance of the abstract class will raise an error
# shape = Shape()  # Raises TypeError: Can't instantiate abstract class Shape with abstract methods area, perimeter

rectangle = Rectangle(5, 3)
print(rectangle.area())  # Output: 15
print(rectangle.perimeter())  # Output: 16
  • 추상클래스를 쓰는 이유?
    • 상위 클래스(=추상 클래스)는 동일한데, 하위 클래스들 간의 같은 기능도 차이가 있을 수 있음.
    • 예를 들어, 똑같은 ‘비행기’라는 추상 클래스라도, 하위 클래스가 ‘여객기’인지, ‘전투기’인지에 따라 ‘비행’ 기능의 차이가 있을 수 있음.

28_예외

  • 예상치 못한 문제로 프로그램 실행이 어려운 상태
  • 문법적인 문제(=에러)는 없으나, 실행 중 발생하는 예상하지 못한 문제
  • 예와 관련 클래스는 Exception 클래스를 상속한다.

    (출처: 제로베이스 데이터 취업스쿨 강의 자료)

29_예외 처리

  • 예외 발생 시 그 부분만 건너뛰고 나머지 기능을 실행하도록 함
  • try (기본 기능) / except (예외 발생 시) / else (예외가 발생하지 않은 경우) 활용
    • else는 옵션. Except 없이 사용할 수 없음.
try:
    x = int(input("Enter a number: "))
    result = 10 / x
except ValueError:
    print("Invalid input. Please enter a valid number.")
		continue
except ZeroDivisionError:
    print("Cannot divide by zero.")
		continue
else:
    print("Division successful!")
    print(f"The result is: {result}")

30_finally 구문

  • 예외발생과 상관없이 항상 실행되는 구문
  • 주로 네트워크와 연결되어 외부자원을 가지고 작업을 할 때, 자원해제를 시켜주는 등 마지막에 정리하는 코드를 final 구문과 함께 사용함.
try:
    x = int(input("Enter a number: "))
    result = 10 / x
except ValueError:
    print("Invalid input. Please enter a valid number.")
		continue
except ZeroDivisionError:
    print("Cannot divide by zero.")
		continue
else:
    print("Division successful!")
    print(f"The result is: {result}")
finally:
    print("Cleanup code here...")

31_exception 클래스

  • 에러의 특성에 대한 정보를 출력.
try:
    x = int(input("Enter a number: "))
    result = 10 / x
except Exception as e:
    print("An exception occurred:", type(e).__name__)

또는,

try:
    x = int(input("Enter a number: "))
    result = 10 / x
except Exception as e:
    print("An exception occurred: {e}")

raise 키워드를 사용해 예외를 발생시킬 수 있다.

def divCalculator(n1, n2):

	if n2 != 0:
		print(f'{n1} / {n2} = {n1 / n2}
	else:
		raise Exception('cannot divide by 0')

try:
	divCalculator(num1, num2)
except Exception as e:
	print(f'Exception: {e}')

실습: 메시지 길이에 따라 SMS 또는 MMS 발송하기

def sendSMS(message):

    if len(message) > 10:
        raise Exception('Too long. It will be sent as MMS', 1)
    else:
        print('Send SMS')

def sendMMS(message):

    if len(message) <= 10:
        raise Exception('Too short. It will be sent as SMS', 2)
    else:
        print('Send MMS')

message = input('Enter message here: ')
try:
    sendMMS(message)

except Exception as e:
    print(f'e: {e.args[0]}')
    print(f'e: {e.args[1]}')

    if e.args[1] == 1:
        sendMMS(message)
    elif e.args[1] == 2:
        sendSMS(message)

소감:

  • 몇 가지 생소한 개념들이 있어 기초문법 파트보다는 시간이 오래 걸렸다. 일단 개념 위주로 익혀 놓고 연습문제 파트에서 다시 다져야겠다.
  • 본격적으로 객체지향 프로그래밍이라는 틀에서 코딩을 생각하게 되니까 훨씬 더 직관적으로 이해가 된다. Low-level에서 작동하는 각각의 기능들과 문법들이 high-level에서 어떻게 사용되는지 머릿 속에 조금은 개념적으로 그려진다.

0개의 댓글