[7일차] 사용자정의함수, 객체지향프로그래밍(캡슐화, 상속)

2026. 5. 28. 17:40KDT/1. Python

# 중첩 함수

# 중첩 함수
def outer_function():
    enclosing_var = "둘러싼 범위 변수"
    
    def inner_function():
        print(enclosing_var)
    
    return inner_function       # 함수 자체를 return 
    
f = outer_function()
f()		# 둘러싼 범위 변수
  • 클로저 변수 : 내부 함수가 외부 함수의 지역 변수를 사용하면, 파이썬은 그 값을 바로 삭제하지 않고 클로저 공간에 보관

# 클로저 공간과 빌트인 함수

1. 클로저 공간

  • 내부 함수가 외부 함수의 변수를 계속 사용할 수 있도록 저장해두는 메모리 영역(정확히는 객체 구조)
  • 내부 함수가 외부 함수의 지역 변수를 참조하고 있으면 파이썬은 해당 값을 지우지 않고 __closure__라는 형태로 보관
  • 이때 저장되는 값은 보통 cell 객체로 감싸져 유지
  • 내부 함수 실행 시 이 클로저 공간을 통해 외부 변수로 접근
  • heap 영역에 클로저 객체 저장

2. 빌트인 함수

  • import 없이 바로 사용 가능한 기본적인 함수
    ex. print(), len(), type(), range() 같은 함수들
  • 변수 이름을 찾을 때 적용되는 LEGB 규칙 (Local -> Enclosing -> Global -> Built-in)에서 마지막 단계이므로
    만약 같은 이름의 변수를 직접 정의하면 Built-in 함수는 가려져 사용이 불가능하게 됨
    (Enclosing : 중첩된 함수의 외부 함수 영역에 있는 변수)

# 함수 메모리 제거

  • 함수도 파이썬 객체이므로 참조 카운팅과 가비지 컬렉션의 원칙에 따라 동작
    (함수를 참조하는 변수나 요소가 없으면 메모리에서 제거될 수 있음)
  • del 명령어로 제거 가능하지만 즉시 메모리에서 제거된다는 것은 보장하지 않음
    ([2일차] 가비지 컬렉션 참고)
  • del로 제거 후 함수 호출하면 "NameError: name 'func1' is not defined" 에러 발생

# 콜백함수 callback function⭐

  • 다른 함수에 인자로 전달되어, 특정 시점이나 조건이 되었을 때 나중에 호출되는 함수
    (= 함수가 다른 함수를 호출)
  • 함수도 객체기 때문에 변수처럼 전달이 가능
  • 공통 작업은 그대로 두고, 세부 동작만 바꾸고 싶을 때 유용
def say_hello(name):
    print(f"{name}님 안녕하세요!")

def execute(callback):      # 여기 callback은 변수명 (a등 다른 이름 사용해도 됨)
    print("작업을 시작합니다.")
    callback("김사과")      # 위의 callback 변수명 사용. "김사과"는 매개변수. 
    print("작업을 종료합니다.")

execute(say_hello)      # 👉여기서 say_hello가 콜백 함수

# say_hello() : 지금 당장 함수를 실행해라
# say_hello : 함수의 주소
# -> execute의 매개변수로 say_hello의 함수 주소를 넘기는 것
def print_score(score):
    print(f"점수: {score}")

def print_pass_fail(score):
    if score >= 80:
        print(f"{score}점: 합격")
    else:
        print(f"{score}점: 불합격")

def print_bonus_score(score):
    print(f"보너스 적용 점수: {score + 5}")

def process_scores(scores, callback):
    for score in scores:
        callback(score)

scores = [80, 95, 70, 100, 60]

print("원점수 출력")
process_scores(scores, print_score)

print("합격 여부 출력")
process_scores(scores, print_pass_fail)

print("보너스 점수 출력")
process_scores(scores, print_bonus_score)

👉콜백함수는 공통 작업은 그대로 두고, 세부 동작만 바꾸고 싶을 때 유용

 

students = [
    {"name": "김사과", "score": 90},
    {"name": "반하나", "score": 75},
    {"name": "오렌지", "score": 100}
]

def get_score(student):
    # print(student["score"], end = " ") # 90 75 100
    return student["score"]

result = sorted(students, key=get_score)
print(result)

# [{'name': '반하나', 'score': 75}, {'name': '김사과', 'score': 90}, {'name': '오렌지', 'score': 100}]

# key=get_score :  key 뒤에 있는 것(get_score)으로 sorted할거야 (get_score = 콜백함수)

👉 sorted()는 학생 하나하나 비교할 때마다 get_score()를 호출해서 정렬 기준값을 얻음


# 람다 함수 lambda function

  • 이름 없이 한 줄로 정의 가능한 익명 함수
  • 일반함수(def)에 비해 구조는 제한적이지만 코드가 간결해짐
  • sorted, map, filter 같은 함수에서 콜백 함수로 자주 사용 (간단한 콜백 함수는 람다 함수 사용하면 굳)
  • 무조건 return 형
  • 이름 없는 함수기 때문에 함수명 호출로 사용 불가능
lambda 매개변수: 반환할_표현식
scores = [45, 70, 88, 52, 100]

result = list(map(lambda score: score + 10, scores))    
print(result)		# [55, 80, 98, 62, 110]

passed = list(filter(lambda score: score >= 60, scores))
print(passed)		# [70, 88, 100]

👉 map() : 리스트의 각 요소에 같은 작업을 적용할 때 사용

👉 filter() : 조건을 만족하는 값만 골라낼 때 사용


# 프로그래밍 방법론

  • 프로그램을 개발하는 다양한 접근 방식이나 철학
  • 프로젝트 규모, 요구 사항, 개발 팀의 특성에 따라 적합한 방법론이 달라지기 때문에 각 방법론의 특징과 장단점 이해가 중요

1. 절차적 프로그래밍 (Procedural Programming)

  • 절차를 중시하는 방식 (= 작업을 순서대로 실행하도록 프로그램 구성)
  • 코드가 명령문으로 이루어져 있고 작업 순서가 중요
  • 함수를 통해 프로그램의 기능을 분리하고, 코드 중복을 줄임

2. 객체지향 프로그래밍 (Object-Oriented Programming, OOP)

  • 객체라는 개념을 사용해 프로그램을 구성
  • 객체 = 데이터(속성) + 함수(메서드)
  • 클래스라는 틀을 이용해 객체를 정의하고, 이를 통해 여러 객체 생성
  • 캡슐화, 상속, 다형성 등의 특징을 활용해 코드를 재사용하고 확장 가능

3. 함수형 프로그래밍 (Functional Programming)

  • 함수 중심 프로그램 구성
  • 수학적 함수 개념에 기초
  • 코드 간결, 직관적
    ex. map, filter, reduce 등의 함수나 재귀를 주로 사용하여 데이터 처리 간결하게 수행

 

# 클래스

클래스와 객체

  • 객체지향 프로그래밍에서 데이터(속성)와 기능(메서드)을 하나로 묶어 객체를 만들기 위한 설계도
  • 클래스를 정의하면 여러 개의 객체(인스턴스) 생성
  • 각 객체는 동일한 구조면서 서로 다른 값을 가질 수 있음
class 클래스이름:
    # 클래스 속성(멤버 변수) 정의
    속성1 = 초기값1
    속성2 = 초기값2

    # 생성자 메서드 (생략 가능)
    def __init__(self, 매개변수1, 매개변수2, ...):
        # 인스턴스 속성 초기화
        self.속성1 = 매개변수1
        self.속성2 = 매개변수2

    # 메서드(멤버 함수) 정의
    def 메서드1(self, 매개변수1, 매개변수2, ...):
        # 메서드 동작 정의
        pass

    def 메서드2(self, 매개변수1, 매개변수2, ...):
        # 메서드 동작 정의
        pass

 


# 객체와 인스턴스

  • 객체(Object)와 인스턴스(instance)는 거의 같은 의미지만 관점에 따라 구분
  • 객체 : 메모리에 생성된 모든 실체를 의미하는 포괄적 의미
  • 인스턴스 : 특정 클래스에 의해 생성된 객체를 가리키는 관계 중심의 용어
    (어떤 클래스의 인스턴스이다)
  • 즉, 모든 인스턴스는 객체이지만 / 모든 객체가 특정 클래스의 인스턴스라고 강조해서 부르는 것은 아니며 / 인스턴스 라는 용여는 주로 클래스와의 관계를 설명할 때 사용

# 메서드

1. 생성자 Constructor

  • 클래스의 인스턴스가 생성될 때 자동으로 호출되는 특별한 메서드
  • 객체의 초기화
  • __init__ : 파이썬 생성자 메서드 이름 (정해져있음)
  • 속성은 self를 사용하여 접근하고 설정
class 클래스이름:
	# 생성자
    def __init__(self, 매개변수1, 매개변수2):       # 매개변수 유무와 관계없이 self는 무조건 필요
        self.속성1 = 매개변수1
        self.속성2 = 매개변수2

 

2. 인스턴스 변수

  • 각 인스턴스마다 별도로 생성되는 변수
  • 객체를 만들 때마다 self 키워드를 통해 생성자(init)에서 정의됨
  • 각 객체가 고유한 값을 따로 저장
  • 인스턴스명으로만 접근 가능
  • 다른 인스턴스에 영향을 주지 않고, 자신만을 데이터를 가질 수 있음
  • 생성자 매개변수 개수 맞추지 않으면 TypeError 발생
# name과 quantity는 인스턴스 변수
class Fruit:
    def __init__(self):
        self.name = ''		
        self.quantity = 0
        
apple = Fruit()
print(apple)    # <__main__.Fruit object at 0x0000022CF53338F0>
print(apple.name)   # 
print(apple.quantity) # 0

banana = Fruit()
print(banana)
print(banana.name)
print(banana.quantity)

인스턴스변수_설명

class Fruit:
    def __init__(self, name, quantity, origin='원산지 미상'):
        self.name = name
        self.quantity = quantity
        self.origin = origin

apple = Fruit('사과', 10, '한국')
banana = Fruit('바나나', 5, '필리핀')

apple = Fruit()  # 오류 발생
# TypeError: __init__() missing 2 required positional arguments
# -> 기본값 지정이 안되어있는 name, quantity 매개변수를 받아오지 못해서 에러!

 

3. 메서드

  • 클래스 내부에 정의된 함수
  • 객체(인스턴스)나 클래스와 관련된 동작(행동)을 수행
  • 종류
    1. 인스턴스 메서드 : self 사용
      self : 어떤 객체에서 호출했는지 찾기 위한 변수(객체 주소를 가지고 있음) -> 메모리를 아껴쓰기 위함
    2. 클래스 메서드 : cls 사용. @classmethod
      cls : 클래스 주소를 가지고 있음
    3. 정적 메서드 : 별도의 참조 없이 독립적으로 동작. @staticmethod
class Fruit:
    def __init__(self, name, quantity):
        self.name = name
        self.quantity = quantity

    # 인스턴스 메서드1
    def print_info(self):
        print(f"과일 이름: {self.name}")
        print(f"수량: {self.quantity}")

    # 인스턴스 메서드2
    def add_quantity(self, amount):
        self.quantity += amount
        print(f"{amount}개 추가되었습니다.")
        
apple = Fruit("사과", 10)
apple.print_info()
apple.add_quantity(5)
apple.print_info()

banana = Fruit("바나나", 3)
banana.print_info()
banana.add_quantity(2)
banana.print_info()

인스턴스메서드_설명

class Order:
    tax_rate = 0.1  # 클래스 변수 (세율: 모든 주문 공통)

    def __init__(self, price, quantity):
        # 인스턴스 변수 = 생성자 안의 변수 (price, quantity)
        self.price = price        # 상품 가격
        self.quantity = quantity  # 수량

    # 인스턴스 메서드
    def total_price(self):
        return self.price * self.quantity

    # 클래스 메서드
    @classmethod        # 데코레이터
    def set_tax_rate(cls, rate):
        cls.tax_rate = rate     
        # 클래스 주소를 가지고 있으므로 클래스 변수 사용 가능
        # 인스턴스가 아닌 (=객체를 생성하지 않고) 클래스로 호출 가능
        # Order.set_tax_rate()로 사용 가능

    # 정적 메서드
    @staticmethod
    def calculate_discount(price, discount_rate):
        return price * (1 - discount_rate)
        # 클래스 이름으로 접근 가능
        # Order.calculate_discount()로 사용 가능
        # 클래스 안에 속해만 있는 것 뿐. self, cls 사용 불가(= 인스턴스 변수, 인스턴스 메서드)
        
# 인스턴스 생성
order1 = Order(10000, 2)  # 10000원 상품 2개

# 인스턴스 메서드 호출
print("총 가격:", order1.total_price())  # 20000

# 정적 메서드 사용 (할인 계산)
discounted = Order.calculate_discount(order1.total_price(), 0.2)
print("할인 적용 가격:", discounted)  # 16000

# 클래스 메서드 사용 (세율 변경)
Order.set_tax_rate(0.2)
print("변경된 세율:", Order.tax_rate)  # 0.2

 

4. 데코레이터 decorator

  • 기존 함수나 메서드를 수정하지 않고 그 기능을 감싸서 추가 동작을 붙여주는 기능
  • 실행 전후에 로그를 출력하거나 권한 체크, 시간 측정 등의 공통 기능을 쉽게 재사용할 수 있게 해줌
  • "내부적으로는 함수를 인자로 받아 새로운 함수를 반환"하는 구조로 동작
    (-> 값을 리턴하는 것이 아닌 함수를 리턴. 클로저로 데코레이터 생성 가능)
  • @데코레이터이름 형태로 사용하면 해당 함수가 자동으로 데코레이터에 전달되어 기능 확장

 

5. 클래스 변수

  • 클래스 생성 시 정의
  • 해당 클래스로 생성된 모든 인스턴스(객체)들이 공유하는 변수
  • 클래스 변수는 클래스명으로 직접 접근하거나 인스턴스명으로 접근 가능
  • 모든 인스턴스가 같은 메모리 공간의 값을 참조하기 때문에 한 인스턴스에서 값을 변경하면 다른 인스턴스에서도 변경된 값을 볼 수 있음
class Fruit:
    origin = "한국"  # 클래스 변수 (공통 속성)

    def __init__(self, name, quantity):
        # 인스턴스 변수 (각 객체마다 다름)
        self.name = name
        self.quantity = quantity

    def print_info(self):
        print(f'원산지: {Fruit.origin}')   # 클래스 변수
        print(f'이름: {self.name}')       # 인스턴스 변수
        print(f'수량: {self.quantity}')   # 인스턴스 변수
        
print("----- 클래스 변수 변경 -----")
Fruit.origin = '필리핀'

apple.print_info()
banana.print_info()

print("----- 인스턴스 변수로 덮어쓰기 -----")
apple.origin = '미국'  # apple만 따로 가짐
apple.print_info()
print(apple.origin)     # 미국 (없던 apple.origin을 만들었기 때문에)
                        # -> 1. 인스턴스 변수를 뒤지고 2. 클래스 변수 뒤지고 없으면 새로 만듦
print(banana.origin)    # 필리핀

# 객체지향 프로그래밍 4대 패러다임

  • 캡슐화, 상속, 다형성, 추상화
  • 네 가지를 통해 재사용성, 확장성, 유지보수성 향상 가능

# 1. 캡슐화 encapsulation

  • 데이터(속성)와 메서드를 하나로 묶고, 외부에서 내부 구현에 직접 접근하지 못하도록 제한하여 객체를 보호
  • 접근 수준 구분 : public, _protected, __private
  • getter/setter, @property : 안전하게 값을 읽고 수정 가능
class Fruit:
    def __init__(self, name, price):
        self.name = name          # public
                                  # 클래스 밖에서 fruit.name = "사과" 이렇게 변경 가능
        self.__price = price      # private (캡슐화) -> 클래스 내에서만 접근 가능 (외부에서 접근 불가)
                                  # 클래스 밖에서 fruit.__price = 5000 이렇게 변경 불가능

    # getter
    def get_price(self):
        return self.__price

    # setter
    def set_price(self, price):
        if price > 0:
            self.__price = price
        else:
            print("가격은 0보다 커야 합니다.")

    def print_info(self):
        print(f"과일: {self.name}")
        print(f"가격: {self.__price}")


class Apple(Fruit):         # Apple 클래스는 Fruit 클래스를 상속
    def __init__(self, name, price, origin):
        super().__init__(name, price) # 굳이 여기서 다시 self.name = name을 할 필요가 없으니까 super().__init__ 사용
        self.origin = origin

    def print_origin(self):
        print(f"{self.name}의 원산지: {self.origin}")
        
# ========================================================

apple = Apple("사과", 3000, "한국")

apple.print_info()
apple.print_origin()

print("가격 조회:", apple.get_price())

print("----- 가격 수정 -----")
apple.set_price(3500)
print("수정된 가격:", apple.get_price())

print("----- 잘못된 값 입력 -----")
apple.set_price(-1000)  # 검증 실패

print("----- 외부 접근 시도 -----")
apple.__price = 10000   # 새로운 변수 생성 (실제 private 변경 아님)

print("getter로 확인:", apple.get_price())  # 여전히 3500
print("직접 접근:", apple.__price)         # 10000 (다른 변수)

👉 self.__price는 실제로 private 되는 것이 아니라 내부적으로 _Fruit__price로 바뀜

 

class Fruit:
    def __init__(self, name, price):
        self.name = name
        self.__price = price  # private

    # getter → 속성처럼 접근 가능
    # getter만 만들기는 가능
    @property
    def price(self):
        return self.__price

    # setter → 값 할당 시 자동 호출
	# getter를 만들지 않으면 setter를 만들 수 없음 (price.setter에서 price 때문에)
    @price.setter
    def price(self, value):
        if value > 0:
            self.__price = value
        else:
            print("가격은 0보다 커야 합니다.")

    def print_info(self):
        print(f"과일: {self.name}")
        print(f"가격: {self.price}")  # 함수가 아니라 속성처럼 사용


class Apple(Fruit):
    def __init__(self, name, price, origin):
        super().__init__(name, price)
        self.origin = origin

    def print_origin(self):
        print(f"{self.name}의 원산지: {self.origin}")
        
# ================================================================

apple = Apple("사과", 3000, "한국")

apple.print_info()
apple.print_origin()

print("가격 조회:", apple.price)  # getter 호출

print("----- 가격 수정 -----")
apple.price = 3500               # setter 호출
print("수정된 가격:", apple.price)

print("----- 잘못된 값 입력 -----")
apple.price = -1000              # 검증 실패

print("----- 외부 접근 시도 -----")
apple.__price = 10000            # 새로운 변수 생성

print("실제 가격:", apple.price)  # 여전히 3500
print("외부에서 만든 값:", apple.__price)

👉 @property는 getter/setter를 함수가 아닌 "속성처럼" 사용하게 해주는 Pythonic한 캡슐화 방식


# 2. 상속 Inheritance

  • 부모 클래스의 속성과 메서드를 자식 클래스가 물려 받아 재사용하고 확장
  • 공통 기능 중복 작성을 피할 수 있고 재사용성 높일 수 있음
  • 자식은 부모의 기능 그대로 사용하거나 필요한 부분을 추가 또는 오버라이딩(재정의) 가능
  • super()를 사용해 부모 클래스의 메서드 호출 가능
class Parent:
    pass

class Child(Parent):
    pass

 

1. 상속 구조

class Fruit:
    def __init__(self, name, quantity):
        self.name = name
        self.quantity = quantity

    def store(self, place):
        print(f'{self.name}를 {place}에 보관합니다')

    def sell(self, amount):
        print(f'{self.name}를 {amount}개 판매합니다')

# 자식(Apple)의 생성자가 없으면 부모(Fruit) 생성자
# Fruit 생성자의 매개변수를 전달해야 함 (안 넣으면 에러)
class Apple(Fruit):
    pass

apple = Apple('사과', 50)

apple.store('냉장고')
apple.sell(5)

 

2. 생성자 호출

# 👉 자식 클래스에 __init__이 없을 때 자동 호출
class Fruit:
    def __init__(self, name):
        print("Fruit 생성자 호출")
        self.name = name

class Apple(Fruit):
    pass

apple = Apple("사과")
# 👉 자식 클래스에 __init__을 직접 정의하면 부모 생성자 호출 안됨 (오버라이딩)
class Fruit:
    def __init__(self, name):
        print("Fruit 생성자 호출")
        self.name = name

class Apple(Fruit):
    def __init__(self):
        print("Apple 생성자 호출")
        # self.name = name  # 부모를 호출한 적이 없는데 self.name = name 코드가 살아있으면 자식에서 덮어쓰게 된다는 것이 말이 안됨

apple = Apple()

# Apple 생성자 호출

# print(apple.name) # AttributeError: 'Apple' object has no attribute 'name'
# Apple() 은 자식 생성자 사용 중이므로 매개변수를 전하면 에러 발생
# 👉 super() 함수는 현재 클래스의 부모 클래스를 참조하며, 부모 클래스의 생성자를 호출할 수 있습니다.
class Fruit:
    def __init__(self, name):
        print("Fruit 생성자 호출")
        self.name = name

class Apple(Fruit):
    def __init__(self, name):
        super().__init__(name)  # 부모 생성자 호출
        print("Apple 생성자 호출")

apple = Apple("사과")
print(apple.name)

# Fruit 생성자 호출
# Apple 생성자 호출
# 사과

 

3. 오버라이딩 overriding

  • 상속 관계에서 부모에 이미 정의된 메서드를 자식 클래스에서 동일한 이름으로 재정의하여 동작을 변경
  • 이를 통해 부모 기본 기능을 그대로 사용할수도 or 일부 또는 전체 수정하여 사용 가능
  • 별도의 키워드 없이 같은 메서드 이름 정의하면 자동 오버라이딩
  • 필요할 경우 super()로 부모의 메서드 함께 호출 가능
class Fruit:
    def __init__(self, name, quantity):
        self.name = name
        self.quantity = quantity

    def eat(self):
        print(f'{self.name}를 먹습니다')

    def store(self, place):
        print(f'{self.name}를 {place}에 보관합니다')

class Apple(Fruit):
    # 부모 클래스에 없는 확장된 메소드
    def wash(self):
        print(f'{self.name}를 깨끗이 씻습니다')

    # 오버라이딩 (부모 꺼 안쓰고 덮어씀)
    def eat(self):
        print(f'{self.name}를 아주 맛있게 먹습니다')

    # 부모 메서드 호출
    def superEat(self):
        super().eat()

# ============================================

apple = Apple('사과', 10)

apple.eat()        # 오버라이딩된 메서드
apple.store('냉장고')  # 부모 메서드
apple.wash()       # 자식 메서드

print("----- 부모 메서드 호출 -----")
apple.superEat()   # 부모 eat 호출

fruit = Fruit('과일', 20)
fruit.eat()
fruit.store('창고')
# fruit.wash()  (Fruit에는 없음)

 

4. 다중 상속

  • 클래스가 둘 이상의 부모로부터 상속 받는 기능
  • 다른 객체 지향 언어와 달리 다중 상속 지원
  • 다중 상속 사용 시 재사용성 향상 가능하지만 동시에 복잡성이 높아지기 때문에 주의
class Parent1:
    pass

class Parent2:
    pass

class Child(Parent1, Parent2):
    pass
class Fruit:
    def __init__(self, name, quantity):
        self.name = name
        self.quantity = quantity

    def eat(self):
        print(f'{self.name}를 먹습니다')

    def sleep(self, hour):
        print(f'{self.name}를 {hour}시간 동안 보관합니다')
    
    def superSleep(self, hour):
        super().sleep(hour)


class Storage:
    def __init__(self, name, quantity):
        self.name = name
        self.quantity = quantity

    def process(self, hour):
        print(f'{self.name}를 {hour}시간 동안 가공합니다')

    def sleep(self, hour):
        print(f'{self.name}를 {hour}시간 동안 저온 숙성합니다')

# 다중 상속
class ProcessedFruit(Fruit, Storage):
    pass
    
# =========================================================

fruit = ProcessedFruit('사과', 10)

fruit.eat()       # Fruit
fruit.process(2)  # Storage
fruit.sleep(8)    # MRO에 따라 Fruit의 sleep 실행 (쉽게 말하면 Fruit, Storage 순서)
fruit.superSleep(5)		# 맨 아래줄 설명에 의해 Storage가 Fruit의 부모 클래스가 됨

print(ProcessedFruit.mro())

# ProcessedFruit.mro() 실행 시 나오는 순서대로 상속받음 (최상위 부모클래스 object)
# ProcessedFruit -> Fruit -> Storage -> object

 

5. object 클래스

  • 모든 클래스의 최상위 부모 클래스(루트 클래스)
  • 사용자가 명시적으로 상속하지 않아도 자동 상속되는 기본 클래스
  • 객체가 가져야 할 최소한의 공통 기능 제공
    (__str__, __repr__, __eq__ 등 기본 매직 메서드의 기본 구현)
  • 즉, 모든 파이썬 객체는 내부적으로 object를 기반으로 동작
    새로운 클래스 정의 시에도 이 기본 기능들 사용 가능 

 

* "LEGB 규칙"이라는게 있구나
(지역변수가 전역변수보다 우선순위가 높다. 빌트인 함수는 사용하게 되면 덮어씌워진다. 두 가지 내용만 알고 있었음)

 

* 함수 정의 시  함수 객체가 heap에 올라가고 호출(call)되면 stack에 복사돼서 사용

 

* 후에 굉장히 많은 데이터 처리를 위해 콜백함수를 많이 사용하게 될 것

 

* 람다 함수는 메모리에 올라가지 않는건가?

 -> False. 람다 함수 실행 시 heap에 생성되고 참조가 사라지면 GC에 의해 즉시 메모리가 해제되어 효율적으로 사용이 가능하다는 의미

 

* PP의 단점을 보완한 것이 OOP

 

* 클래스 : 객체를 만들기 위한 설계도(빵틀)

    - 구성 요소 : 1. 속성 2. 메서드

    - 인스턴스 생성 시 메서드는 공용으로 사용

    - 인스턴스 메서드는 heap에 올라감 (속성은 따로 사용 -> 인스턴스 변수 부분 참고)

 

* @staticmethod 필요없지 않냐? -> 원래 함수 자체는 독립적으로 사용하는게 맞음 -> 근데 객체 생성 없이 그냥 필요한 함수가 있을수도 있다 (관점의 차이?)

 

* 캡슐화 : 메서드를 통해 속성을 변경하도록 하여 클래스 속성을 보호하고자 사용하는 것

    - getter : 값을 return할 때 사용

    - setter : 값을 변경할 때 사용

 

* 다중 상속 : C++에서는 개발 시 교통 정리를 잘 해야 하지만 Python에서는 교통 정리가 잘 되어 있음

 

[수업후기]

- 내용은 알지만 구체적인 용어를 몰랐던 것들에 대해 공부할 수 있었다. 예를 들어 LEGB규칙이나 클로저 공간 같은 것.

 

- 대학교 수업 중 (과목이름 기억이안남) 콜백 함수가 시험에 나왔는데 틀렸던 기억이 떠올랐다

 

- 람다 함수도 코테 공부할 때 자주 사용했던 아이라 반가웠다.

 

- 프로그래밍 방법론도 귀에 피나게 들었던 내용이다. 그만큼 중요하다는 거겠지?

 

- 클래스를 설명하는 건 어딜가도 똑같다. 빵틀을 그렇게나 좋아하셨던 교수님이 생각난다. 최초로 클래스를 공부했을 때를 떠올려보면 클래스와 객체, 인스턴스, 인스턴스 변수 등 한 번에 몰아치는 용어들을 이해하는 게 힘들었던 기억이 있다.  이 수업에서는 용어들에 대해 정확하게 짚고 설명해주셔서 좋다. (내가 비전공자였다면 정말 따라가기 힘들었을 것 같아ㅠ,ㅠ) 

 

- Spring의 Annotation과 Python의 Decorator가 비슷하게 생겨서 찾아보니 전혀 비슷하지 않은 내용이었다. 그냥 기호만 같은거였어..!

  Spring의 Annotation Python의 Decorator
핵심 개념 코드에 대한 메타 데이터 제공 및 설정 기본 함수나 클래스를 감싸는 동작 변경/확장
동작 시점 컴파일 타임 또는 런타임 런타임(호출시점에 동적으로 실행)
내부 구현 리플렉션을 통해 메타데이터 읽어 프록시/빈 생성 함수 안에 함수를 넣는 클로저 형태
주요 목적 DI/IoC 설정, 트랜잭션 등 프레임워크 수준의 처리 로깅, 권한 검사, 실행 시간 측정 등 함수 단위의 공통 로직

 

- 4대 패러다임도 Java 수강할 때 귀에 딱지 앉도록 들었던 내용이다.

 

- 어제보다 확실히 복잡한 내용을 해서 수강생들의 질문이 늘었다. 내일부터 블로그 작성이 아닌 과제를 내주신다는데 굉장히 걱정이 된다. 벌써부터 울고 싶어..!!!