본문 바로가기

클라우드(Cloud)

[스나이퍼팩토리] 카카오클라우드 AIaaS 마스터 클래스 9주차 - 파이썬 심화 1 (Python Advanced 1)

 

 

 지난 글까지 파이썬(Python)의 기초적인 개념들을 정리하며 학습해 보았고, 학습한 내용을 바탕으로 예제 코드도 작성해 보았다. 이제는 파이썬을 조금 더 잘 이용해 보기 위해 심화라고 할 수 있는 개념들을 학습할 것이다. 파이썬을 배우고 사용해 왔다고는 하지만 개념을 다시 보고 어떻게 사용하는지를 익히는 것은 오랜만이기에 예제 코드를 자세히 살펴보며 학습하는 것을 목표로 하려 한다. 

 

 


✅ 함수

1️⃣ 함수란?

특정 작업을 수행하는 코드 블록으로, 코드의 재사용과 가독성 향상, 유지보수의 용이성 등을 이유로 사용한다. 

def greet(name):
    print(f"안녕, {name}!")

 

 

2️⃣ 매개변수의 종류

유형 설명 예시
필수 매개변수 반드시 전달해야 함 def add(a, b):
기본값 매개변수 값을 주지 않으면 기본값 사용 def greet(name="서연"):
가변 매개변수 *args 개수가 정해지지 않은 위치 인자, 튜플로 받음 def sum_all(*args):
키워드 매개변수 **kwargs 키=값 형태의 인자들, 딕셔너리로 받음 def print_info(**kwargs):
 

📌 순서 규칙: **일반 → 기본값 → *args → kwargs

 

3️⃣ lambda 함수 (익명 함수)

 lambda 함수이름이 없이 일회성으로 사용되어 익명 함수라고도 불린다. 또한 return도 필요하지 않으며, 한 줄로 작성되기에 간편하게 사용할 수 있다는 점이 특징이다.

# 형태 => lambda 매개변수: 표현식

square = lambda x: x**2
print(square(4))  # 16

 

 

4️⃣ 고차 함수와 람다 활용

함수 설명 예시
map() iterable의 모든 요소에 함수 적용 map(lambda x: x*2, [1,2,3])
filter() 조건을 만족하는 요소만 필터링 filter(lambda x: x > 5, [3,5,7])
reduce() 누적 계산, functools 모듈 필요 reduce(lambda x,y: x+y, [1,2,3])

 

5️⃣ 클로저 (Closure)

함수가 자신을 둘러싼 스코프의 변수를 기억하는 함수로, 외부 함수가 종료된 뒤에도 그 변수를 기억한다.

def make_multiplier(n):
    def multiplier(x):
        return x * n
    return multiplier

doubler = make_multiplier(2)
print(doubler(5))  # 10

 

6️⃣ 데코레이터 (Decorator)

기존 함수의 기능을 수정하거나 확장하는 함수로, @decorator_name을 함수 위에 붙여 사용한다. 

def log(func):
    def wrapper(*args, **kwargs):
        print(f"{func.__name__} 실행 전")
        result = func(*args, **kwargs)
        print(f"{func.__name__} 실행 후")
        return result
    return wrapper

@log
def say_hello():
    print("Hello!")

say_hello()

⚙️ 요약 정리

개념 요약
*args 위치 인자 여러 개를 튜플로 받음
**kwargs 키워드 인자 여러 개를 딕셔너리로 받음
lambda 짧고 간단한 익명 함수
map/filter/reduce 리스트 처리에 자주 사용되는 고차 함수
클로저 외부 변수 상태를 기억하는 함수
데코레이터 기존 함수에 기능을 추가하는 패턴

✅ 모듈과 패키지

1️⃣ 모듈(Module)

 함수, 클래스, 변수 등을 담고있는 코드 단위로, 다른 파이썬 파일에서 import로 불러와 코드를 재사용할 수 있으며, .py 파일 하나가 모듈인 셈이다. 

# calc.py
def add(a, b):
    return a + b

# main.py
import calc
print(calc.add(3, 4))  # 7

 

 

2️⃣ 모듈의 장점

  • 코드 재사용성: 한 번 정의하고 여러 프로젝트에서 사용 가능
  • 구조화: 관련 코드끼리 묶어 관리
  • 네임스페이스 분리: 충돌 방지
  • 협업 용이: 파일 단위로 나눠서 작업 가능

 

3️⃣ 패키지(Package)란?

 여러 모듈(.py 파일)을 담은 폴더를 의미한다. 내부에 __init__.py 파일이 있어야 패키지로 인식되는 권장 사항이 있다. 

 

예시 구조:

my_project/
├── my_package/
│   ├── __init__.py
│   ├── math_utils.py
│   └── string_utils.py
└── main.py

 

사용 예시: 

from my_package.math_utils import add

 

 

4️⃣ 모듈 import 방법

방식 설명 예시
절대 경로 import 루트 디렉토리부터 전체 경로 from package.module import func
상대 경로 import 현재 위치 기준 . 또는 .. 사용 from . import module
from ..subpackage import mod
 

 Python에서는 가독성과 유지보수를 이유로 절대 경로 import 권장

 

5️⃣ 외부 패키지 관리 - pip

  • pip install 패키지명: 외부 패키지 설치
  • pip freeze > requirements.txt: 현재 설치된 패키지 목록 저장
  • pip install -r requirements.txt: 목록에 따라 일괄 설치

 

6️⃣ 가상환경(Virtual Environment)

프로젝트마다 독립된 패키지 환경을 구성하기 위해 가상환경(Virtual Environment)를 사용하기도 한다. 

 

사용 예시 (Mac/Linux): 

python -m venv venv
source venv/bin/activate
deactivate  # 비활성화

 

 

7️⃣ 표준 라이브러리 & 패키지

종류 설명 예시 
표준 라이브러리 설치 없이 기본 제공 math, os, sys, datetime
외부 라이브러리 pip로 설치 requests, pandas, numpy
 

⚙️ 요약 정리

용어  설명
모듈 .py 파일 하나, 함수/클래스를 포함
패키지 여러 모듈을 묶은 폴더 구조
__init__.py 패키지임을 알리는 초기화 파일
pip 패키지 설치 도구
가상환경 프로젝트별 패키지 독립 환경

✅ 객체지향 프로그래밍(OOP)

1️⃣ 객체지향이란?

 프로그램을 객체(Object)들의 상호작용으로 구성하는 방식을 말한다. 이는 현실 세계를 프로그래밍적으로 표현하는데 유리하다는 특징을 갖는다. 이때 말하는 객체는 데이터(속성)과 기능(메서드)를 포함한 것을 의미한다. 

 

예시: 🚗 자동차(Car) 객체의 표현 방식

class Car:
    def __init__(self, color, speed):
        self.color = color
        self.speed = speed

    def accelerate(self):
        self.speed += 10

 

2️⃣ OOP의 4가지 핵심 개념

개념 설명
캡슐화 속성과 메서드를 하나로 묶고 외부에 내부를 감춤 (private, protected)
상속 기존 클래스를 바탕으로 새로운 클래스를 확장
다형성 동일한 메서드 이름이 클래스마다 다르게 동작
추상화 불필요한 세부사항을 숨기고 핵심만 표현
 

3️⃣ 클래스와 객체

  • 클래스(Class): 객체를 만들기 위한 설계도
  • 객체(Object): 클래스에서 생성된 실체 (== 인스턴스)

클래스는 붕어빵🍘, 객체는 붕어🐟 라고 생각하면 이해가 쉬울 수 있을 것 같다. 

 

예시: 

class Dog:
    def __init__(self, name):
        self.name = name

    def bark(self):
        print(f"{self.name}가 짖습니다!")

d1 = Dog("코코")
d1.bark()  # 코코가 짖습니다!

 

 

4️⃣ 접근 제어자 (캡슐화)

종류 기호 접근 가능성
Public name 어디서나 접근 가능
Protected _name 클래스 내부와 자식 클래스에서 접근 권장
Private __name 클래스 내부에서만 직접 접근 가능 (우회 가능)
 

5️⃣ 클래스 구성 요소

항목 설명
인스턴스 변수 객체마다 따로 존재
클래스 변수 모든 인스턴스가 공유
인스턴스 메서드 self를 첫 매개변수로 받음
클래스 메서드 @classmethod, cls 사용
정적 메서드 @staticmethod, 클래스와 무관한 유틸성 기능
 

6️⃣ 상속(Inheritance)

기존의 클래스를 확장해 코드를 재사용한다.

class Animal:
    def speak(self):
        print("동물이 소리냅니다")

class Dog(Animal):
    def speak(self):
        print("멍멍!")

 

→ super()로 부모 메서드 호출 가능

 

7️⃣ 다형성(Polymorphism)

동일한 speak() 메서드가 클래스마다 다르게 동작한다. 

animals = [Dog(), Cat(), Duck()]
for a in animals:
    a.speak()  # 각각 다른 출력

 

8️⃣ 추상 클래스 & 인터페이스

공통 구조만 정의하고, 구현은 하위 클래스가 맡음

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

 

9️⃣ SOLID 원칙 요약

원칙 의미
S - 단일 책임 클래스는 하나의 책임만 가져야 함
O - 개방-폐쇄 확장에는 열려 있고, 수정에는 닫혀야 함
L - 리스코프 치환 부모 대신 자식을 넣어도 프로그램이 동작해야 함
I - 인터페이스 분리 사용하지 않는 메서드에 의존하지 않아야 함
D - 의존 역전 추상에 의존하고 구체화에 의존하지 말 것
 

⚙️ 요약 정리

개념 설명
클래스 객체를 만드는 설계도
객체 클래스에서 생성된 실체
캡슐화 내부 구현 숨기기
상속 코드 재사용
다형성 같은 이름 다른 행동
추상화 필수만 드러내기
접근 제어 _, __로 정보 은닉

 


위 내용 중 객체지향 설계 개념인 SOLID 원칙을 너무 요약해서 정리한 것 같아 각 원칙마다 예제 코드와 함께 살펴볼 것이다. 

 

✅ SOLID 원칙이란?

객체지향 설계를 더 유연하고 확장 가능하게 만들어주는 5가지 원칙의 집합이다. 

원칙 의미
S 하나의 책임만 가지자 (단일 책임 원칙)
O 확장엔 열려 있고, 수정엔 닫혀 있어야 함
L 자식은 부모를 대체할 수 있어야 함
I 인터페이스는 작게 쪼개자
D 추상에 의존하고 구체에 의존하지 말자
 

 

🔹 S - 단일 책임 원칙 (SRP: Single Responsibility Principle)

❌ 하나의 클래스가 여러 역할을 하면 유지보수가 어려워짐

 

🧪 나쁜 예시 (문제 코드)

class Report:
    def __init__(self, data):
        self.data = data

    def save_to_file(self):
        with open("report.txt", "w") as f:
            f.write(self.data)
  • 이 클래스는 데이터 보관 + 파일 저장 두 가지 책임을 가짐 ❌

✅ 좋은 예시 (개선 코드)

class Report:
    def __init__(self, data):
        self.data = data

class ReportSaver:
    def save(self, report: Report):
        with open("report.txt", "w") as f:
            f.write(report.data)
  • Report: 데이터만 관리
  • ReportSaver: 저장 기능 전담 각 클래스가 하나의 책임만 갖도록 해야한다. 

 

🔹 O - 개방-폐쇄 원칙 (OCP: Open-Closed Principle)

새로운 기능 추가엔 열려 있고, 기존 코드 수정엔 닫혀 있어야 함

 

❌ 나쁜 예시 (조건문으로 분기 처리)

def discount(price, user_type):
    if user_type == "student":
        return price * 0.8
    elif user_type == "vip":
        return price * 0.6

 

✅ 좋은 예시 (확장 가능한 구조)

class DiscountStrategy:
    def apply(self, price):
        return price

class StudentDiscount(DiscountStrategy):
    def apply(self, price):
        return price * 0.8

class VIPDiscount(DiscountStrategy):
    def apply(self, price):
        return price * 0.6

# 사용
def calculate_price(price, strategy: DiscountStrategy):
    return strategy.apply(price)

print(calculate_price(10000, StudentDiscount()))  # 8000
  • 새로운 할인 정책은 클래스를 추가만 하면 됨 → 기존 코드 수정 ❌

 

🔹 L - 리스코프 치환 원칙 (LSP: Liskov Substitution Principle)

부모 클래스 객체 대신 자식 객체를 사용해도 프로그램이 깨지지 않아야 함

 

❌ 나쁜 예시

class Bird:
    def fly(self):
        print("날아간다")

class Penguin(Bird):
    def fly(self):
        raise Exception("펭귄은 못 날아요!")
  • Penguin은 Bird이지만 fly()를 오버라이딩해서 예외 발생

✅ 좋은 예시 (클래스 분리)

class Bird:
    pass

class FlyingBird(Bird):
    def fly(self):
        print("날아간다")

class Penguin(Bird):
    def swim(self):
        print("수영한다")
  • 펭귄은 날지 않는 새니까, fly()가 없는 클래스로 분리 부모 대체해도 무리 없음

 

🔹 I - 인터페이스 분리 원칙 (ISP: Interface Segregation Principle)

클라이언트는 자신이 사용하지 않는 인터페이스에 의존하면 안됨
→ 너무 많은 기능을 하나의 클래스에 몰아넣지 말자

 

❌ 나쁜 예시

class MultiFunctionDevice:
    def print(self):
        pass
    def scan(self):
        pass
    def fax(self):
        pass
  • 모든 기능을 상속받은 클래스는 fax를 안 써도 구현해야 함 불필요 ❌

✅ 좋은 예시 (역할 분리)

class Printer:
    def print(self):
        pass

class Scanner:
    def scan(self):
        pass

class Fax:
    def fax(self):
        pass

class MultiFunctionMachine(Printer, Scanner, Fax):
    pass
  • 필요한 기능만 상속 유연한 설계라고 할 수 있음 

 

🔹 D - 의존성 역전 원칙 (DIP: Dependency Inversion Principle)

상위 모듈은 하위 모듈에 의존하면 안 되고, 둘 다 추상(인터페이스)에 의존해야 함

 

❌ 나쁜 예시

class MySQLDatabase:
    def connect(self):
        print("MySQL에 연결!")

class App:
    def __init__(self):
        self.db = MySQLDatabase()
  • App 클래스는 MySQL에 직접 의존함 → 다른 DB로 교체 불가능 ❌

✅ 좋은 예시

class Database:
    def connect(self):
        pass

class MySQLDatabase(Database):
    def connect(self):
        print("MySQL 연결")

class PostgreSQLDatabase(Database):
    def connect(self):
        print("PostgreSQL 연결")

class App:
    def __init__(self, db: Database):
        self.db = db

    def start(self):
        self.db.connect()

# 주입
db = PostgreSQLDatabase()
app = App(db)
app.start()
  • App은 Database 추상 클래스에만 의존 → DB 교체가 쉬움 

 지금까지 함수를 비롯해 모듈과 패키지, 객체 지향에 대한 개념까지 내용을 정리하며 학습해보았다. 위 개념에서는 특별히 이해가 안된다거나 모르겠다고 생각되는 개념이 없었지만, 잠깐 의문이 들어서 찾아보았던 내용을 아래 정리해보며 넘어가볼 것이다. 

 

 

 단일 책임 원칙을 설명하는 문장이 '하나의 클래스가 여러 역할을 하면 유지 보수가 어려우니 여러 역할을 하면 안된다' 였다. 

 

 이때 갑자기 의문이 들었던 부분이 있었는데, 하나의 클래스가 여러 역할을 한다는 부분이었다. 여기서 말하는 여러 역할이라는 말은 클래스의 목적을 의미한다. 

 

 예를 들어, "도서관 이용자 관리"라는 목적의 클래스에 도서 추가와 같은 기능이 있다면 단일 책임 원칙을 위반했다고 할 수 있는 것이다. 

 

 분명 이해했었고 알고 있었던 개념이지만 어느날 갑자기 헷갈리고 이상하게 느껴질 때가 있는데 그럴 때마다 다시 복습한다는 생각으로 학습하면 도움이 될 것 같다. 

 

 다음 글에서는 해당 글에서 학습한 개념들과 관련된 예제 코드들을 살펴볼 예정이다. 

 


본 후기는 [카카오엔터프라이즈x스나이퍼팩토리] 카카오클라우드로 배우는 AIaaS 마스터 클래스 (B-log) 리뷰로 작성 되었습니다.