[1] python 심화 수업 복습 - 함수, 모듈과 패키지, 클래스와 객체지향
✅ 함수
● 개념
- 특정 작업을 수행하는 코드 블록
- 코드 재사용과 모듈화를 통해 유지보수성을 높임
● 매개변수와 인자
| 용어 | 설명 |
| 매개변수(Parameter) | 함수 정의 시 사용하는 변수 |
| 인자(Argument) | 함수 호출 시 전달하는 실제 값 |
● 매개변수 유형
- 필수 매개변수: 반드시 값을 전달해야 함
- 기본값 매개변수: 기본값 지정 가능 (def func(x=10):)
- 가변 매개변수:
- *args: 여러 개의 위치 인자를 튜플로 받음
- **kwargs: 키워드 인자를 딕셔너리로 받음
● lambda 함수 (익명 함수)
일반 함수와 람다 함수의 차이를 보면
# 일반 함수
def square(x):
return x ** 2
# 람다 함수
square_lambda = lambda x: x ** 2
print(square(5)) # 출력: 25
print(square_lambda(5)) # 출력: 25
- 한 줄로 표현 가능하고 간단한 계산/콜백에 유용하다.
- map(), filter(), reduce() 같은 고차 함수와 자주 사용된다.
● 데코레이터
데코레이터는 기존 함수에 기능을 추가할 수 있도록 해주는 함수다.
함수를 꾸며주는 함수라고 이해하면 쉽다.
한 줄로 다른 함수의 동작을 확장할 수 있어서 코드 반복 줄이기, 로깅, 권한 체크 등에 많이 사용된다.
우선 add 기능이 있는 기본 함수를 만들고
def add(x,y):
return x + y
함수를 받아서 감싸는 함수를 만든다. ==> 이게 데코레이터 함수
def decorator_function(func):
def wrapper(*args, **kwargs):
print("함수 호출 전")
result = func(*args, **kwargs)
print("함수 호출 후")
return result
return wrapper
내부 흐름 설명
- decorator_function(func)
→ 인자로 함수를 받음 (ex) add 함수) - 내부에 wrapper()라는 또 다른 함수를 만듦
→ 이 함수는 원래 함수(func)를 감싸는 함수 - print("함수 호출 전")
→ 원래 함수 실행 전에 먼저 실행됨 - func(*args, **kwargs)
→ 원래 함수 실행 (add(x, y)가 여기서 실행됨) - print("함수 호출 후")
→ 원래 함수 실행 후에 실행됨
즉, wrapper()는 원래 함수(add)의 전후에 코드를 끼워넣은 거다.
✅ 모듈과 패키지
● 모듈
- .py 확장자를 가진 Python 코드 파일
- 함수, 변수, 클래스 등을 담아 다른 코드에서 재사용 가능
모듈 파일을 작성하고 작성한 모듈을 사용한 예시를 보면
#모듈 파일
# calculator.py
# 변수 정의
PI = 3.14159
# 함수 정의
def add(a, b):
"""두 수의 합을 반환"""
return a + b
def subtract(a, b):
"""첫 번째 수에서 두 번째 수를 뺀 결과 반환"""
return a - b
def multiply(a, b):
"""두 수의 곱을 반환"""
return a * b
def divide(a, b):
"""첫 번째 수를 두 번째 수로 나눈 결과 반환"""
if b == 0:
raise ValueError("0으로 나눌 수 없습니다.")
return a / b
# 클래스 정의
class Circle:
def __init__(self, radius):
self.radius = radius
def circumference(self):
return 2 * PI * self.radius
# main.py
# 모듈 전체 가져오기
import calculator
# 모듈의 함수 사용
result1 = calculator.add(10, 5)
print(f"10 + 5 = {result1}") # 출력: 10 + 5 = 15
# 모듈의 변수 사용
print(f"원주율: {calculator.PI}") # 출력: 원주율: 3.14159
# 모듈의 클래스 사용
my_circle = calculator.Circle(radius=7)
print(f"반지름이 7인 원의 둘레: {my_circle.circumference()}")
# 출력: 반지름이 7인 원의 둘레: 43.98226
모듈과 라이브러리의 차이
모듈은 단일 .py 파일로 변수,함수,클래스 등을 포함하고,
라이브러리는 여러 모듈이나 패키지의 모음이다.
● 패키지
- 모듈을 디렉토리 단위로 묶은 것
- __init__.py 파일이 포함된 폴더
- 관련 모듈을 구조적으로 관리 가능
● 절대 vs 상대 임포트
| 구분 | 절대 | 상대 임포트 |
| 기준 위치 | 프로젝트 루트 기준 | 현재 파일 기준 |
| 형식 예시 | from package.module import foo | from .module import foo |
| 장점 | 명확하고 이해 쉬움 | 패키지 내부 구조 변경에 유연함 |
| 단점 | 경로 길어질 수 있음 | 가독성이 떨어질 수 있음 |
✅ 클래스와 객체지향
● 개념
- 객체(Object): 데이터와 기능을 묶은 단위
- 클래스(Class): 객체를 만들기 위한 설계도
- 현실 세계의 개념을 코드로 모델링하는 것
● 생성자와 소멸자
생성자는 객체가 생성될 때 자동으로 호출되는 메서드이고
소멸자는 객체가 메모리에서 제거될 때 호출되는 메서드이다.
__del__은 객체가 더 이상 참조되지 않을 때 자동으로 호출된다.
class Resource:
def __init__(self, name):
self.name = name
print(f"{self.name} 리소스가 초기화되었습니다.")
def __del__(self):
print(f"{self.name} 리소스가 해제되었습니다.")
def use(self):
print(f"{self.name} 리소스를 사용 중입니다.")
# 객체 생성
res = Resource("데이터베이스") # 출력: 데이터베이스 리소스가 초기화되었습니다.
res.use() # 출력: 데이터베이스 리소스를 사용 중입니다.
# 객체 제거
res = None # 출력: 데이터베이스 리소스가 해제되었습니다.
● 메서드의 종류
| 메소드 종류 | |
| 인스턴스 메서드 | self 사용, 객체 상태 변경 가능 |
| 클래스 메서드 | @classmethod, 클래스 전체에 영향 |
| 정적 메서드 | @staticmethod, 상태에 접근 X, 유틸성 함수 |
| 설명 | |
| 캡슐화 (Encapsulation) | 데이터 + 기능을 하나로 묶음. 내부 구현을 숨기고 인터페이스만 제공 |
| 상속 (Inheritance) | 기존 클래스의 기능을 물려받아 새로운 클래스를 정의 |
| 다형성 (Polymorphism) | 같은 인터페이스가 다른 동작을 수행 |
| 추상화 (Abstraction) | 핵심만 드러내고 세부는 감춤. 인터페이스 중심의 설계 |
● 캡슐화
캡슐화는 객체의 데이터(속성)와 해당 데이터를 조작하는 메서드를 하나로 묶고, 외부에서 데이터에 직접 접근하지 못하도록 제한하는 것이다. 객체 내부의 세부 구현을 숨기고, 필요한 경우에만 정의된 메서드(인터페이스)를 통해 접근하도록 한다.
캡슐화의 목적은
- 데이터 보호: 외부에서 객체의 데이터를 임의로 변경하지 못하게 함
- 내부 구현 은닉: 객체의 내부 동작 방식을 숨겨 외부 코드와의 결합도를 낮춤
- 유지보수성 향상: 내부 구현이 변경되더라도 외부 코드는 영향을 받지 않는다.
캡슐화를 구현하는 방법은 먼저 비공개 변수를 설정하고, 데코레이터를 사용해 getter,setter 속성을 제공한 뒤 메소드를 통해 간접
접근 하는 방식으로 구현한다.
비공개 변수:
class Book:
def __init__(self, title, author, isbn, year):
self.__title = title
self.__author = author
self.__isbn = isbn
self.__year = year
self.__is_borrowed = False
- 이런 식으로 변수명 앞에 __더블 언더스코어를 붙여 비공개로 만든다. 이는 Python의 이름 맹글링을 통해 외부에서 직접 접근을 어렵게 하는 것이다.
- 예: self.__title은 비공개 변수로, 클래스 외부에서 직접 접근할 수 없다.
Getter/Setter 메서드:
@property
def title(self):
return self.__title
- 이렇게 @property 데코레이터를 사용해 읽기 전용(getter) 또는 쓰기 전용(setter) 속성을 제공한다.
- 이를 통해 데이터 접근을 제어한다.
메서드를 통한 간접 접근의 예를 보면
def borrow(self):
if self.__is_borrowed:
raise Exception("이미 대출된 책입니다.")
self.__is_borrowed = True
- __is_borrowed 상태는 borrow()와 return_book() 메서드를 통해서만 변경된다.
- 외부에서 book.__is_borrowed = True로 직접 변경할 수 없으므로, 대출 상태의 무결성이 보장되는 것이다.
- 비공개 데이터를 조작하려면 클래스에 정의된 메서드를 사용해야 한다.
- 외부에서 내부 데이터를 직접 수정하지 못하도록 제한한다.
● 접근 제어
- public 속성/메소드
- 일반 이름으로 정의
- 클래스 외부/내부 사용 가능
- protected 속성/메소드
- 언더스코어 하나로 시작 ex) _name
- 클래스 내부/자식 클래스에서만 사용
- 외부에서 접근 가능 but 직접 사용 x 권장
- Private 속성/메소드
- 언더스코어 두 개로 시작 ex) __name
- 클래스 내부에서만 직접 접근 가능
● 객체지향 설계 원칙 (SOLID 원칙)
유지보수하기 좋고, 확장 가능하며, 이해하기 쉬운 코드를 만들기 위해 체계적인 기준을 정리해 놓은 것이 SOLID 원칙이다.
각 원칙들의 정의와 중요성에 대해 정리해 보겠다.
1. 단일 책임 원칙 (SRP)
- 한 클래스는 하나의 책임만 가져야 한다
클래스가 너무 많은 역할을 하다 보면 수정 시 영향 범위가 커지고, 예기치 않은 오류가 발생할 수 있다. 예를 들어, 회원 정보를 저장하는 클래스가 로그인 로직까지 갖고 있다면, 로그인 방식이 바뀔 때 회원 저장 코드까지 건드리게 된다. 책임이 하나로 명확할수록 유지보수가 쉬워진다.
2. 개방-폐쇄 원칙 (OCP)
- 확장에는 열려 있으나, 수정에는 닫혀 있어야 한다
기존 코드를 건드리지 않고 기능을 확장할 수 있어야 한다. 예를 들어 새로운 결제 방식을 추가할 때 기존 Payment 클래스를 수정하지 않고, 새로운 서브클래스를 만들어 확장하는 방식이다. 이렇게 하면 기존 코드의 안정성을 해치지 않으면서도 기능을 유연하게 늘릴 수 있다.
3. 리스코프 치환 원칙 (LSP)
- 자식 클래스는 부모 클래스의 역할을 대체할 수 있어야 한다
상속을 받은 자식 클래스가 부모 클래스의 기능을 그대로 사용할 수 있어야 하며, 이를 대체해도 프로그램이 문제 없이 동작해야 한다. 그렇지 않으면 상속 구조의 의미가 퇴색되고, 예측 불가능한 버그가 생길 수 있다.
4. 인터페이스 분리 원칙 (ISP)
- 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다
하나의 거대한 인터페이스보다는, 목적에 맞게 분리된 작은 인터페이스 여러 개로 나누는 것이 좋다. 예를 들어, ‘날 수 있는 동물’과 ‘걷는 동물’을 같은 인터페이스로 묶으면, 펭귄처럼 날지 못하는 동물은 사용하지도 않는 ‘fly()’ 메서드를 구현해야 하는 문제가 생긴다.
5. 의존 역전 원칙 (DIP)
- 상위 모듈은 하위 모듈의 구현에 의존하면 안 된다
즉, 구체적인 구현이 아니라 인터페이스에 의존해야 한다. 그래야 나중에 구현체를 바꾸더라도 비즈니스 로직이 영향을 받지 않는다. 예를 들어 데이터 저장 방식이 MySQL에서 MongoDB로 바뀌더라도, 인터페이스만 잘 맞춰두면 서비스 코드를 변경할 필요가 없다.
[2] 과제
이번에 배운 파이썬 개념을 토대로 푼 문제 중에서 내가 푼 코드에서 개선해야될 부분이 있었던 문제와 가장 어려웠던 문제에
대해 정리해 보도록 하겠다.
문제 1
- 함수 Quiz 02 데코레이터
함수 호출 시 매개변수와 반환값을 로그로 출력하는 데코레이터를 작성하고 이 데코레이터를 add 함수에 적용해 보는 문제였다.
내가 작성한 코드
가변 매개변수를 사용하여 더 일반화된 데코레이터로 작성해 보았다.
def log_function_call(func):
def wrapper(*args, **kwargs):
print(f"입력값: {args}, {kwargs}")
result = func(*args, **kwargs)
print(f"반환값: {result}")
return result
return wrapper
- 유연성: *args와 **kwargs를 사용해 어떤 함수에도 적용 가능한 데코레이터로 개선.
- 확장성: 로깅 형식을 커스터마이징하거나, 예외 처리를 추가할 수 있음.
- 캡슐화 강화: 데코레이터는 기존 로직에 직접 접근하지 않으므로 캡슐화를 침해하지 않고 부가기능을 적용할 수 있다.
문제 2
- 이 문제는 도서, 회원, 도서관 클래스를 객체 지향적으로 설계하여 도서 관리, 검색, 대출/반납, 회원 등록 및 대출 현황 확인 등의 기능을 구현하고, SOLID 원칙과 캡슐화를 적용해 유연하고 안정적인 도서관 관리 시스템을 만드는 과제였다.
코드
SOLID 원칙 적용
- 단일 책임 원칙:
- Book 클래스는 도서 정보와 대출 상태 관리만 담당
- Member 클래스는 회원 정보와 대출 목록 관리만 담당
- Library 클래스는 도서와 회원의 전체 관리 및 대출/반납 프로세스를 담당
- 인터페이스 분리 원칙:
- 각 클래스는 필요한 기능만 제공한다. 예를 들어, Book 클래스는 대출/반납 상태 관리에 필요한 메서드만 정의하고, Member는 대출/반납 처리만 다룬다.
- 캡슐화:
- __ 접두사를 사용하여 비공개 변수(__title, __borrowed_books 등)를 정의하고, @property로 읽기 전용 접근을 제공하여 캡슐화를 적용하였다.
이번 주 파이썬 수업에서는 함수, 클래스와 객체지향, 그리고 모듈에 대해 배웠다.
데코레이터를 배우면서 핵심 로직은 건드리지 않고 추가 기능만 붙여서 구현하는 게 나름 재밌었다.
그리고 클래스는 데이터와 동작을 하나로 묶어 관리하기 편리하다는 특징이 있고, 특히 캡슐화(비공개 변수, getter/setter)를 통해 데이터를 보호하고, 외부에서 안전하게 접근하도록 설계하는 게 객체지향의 핵심이라는 것을 배웠다.
처음에는 간단한 문법일 거라 생각했지만, 직접 과제를 통해 배운 개념들을 활용해 구현해보면서 어려움을 느꼈다.
더 많은 예제들을 풀어보면서 배운 개념들을 적용해 봐야겠다.
본 후기는 [카카오엔터프라이즈x스나이퍼팩토리] 카카오클라우드로 배우는 AIaaS 마스터 클래스 (B-log) 리뷰로 작성 되었습니다.
'카카오클라우드 AIaaS 교육 > AIaaS를 위한 머신러닝&AI' 카테고리의 다른 글
| [스나이퍼팩토리] AIaaS 마스터 클래스 9주차 DAY 42 - python 심화 (0) | 2025.05.27 |
|---|---|
| [스나이퍼팩토리] AIaaS 마스터 클래스 9주차 DAY 43 - numpy,pandas (0) | 2025.05.26 |
| [스나이퍼팩토리] AIaaS 마스터 클래스 9주차 DAY 41 - python 심화 (0) | 2025.05.26 |
| [python 기초] 과제 (1) | 2025.05.22 |
| [스나이퍼팩토리] AIaaS 마스터 클래스 9주차 DAY 38,39 - python 기초 (0) | 2025.05.22 |