상속이란?
클래스를 정의할 때 이미 구현된 클래스를 상속 받아서 속성이나 기능이 확장되는 클래스를 구현하는 것!
-기존의 클래스가 가지고 있는 메서드와 필드를 그대로 물려받는 새로운 클래스를 만드는 것.
-새로운 하위 클래스에서는 필드, 메서드의 추가 및 확장이 가능하다.
-공통 내용은 상위 클래스에 두고 상속 받음으로써 효율적인 코딩이 가능하다.
-> 부모(슈퍼)클래스와 자식(서브)클래스
-> 자식 클래스를 선언할 때는 class 자식클래스(부모 클래스) 형식으로
-> super(): 자식 클래스에서 부모 클래스의 값 사용할 때 쓰는 키워드
<부모 클래스에 생성자가 있는 경우 코드 예시>
#상속
#부모 클래스가 생성자가 없을 때
class Animal: #부모 클래스
def speak(self): #클래스 함수
print("동물이 소리를 냅니다.")
def move(self):
print("동물이 움직입니다.")
#자식 클래스
class Cat(Animal):
def meow(self):
print("야옹!")
cat = Cat() #생성자가 없으므로 그냥 이렇게 생성 가능
cat.speak()
cat.move() #자식클래스 인스턴스가 부모 클래스의 메서드 사용 가능
cat.meow()
<실행 결과>
동물이 소리를 냅니다.
동물이 움직입니다.
야옹!
-> 생성자가 없는 부모 클래스를 상속 받을 때 자식 클래스도 별도의 생성자를 구현하지 않아도 부모 클래스의 속성과 메서드를 바로 사용할 수 있다.
<부모 클래스에 생성자가 있는 경우 코드 예시>
class Animal: #부모 클래스
def __init__(self, name): #생성자가 있는 부모 클래스
self.name = name
def speak(self): #클래스 함수
print(f"{self.name}가 소리를 냅니다.")
def move(self):
print(f"{self.name}가 움직입니다.")
#자식 클래스
class Cat(Animal): #'야옹'은 기본 값!
def __init__(self, a, sound = "야옹"):
#부모 클래스와 같이 생성자에 대한 처리를 해주어야 한다!
#변수 추가 가능하지만 부모 클래스에서의 변수 먼저 입력하는 것이 관례!
super().__init__(a)
#자식 클래스에서 부모 클래스의 생성자 명시적으로 호출! self는 필요 없음.
#부모 클래스에 있는 name 생성자를 '호출'하는 것!
#(꼭 name이라 안 하고, 여기에서는 a라고 썼다. 관례적으로는 부모 클래스에서와 동일하게 변수 이름.)
self.sound = sound
#자식 클래스의 생성자 매개변수 꼭 부모 클래스와 같을 필요 없음
#사용 시 self.변수명으로 사용
def meow(self): #자식 클래스 함수
print(f"{self.name}가 {self.sound} 짖습니다.")
#여기는 self.a 하면 안 됨 부모 클래스에서 호출하는 거니까!
cat = Cat("Lemon") #sound 값이 안 올 때는 기본 값 사용!
cat.speak()
cat.move() #부모 클래스 함수 호출 가능
cat.meow()
<실행 결과>
Lemon가 소리를 냅니다.
Lemon가 움직입니다.
Lemon가 야옹 짖습니다.
파이썬은 하나의 자식 클래스가 여러 부모 클래스의 상속을 받을 수 있다.
<다중 상속 활용 예시 코드>
#다중 상속
#정보 은닉은 꼭 안 써도 됨! 게터 세터도 써도 되고 안 써도 된다
class Engine: #부모 클래스1
def __init__(self,horsepower):
self.horsepower = horsepower
self.count = 100 #클래스 변수
class Wheels: #부모 클래스2
def __init__(self, count):
self.count = count
#자식 클래스
class Car(Engine, Wheels): #상속 받는 부모 클래스 모두 써주기
def __init__(self, horsepower, wheel_count):
Engine.__init__(self, horsepower)
Wheels.__init__(self, wheel_count)
#부모클래스명__innit__()으로 명시적 호출
#여기는 self까지 써줘야!
def info(self):
print(f"이 자동차는 {self.horsepower} 마력과 {self.count}개의 바퀴를 가지고 있다.")
def test(self):
print(f"어디? {self.count}")
######---------------------동일한 값이 들어갔을 때 오류가 나타나며 앞에 있는 엔진의 카운트가 들어가야 하는데 왜 오류가 안 날까?
car = Car(150, 4)
car.info()
car.test()
print(Car.mro()) #mro를 사용하여 클래스의 상속 순서를 리스트에 반환
<실행 결과>
이 자동차는 150 마력과 4개의 바퀴를 가지고 있다.
어디? 4
[<class 'main.Car'>, <class 'main.Engine'>, <class 'main.Wheels'>, <class 'object'>]
다형성이란?
동일한 이름의 메서드가 객체의 타입에 따라 다르게 동작하는 것을 말한다.
-> 주로 상속 관계에서 메서드 동작을 변경
=> 다형성을 실현하는 기법 중 하나로 오버라이딩이 존재한다.
-메서드의 재정의 (Method Overriding)
-상속된 메서드의 내용이 자식 클래스에 맞지 않는 경우, 자식 클래스에서 동일한 메서드를 재정의한다.
=> 즉, 자식 클래스는 부모 클래스의 기본 동작을 변경하거나 확장할 수 있다.
-super().메서드명을 사용하여 자식 클래스에서 부모 클래스의 메서드를 호출할 수 있다.
-오버라이딩한 메서드는 부모 클래스 메서드의 매개변수 개수와 동일하게 사용하는 것을 권장한다.
<오버라이딩 활용 코드 예시>
#오버라이딩
class Parent:
def greet(self):
print("안녕하세요. 부모 클래스")
class Child(Parent):
def greet(self): #같은 이름, 같은 매개변수 개수
super().greet() #부모클래스에 있는 greet() 호출
print("안녕하세요. 자식 클래스") #부모 클래스 메서드에 출력문 하나 더 추가 -> 기능 확장!
p = Parent()
c = Child()
p.greet()
print()
c.greet()
<실행 결과>
안녕하세요. 부모 클래스 ->p.greet()의 결과
안녕하세요. 부모 클래스
안녕하세요. 자식 클래스 ->c.greet()의 결과
주어진 부모 클래스 Product를 바탕으로 조건을 만족하는 자식 클래스 Electronic 클래스와 Food 클래스를 설계해 보세요.
<Electronic 클래스의 조건>
추가변수: warranty_period (보증 기간, 기본값 12개월)
새로운 메서드: extend_warranty(months)를 작성하여 보증 기간을 연장
오버라이딩: display_info 메서드를 오버라이딩하여 보증 기간 정보를 포함한 상품 정보를 출력
<Food 클래스의 조건>
추가변수: expiration_date (유통 기한 - 날짜 형태는 "YYYY-MM-DD")
새로운 메서드: is_expired(current_date)를 작성하여 유통 기한이 지났는지 여부를 확인하고 결과를 출력
오버라이딩: display_info 메서드를 오버라이딩하여 유통기한 정보를 포함한 상품 정보를 출력
#실습. 상속과 오버라이딩
class Product: #부모 클래스
def __init__(self, name, price, quantity): #생성자
self.name = name
self.price = price
self.quantity = quantity
# 재고 업데이트 메서드
def update_quantity(self, amount):
self.quantity += amount
print(f"{self.name} 재고가 {amount}만큼 {'증가' if amount > 0 else '감소'}했습니다. 현재 재고: {self.quantity}")
# 상품 정보 출력 메서드
def display_info(self):
print(f"상품명: {self.name}")
print(f"가격: {self.price}원")
print(f"재고: {self.quantity}개")
#자식 클래스
class Electronic(Product):
def __init__(self, name, price, quantity, warranty_period=12): #생성자, 보증기간 기본값 12개월
super().__init__(name, price, quantity) #부모 클래스의 생성자 호출
self.warranty_period = warranty_period
#보증기간 연장 메서드
def extend_warranty(self, months):
self.warranty_period += months
print(f"보증기간이 {months}개월 연장. 현재 보증기간{self.warranty_period}개월")
#오버라이딩
def display_info(self): #같은 메서드명, 같은 매개변수 개수
super().display_info() #부모 클래스 메서드 호출
print(f"보증기간: {self.warranty_period}개월") #보증기간 정보 출력 추가
class Food(Product):
def __init__(self, name, price, quantity, expiration_date):
super().__init__(name, price, quantity)
self.expiration_date = expiration_date
#유통기한 확인 메서드
def is_expired(self, current_date):
if current_date > self.expiration_date:
print(f"{self.name}은/는 유통기한이 지났습니다.")
else:
print(f"{self.name}은/는 유통기한이 지나지 않았습니다")
def display_info(self): #오버라이딩
super().display_info()
print(f"유통기한 : {self.expiration_date}")
tv = Electronic("삼성스마트TV", 1500000, 2, 24)
# tv.display_info()
# tv.extend_warranty(12)
# tv.display_info()
milk = Food("서울우유", 3000, 30, "2024-12-10")
milk.is_expired("2024-11-28")
milk.is_expired("2024-12-28")
milk.display_info()
<실행 결과>
서울우유은/는 유통기한이 지나지 않았습니다
서울우유은/는 유통기한이 지났습니다.
상품명: 서울우유
가격: 3000원
재고: 30개
유통기한 : 2024-12-10
추상화란?
복잡한 시스템의 세부 사항을 감추고, 필요한 부분만 노출함으로써 코드를 더 간단하고 명확하게 작성하는 것!
->복잡한 세부 구현 사항을 몰라도 제공된 기능을 사용할 수 있게!
=> abc (Abstract Base Class) 모듈을 사용해 추상화를 지원
추상 클래스란?
-하나 이상의 추상 메서드를 포함한 클래스
-구체적인 구현 없이 구조만 제공하며, 이를 기반으로 구체적인 동작을 구현하는 자식 클래스를 설계하는데 사용
-직접 인스턴스화할 수 없으며, 반드시 상속받아 사용해야 함.
-추상 클래스는 생성자, 일반 메서드와 추상 메서드를 모두 포함하는 것이 가능하다!
=>즉, 공통된 동작은 추상 클래스에서 정의하고, 구체적인 동작은 자식 클래스에서 구현한다!
추상 메서드란?
-선언만 되어있는 메서드!
-자식 클래스에서 반드시 구현해야 하는 메서드로 강제성을 지닌다.
-추상 메서드는 추상 클래스 내에서 정의되며, @abstractmethod 데코레이터를 사용한다.
<추상 클래스 활용 예시 코드>
#추상화
#추상클래스
from abc import ABC, abstractmethod
class PaymentSystem(ABC): #ABC를 꼭 상속받아야 추상화 가능!
#추상메서드
@abstractmethod
def authenticate(self):
pass #무조건 패스!!
@abstractmethod
def process_payment(self, amount):
pass
def payment_info(self, amount):
print(f"{amount}원 결제가 완료되었습니다.")
class kakaoPay(PaymentSystem): #자식 클래스, 추상 클래스 상속 받음
def authenticate(self):
print("카카오페이 인증완료되었습니다.")
def process_payment(self, amount):
print(f"카카오페이로 {amount}원을 결제합니다.")
pay = 50000
kakao = kakaoPay() # 자식 클래스의 인스턴스 선언
kakao.authenticate()
kakao.process_payment(pay)
kakao.payment_info(pay)
<실행 결과>
카카오페이 인증 완료 되었습니다.
카카오페이로 50000원을 결제합니다.
50000원 결제가 완료되었습니다.
-메서드의 self 개념 중 클래스를 인스턴스화 하지 않고 호출해서 사용하는 경우에는 self가 필요 없음
->이 경우에는 일반적으로 클래스 메서드나 정적 메서드를 활용한다!
-자기 자신의 클래스를 첫 번째 인자로 받는 메서드(cls 사용)
-클래스 속성에 접근하거나 클래스 상태를 변경할 수 있다.
-> 클래스 변수에 접근하거나 수정할 때
-> 클래스 또는 자식 클래스 간의 동작을 통합적으로 처리할 때
-@classmethod 데코레이터 사용
인스턴스 생성 후 사용하는 것이 아닌 클래스에서 호출!
<클래스 메서드 활용 코드 예시1 - 클래스 변수에 접근>
class Converter:
conversion_rate = 1.60934 #클래스 변수
@classmethod #클래스 메서드 데코레이터
def miles_to_kilometer(cls, mile): #자신의 클래스를 첫번째 인자로 받음
return mile * cls.conversion_rate #클래스 변수에 접근!
print(Converter.miles_to_kilometer(7))
#인스턴스 선언하면 클래스 메서드인지 구분 어려우므로 하지 x
<클래스 메서드 활용 코드 예시2 - 생성자가 아닌 클래스 메서드로 인스컨스 생성>
class Person:클랫
def __init__(self, name, age): #생성자
self.name = name
self.age = age
@classmethod
def from_birth_year(cls, name, birth_year):
age = 2024- birth_year
return cls(name, age) #
#생성자가 아닌 클래스 메서드를 통해서 객체 생성!
p1 = Person.from_birth_year("홍길동", 1990)
#print(p1) #튜플 형태로 출력
print(p1.name, p1.age)
<클래스 메서드 활용 코드 예시3 - 클래스 변수 변경>
#----------------클래스 변수를 사용하는 법
class Counter:
count = 0 #클래스 변수
@classmethod
def increment(cls):
cls.count += 1
@classmethod
def get_count(cls):
return cls.count
Counter.increment()
Counter.increment()
Counter.increment()
print(Counter.get_count()) #출력 결과 3.
<클래스 메서드 활용 코드 예시4 - 자식 클래스의 정보 유지>
#자식 클래스의 정보유지
class Animal: #부모 클래스
species = "동물" #부모 클래스 변수
@classmethod
def get_species(cls):
return cls.species
class Dog(Animal): #자식 클래스
species = "진돗개" #자식 클래스 변수
print(Animal.get_species()) #부모 클래스의 클래스 메서드
print(Dog.get_species()) #자식 클래스에서 부모 클래스의 클래스 메서드 호출
#서브 클래스에서 호출할 때 서브 클래스의 정보를 유지 시켜준다.
<실행 결과>
동물
진돗개
-cls나 self를 사용하지 않는다.
-클래스와 독립적인 작업 (e.g. 유틸리티 함수)을 정의할 때 유용하다.
-@staticmethod 데코레이터를 사용한다.
-정적 메서드는 일반 함수로 작성하는 것도 가능하지만, 함수의 목적을 클래스와 연관지어 코드의 효율성을 높일 수 있다.
=> 클래스로 묶어둔다는 장점!
<정적 메서드 활용 코드 예시>
# 정적 메서드
class MathUtils:
@staticmethod #정적 메서드 데코레이터
def add(a,b):
return a + b
@staticmethod
def minus(a,b):
return a - b
#어떤 기능을 다 모아두거나 굳이 인스턴스를 선언할 필요가 없을 땨
print(MathUtils.add(30, 40)) #결과 70
print(MathUtils.minus(10, 20)) #결과 -10
<클래스 메서드 예시 코드 추가>
#클래스메서드 추가
class ShapeUtils:
pi = 3.14159 #클래스 변수
@classmethod
def circle_area(cls, radius):
return cls.pi * radius**2 #클래스 변수에 접근
@classmethod
def desc(cls, radius):
area = cls.circle_area(radius)
return f"{radius}의 원주율은 {area}입니다."
print(ShapeUtils.circle_area(5))
print(ShapeUtils.desc(5))
<추상 클래스 ElectricityData 생성>
1. ElectricityData는 생성자를 통해 usage_data와 total_usage를 초기화해야 한다.
2. usage_data는 전역 사용량 데이터를 담은 리스트로, 각 항목은 날짜와 사용량을 포함한 딕셔너리 형태이다.
3. usage_data와 total_usage를 getter와 setter로 관리하여 캡슐화를 구현한다.
메서드 제작
1. 추상 메서드
calculate_total_usage(): 전력 사용량 데이터를 기반으로 총 사용량을 계산
get_usage_on_date(date): 특정 날짜의 전력 사용량을 반환
2. 일반 메서드
add_usage(date, usage): 새로운 날짜의 전력 사용량을 추가
remove_usage(date): 특정 날짜의 전력 사용량 데이터를 삭제
<자식 클래스 HomeElectricityData 생성>
1. 부모 클래스 ElectricityData를 상속받는다.
2. 부모 클래스의 추상 메서드인 calculate_total_usage()와 get_usage_on_date()를 구현한다.
3. 클래스 메서드를 사용하여 electricity_usage 리스트에서 특정 날짜 범위 내의 데이터를 필터링하는 기능을 추가한다.
4. 정적 메서드를 사용하여 전력 사용량 데이터에서 가장 높은 사용량을 찾는 기능을 추가한다.
<아래의 내용을 구현하세요>
1. 데이터를 입력하여 총 전력 사용량을 계산하고 특정 날짜의 사용량을 조회할 수 있어야 한다.
2. 데이터의 추가 및 삭제가 가능해햐 한다.
3. 정적 메서드를 통해 가장 높은 전력 사용량과 해당 날짜를 찾을 수 있다.
4. 클래스 메서드를 통해 특정 날짜 범위 내의 사용량을 출력할 수 있다.
<실습 코드>
# 실습. 클래스 종합 프로그래밍
from abc import ABC, abstractmethod
#전역 변수로 두기
electricity_usage = [
{"date": "2024-11-01", "usage": 12.5},
{"date": "2024-11-02", "usage": 15.3},
{"date": "2024-11-03", "usage": 10.8},
{"date": "2024-11-04", "usage": 14.2},
{"date": "2024-11-05", "usage": 13.6}
]
#추상클래스
class ElctricityData(ABC):
def __init__(self, usage_data, total_usage=0):
#생성자를 통해 usage_data와 total_usage를 초기화
self._usage_data = usage_data #접근 제한자, protected
self._total_usage = total_usage
#-------------------getter & setter-------------------
@property
def usage_data(self): #usage의 getter
return self._usage_data
@usage_data.setter
def usage_data(self, new_data): #usage의 setter
self._usage_data = new_data
@property
def total_usage(self): #total usage의 getter
return self._total_usage
@total_usage.setter #total usage의 setter
def total_usage(self, new_total):
self._total_usage = new_total
#---------------------------------------------------
#추상메서드
@abstractmethod
def calculate_total_usage(self):
pass
@abstractmethod
def get_usage_on_date(self, date):
pass
#일반메서드
def add_usage(self, date, usage): #새로운 날짜의 전력 사용량을 추가
self._usage_data.append({"date": date, "usage": usage})
def remove_usage(self, date): #특정 날짜의 전력 사용량을 삭제
self._usage_data = [i for i in self._usage_data if i["date"] != date ]
#--------------------------------------------------------
#자식클래스
class HomeElectricityData(ElctricityData):
#생성자 따로 없음
#추상메서드 구현
def calculate_total_usage(self):
#전력 사용량 데이터를 기반으로 총 사용량을 계산
#리스트 내포를 활용하여 딕셔너리 리스트의 usage의 총 합
self.total_usage = sum( i["usage"] for i in self.usage_data)
def get_usage_on_date(self, date):
특정 날짜의 전력 사용량을 반환
for i in self.usage_data:
if i["date"] == date:
return i["usage"]
#클래스 메서드
#electricity_usage 리스트에서 특정 날짜 범위 내의 데이터를 필터링
@classmethod
def filter_date(cls, usage_data, start_date, end_date):
filter_data = [ i for i in usage_data if start_date <= i["date"] <= end_date ]
return cls(filter_data)
#정적 메서드
#전력 사용량 데이터에서 가장 높은 사용량을 찾는 기능
@staticmethod
def max_usage(usage_data):
return max(usage_data, key=lambda x: x["usage"])
#여기서 max() 함수의 key 값은 무엇을 기준으로 max값을 구할 것인가에 대한 것! 주로 lambda 함수를 이용한다.
home = HomeElectricityData(electricity_usage) #인스턴스 선언
home.calculate_total_usage()
print("총 전력 사용량", home.total_usage)
print("특정날짜 ", home.get_usage_on_date("2024-11-05"))
home.add_usage("2024-11-29", 11.0)
home.remove_usage("2024-11-02")
print(home.usage_data) #11-02 데이터 제거되고 11-29 데이터 추가된 것 확인
result = HomeElectricityData.filter_date(electricity_usage, "2024-11-03", "2024-11-05")
print(result.usage_data) #이건 원본에서 11-03~11-05 필터링한 데이터
max_result = HomeElectricityData.max_usage(electricity_usage)
print(max_result)
<실행 결과>
총 전력 사용량 66.4
특정날짜 13.6
[{'date': '2024-11-01', 'usage': 12.5}, {'date': '2024-11-03', 'usage': 10.8}, {'date': '2024-11-04', 'usage': 14.2}, {'date': '2024-11-05', 'usage': 13.6}, {'date': '2024-11-29', 'usage': 11.0}]
[{'date': '2024-11-03', 'usage': 10.8}, {'date': '2024-11-04', 'usage': 14.2}, {'date': '2024-11-05', 'usage': 13.6}]
{'date': '2024-11-02', 'usage': 15.3}
<후기>
클래스 메서드의 경우 어떤 경우에 필요한 것인지 이해하기 어려웠다...
주의해서 복습해두도록 하자.