Python_3 - jjin-choi/study_note GitHub Wiki

Β§ Meta Programming

https://github.com/imguru-mooc/python_intermediate/tree/master/3.%EB%A9%94%ED%83%80%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D

1. ν•¨μˆ˜ 감싸기 (λ°μ½”λ ˆμ΄ν„°)

  • ν•¨μˆ˜μ— 좔가적인 처리 (λ‘œκΉ…, 타이밍 λ“±)을 ν•˜λŠ” 레퍼 λ ˆμ΄μ–΄λ₯Ό λ„£κ³  싢은 경우

  • meta data : ν•¨μˆ˜μ˜ 뢀가적인 정보듀

    • ν•¨μˆ˜μ˜ 이름, argument, doc λ¬Έμžμ—΄, annotation (인자의 주석) λ“±
    • μ—¬λŸ¬ 쀄 주석 μ²˜λ¦¬λŠ” ''' ''' 을 μ΄μš©ν•˜λ©΄ 되고 이λ₯Ό doc λ¬Έμžμ—΄ 이라고 ν•œλ‹€.
  • λ‹¨μˆœνžˆ ν•¨μˆ˜μ— decorator λ₯Ό μ”Œμš°λ©΄ ν•¨μˆ˜μ˜ meta 정보가 μ‚¬λΌμ§€κ²Œ λœλ‹€.

    • 이λ₯Ό 막기 μœ„ν•΄ @wraps(func) 을 μΆ”κ°€ν•΄μ£Όμ—ˆλ‹€.
# ------------- # 
#   version 1   #
# ------------- #

import time
from functools import wraps

def timethis(func):
    # wrapper = wraps(func, wrapper) 둜 λ„˜μ–΄κ°„ 것과 κ°™λ‹€
    # 이 μ½”λ“œμ—μ„œλŠ” wraps(countdown)(wrapper) λ₯Ό λ„˜κ²¨μ£ΌλŠ” 것과 κ°™λ‹€. 
    # wraps(countdown) μ—μ„œ meta data λ₯Ό λ½‘μ•„μ„œ λ‹€μ‹œν•œλ²ˆ λ°μ½”λ ˆμ΄ν„°λ₯Ό μ”Œμš°λŠ” 것

    @wraps(func)     # μƒˆλ‘œ μΆ”κ°€λœ λΆ€λΆ„ (ν•¨μˆ˜ν˜• λ°μ½”λ ˆμ΄ν„°) 

    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end-start)
        return result
    return wrapper

if __name__ == '__main__':
    # μ•„λž˜ μ½”λ“œλŠ” 사싀 이런 μ˜λ―Έκ°€ λœλ‹€. 
    # countdown = timethis(countdown)
    # λ”°λΌμ„œ ν•¨μˆ˜μ— 인자둜 λ“€μ–΄κ°„ countdown κ³Ό ν•¨μˆ˜ return 으둜 값을 κ°–κ²Œ 된 countdown 은 μ „ν˜€ λ‹€λ₯Έ 쑴재

    @timethis               
    def countdown(n:int):
        while n > 0:
            n -= 1

    countdown(100000)
    countdown(10000000)
# ------------- # 
#   version 2   #
# ------------- #

import time
from functools import wraps

def timethis(func):
    '''
    Decorator that reports the execution time.
    '''
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end-start)
        return result
    return wrapper

if __name__ == '__main__':
    @timethis
    def countdown(n:int):
        '''
        Counts down
        '''
        while n > 0:
            n -= 1

    countdown(100000)
    print('Name:', countdown.__name__)
    print('Docstring:', repr(countdown.__doc__))
    print('Annotations:', countdown.__annotations__)

2. λ°μ½”λ ˆμ΄ν„° ν’€κΈ°

  • ν•¨μˆ˜μ— λ°μ½”λ ˆμ΄ν„°λ₯Ό μ μš©ν–ˆλŠ”λ°, 이λ₯Ό μ·¨μ†Œν•˜κ³  원본 ν•¨μˆ˜μ— μ ‘κ·Όν•˜κ³  μ‹Άλ‹€.
    • @wraps λ₯Ό μ‚¬μš©ν•΄μ„œ λ°μ½”λ ˆμ΄ν„°λ₯Ό μ˜¬λ°”λ₯΄κ²Œ κ΅¬ν˜„ν–ˆλ‹€κ³  κ°€μ •ν•˜λ©΄, μ›λ³Έν•¨μˆ˜μ—λŠ” wrapped μ†μ„±μœΌλ‘œ μ ‘κ·Όν•  수 μžˆλ‹€.
from functools import wraps

def decorator1(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print('Decorator 1')
        return func(*args, **kwargs)
    return wrapper

def decorator2(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print('Decorator 2')
        return func(*args, **kwargs)
    return wrapper

@decorator1
@decorator2
def add(x, y):
    return x + y

# add = decorator2(add)
# add = decorator1(add)
# ν•˜μ§€λ§Œ decorator1 λΆ€ν„° 호좜됨 

print(add(2,3))

print(add.__wrapped__(2,3))

# print(add.__wrapped__.__wrapped__(2,3))
# 두 번 decorator λ₯Ό λ²—κ²¨λ‚΄μ„œ 원본 ν•¨μˆ˜λ₯Ό μ ‘κ·Όν•  수 μžˆμ„κΉŒ? YES ! 
  • wrapped λŠ” decorator λ₯Ό μ”Œμš°λ©΄ 생기며 ν•œλ²ˆ ν‘ΈλŠ” 것과 κ°™λ‹€.

    • λ”°λΌμ„œ decorator1 이 ν’€λ €μ„œ κ·Έ μ•ˆμ— decorator2 λΆ€ν„° μˆ˜ν–‰λ˜κ²Œ λœλ‹€.
    • 단 wrapped 도 원본 ν•¨μˆ˜μ˜ ν•¨μˆ˜ 객체λ₯Ό μ €μž₯ν•˜κΈ° μœ„ν•΄μ„œ wraps(func) 이 ν•„μš”ν•˜λ‹€.
  • log level 에 λ”°λΌμ„œ λͺ¨λ“œμ— 따라 logλ₯Ό 남길 수 μžˆλ„λ‘

    • @logged(logging.DEBUG) μ˜λ―ΈλŠ”, add = logged(logging.DEBUG)(add) 와 λ™μΌν•˜λ‹€
    • log level, module name, ν•¨μˆ˜λͺ… 으둜 남길 수 μžˆλŠ” μ½”λ“œ
    • ν•¨μˆ˜ λ³„λ‘œ 단계λ₯Ό λ‹€λ₯΄κ²Œ ν•  수 있음
from functools import wraps
import logging

def logged(level, name=None, message=None):
    def decorate(func):
        logname = name if name else func.__module__
        log = logging.getLogger(logname)
        logmsg = message if message else func.__name__

        @wraps(func)
        def wrapper(*args, **kwargs):
            log.log(level, logmsg)
            return func(*args, **kwargs)
        return wrapper
    return decorate

@logged(logging.DEBUG)
def add(x, y):
    return x + y

@logged(logging.CRITICAL, 'example')
def spam():
    print('Spam!')

if __name__ == '__main__':
    import logging
    logging.basicConfig(level=logging.DEBUG)
    print(add(2,3))
    spam()

3. μ‚¬μš©μžκ°€ 쑰절 κ°€λŠ₯ν•œ 속성을 가진 λ°μ½”λ ˆμ΄ν„°

  • ν•¨μˆ˜λ₯Ό κ°μ‹ΈλŠ” λ°μ½”λ ˆμ΄ν„° ν•¨μˆ˜λ₯Ό μž‘μ„±ν•˜λŠ”λ°, μ‚¬μš©μžκ°€ μ‹€ν–‰ μ‹œκ°„μ— λ™μž‘μ„±μ„ λ³€κ²½ν•  수 μžˆλ„λ‘ 속성을 μ‘°μ ˆν•˜κ³  싢은 경우

    • 즉, log level 이 DEBUG level 둜 정해진 것을 λ™μ μœΌλ‘œ λ°”κΏ€ 수 μžˆλŠ”κ°€.
    • ν•¨μˆ˜ 이름 μ΄λ‚˜ λͺ¨λ“ˆ 이름 등을 λ™μ μœΌλ‘œ λ°”κΎΈκΈ°
  • nonlocal level 이라고 μ„€μ •ν•΄μ£Όλ©΄ ν•œλ²ˆ μ§€μ •λœ level 을 λ³€κ²½ ν•  수 있음.

    • @attach_wrapper μ—μ„œ set_level 을 wrapper ν•΄μ£ΌλŠ”λ°, set_level = attach_Wrapper(wrapper)(set_level)
  • partial ? 이 뭘까

    • ν•¨μˆ˜ 인자 쀑에 νŠΉμ • ν•˜λ‚˜λ₯Ό μ–΄λ–€ κ°’μœΌλ‘œ κ³ μ •ν•˜κ³  μ‹Άμ„λ•Œ μ‚¬μš©ν•œλ‹€.
    • μΈμžκ°€ μ—¬λŸ¬κ°œμ΄μ—¬λ„ μœ μ €κ°€ μ‚¬μš©ν•  λ•Œμ—λŠ” νŠΉμ • 인자만 μ‚¬μš©ν•  수 μžˆλŠ” κΈ°λŠ₯
    • attach_wrapper μ—μ„œμ˜ 첫번째 μΈμžλŠ” wrapper 이라고 κ³ μ •μ‹œν‚¨ 것.

5. λ°μ½”λ ˆμ΄ν„°λ₯Ό μ‚¬μš©ν•΄μ„œ ν•¨μˆ˜μ—μ„œ νƒ€μž… 확인

  • ν•¨μˆ˜μ˜ λ§€κ°œλ³€μˆ˜ νƒ€μž…μ„ κ°•μ œμ μœΌλ‘œ ν™•μΈν•˜λŠ” κΈ°λŠ₯

  • λͺ¨λ“  λ§€κ°œλ³€μˆ˜μ— λŒ€ν•΄ νƒ€μž…μ„ λͺ…μ‹œν•˜κ±°λ‚˜ ν˜Ήμ€ λΆ€λΆ„λ§Œ 지정할 수 μžˆμ–΄ λ°μ½”λ ˆμ΄ν„°κ°€ μ–΄λŠ 정도 μœ μ—°ν•¨

  • signature : ν•¨μˆ˜μ—μ„œ λ„˜κΈ΄ μ‹€μ œ 인자λ₯Ό λ½‘μ•„μ˜€λŠ” 것. κ·Έ νƒ€μž…μ— λŒ€ν•΄μ„œλŠ” λ½‘μ•„μ˜€μ§€ μ•ŠμŒ.

    • sig = signature(func) ν•œ ν›„ sig.bind_partial(...) 을 ν•˜κ²Œ 되면 typeκ³Ό valueλ₯Ό list둜 μ„œλ‘œ λ¬Άμ–΄μ€€λ‹€.
    • closure 에 μ˜ν•΄μ„œ μ›λž˜ λ„˜μ–΄μ™€μ•Ό ν•  type 이 μ €μž₯λ˜μ–΄ 있으면 type 을 체크할 수 있음

6. λ°μ½”λ ˆμ΄ν„°λ₯Ό 클래슀의 μΌλΆ€λ‘œ μ •μ˜

  • 클래슀 λ‚΄λΆ€ methodλŠ” 2μ’…λ₯˜.

    • instance method : 객체의 μ£Όμ†Œκ°€ λ„˜μ–΄κ°
    • class method : class method λΌλŠ” λ°μ½”λ ˆμ΄ν„°
    • wraps (원본 ν•¨μˆ˜μ˜ 데이터 μ €μž₯을 μœ„ν•΄μ„œ κ°€μž₯ λ¨Όμ € μ”Œμ›Œμ€€λ‹€.)
  • class Person : first_name = property() 둜 μ‚¬μš© κ°€λŠ₯

    • λ‚΄μž₯ λ°μ½”λ ˆμ΄ν„° @property λŠ” μ‹€μ œλ‘œ getter() setter() delete() λ©”μ†Œλ“œλ₯Ό 가진 클래슀이고 λ°μ½”λ ˆμ΄ν„°μ²˜λŸΌ λ™μž‘λœλ‹€.
  • 클래슀λ₯Ό 전체 λ°μ½”λ ˆμ΄ν„°λ‘œ μ‚¬μš© κ°€λŠ₯. ν•¨μˆ˜λ₯Ό λ°μ½”λ ˆμ΄ν„°λ‘œ 감싸고 μ‹Άμ§€λ§Œ κ·Έ κ²°κ³ΌλŠ” 호좜 κ°€λŠ₯ μΈμŠ€ν„΄μŠ€κ°€ λ˜λ„λ‘ ν•˜κ³  싢은 경우. 그리고 λ°μ½”λ ˆμ΄ν„°λ₯Ό 클래슀 μ •μ˜ λ‚΄λΆ€/μ™ΈλΆ€μ—μ„œ λͺ¨λ‘ μ‚¬μš©ν•˜κ³  μ‹Άλ‹€.

    • call κ³Ό get 을 μž¬μ •μ˜ ν•΄μ£Όμ–΄μ•Ό 함.
  • μ•„λž˜μ™€ 같은 μ˜ˆμ‹œμ—μ„œ 객체 add 에 κ΄„ν˜Έλ₯Ό λ„£μ–΄ 값을 λ„£μ–΄μ£Όλ©΄, 객체에 값을 λ„£κΈ° λ•Œλ¬Έμ— μ—λŸ¬κ°€ λ°œμƒν•œλ‹€. 사싀 이 것은 add.call() 와 λ™μΌν•˜κΈ° λ•Œλ¬Έμ— __call__을 μ •μ˜ν•΄μ£Όλ©΄ λ¬Έμ œκ°€ μ—†μŒ

@Profiled
def add(x, y):
   return x + y
# add = Profiled(Add)
# add(2, 3) -> add.__call__(2, 3)
  • λ˜ν•œ 클래슀의 멀버에 λ°μ½”λ ˆμ΄ν„°λ₯Ό μ”Œμ›Œμ£ΌλŠ” μƒν™©μ—μ„œλŠ” get 이 ν•„μš”ν•˜λ‹€.
    • 클래슀 객체 μ•ˆμ˜ 값을 가져와야 ν•˜κΈ° λ•Œλ¬Έμ—

def __get__(self, instance, cls):
   if instance is None:
      return self
   else:
      return types.MethodType(self, instance)

# instance κ°€ λ„˜μ–΄μ˜€λŠ” κ²½μš°μ™€ λ„˜μ–΄μ˜€μ§€ μ•ŠλŠ” κ²½μš°κ°€ 있음. 
# λ„˜μ–΄μ˜€μ§€ μ•ŠλŠ” κ²½μš°λŠ” λ°μ½”λ ˆμ΄ν„°μ˜ μ…€ν”„λ₯Ό λ„˜κΈ°κΈ° λ•Œλ¬Έμ—, instanceκ°€ λ„˜μ–΄κ°€μ§€ μ•ŠμŒ. 
...

class Spam:
   @Profiled
   def bar(self, x):
      print (self, x)

if __name__ = '__main__':
   s = Spam()
   s.bar(1) 
   s.bar(2)
   s.bar(3) 
   # s 클래슀의 μΈμŠ€ν„΄μŠ€ 멀버 λ‚΄λΆ€ ν•¨μˆ˜λ₯Ό κ°€μ Έμ˜€κΈ° μœ„ν•΄ get 도 ν•„μš”ν•˜κ³  call 도 ν•„μš”ν•˜λ‹€. 
   print ('ncalls:', Spam.bar.ncalls) 
   # 이 κ²½μš°κ°€ instance κ°€ None 인 κ²½μš°μ— ν•΄λ‹Ή. λ°μ½”λ ˆμ΄ν„° @Profiled 의 ncallss 에 μ ‘κ·Ό. 

7. ν΄λž˜μŠ€μ™€ static method 에 λ°μ½”λ ˆμ΄ν„° 적용

  • ν΄λž˜μŠ€λ‚˜ static method 에 λ°μ½”λ ˆμ΄ν„°λ₯Ό μ μš©ν•˜κ³  싢은 경우
    • λ°μ½”λ ˆμ΄ν„°λ₯Ό @classmethod λ˜λŠ” @staticmethod μ•ž (즉 μ•„λž˜)에 μ μš©ν•˜λ„λ‘ μ£Όμ˜ν•˜μž

8. μΈμŠ€ν„΄μŠ€ 생성 μ‘°μ ˆμ— λ©”νƒ€ν΄λž˜μŠ€ μ‚¬μš©

  • meta-class : μ–΄λ–€ ν΄λž˜μŠ€κ°€ type을 μƒμ†λ°›μœΌλ©΄, 그것을 meta-class 라고 ν•œλ‹€.
    • class λ₯Ό λ§Œλ“œλŠ” class
    • 일반적인 class μ—μ„œ call 을 μž¬μ •μ˜ ν•  λ•Œμ™€ meta-class μ—μ„œ μž¬μ •μ˜ ν•  λ•ŒλŠ” μ „ν˜€ λ‹€λ₯΄λ‹€.
    • class λ₯Ό λ§Œλ“€ λ•Œ λͺ¨λ“  κ·œμΉ™μ΄ meta-class μ•ˆμ— λ“€μ–΄κ°„λ‹€.
    • meta-class 의 call 은 class κ°€ μƒˆλ‘œμš΄ 객체λ₯Ό λ§Œλ“€ λ•Œ λ™μž‘ (s = Class())
    • λ”°λΌμ„œ 후킹이 κ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ—, 싱글톀, 캐싱 λ“± κΈ°λŠ₯ κ΅¬ν˜„μ„ μœ„ν•΄ μΈμŠ€ν„΄μŠ€κ°€ μƒμ„±λ˜λŠ” 방법을 λ³€κ²½ν•˜κ³  싢을 λ•Œ
    • 싱글톀 : 객체λ₯Ό ν•˜λ‚˜λ§Œ μ‘΄μž¬ν•˜λ„λ‘ !
class NoInstances(type):
    # μ•„λž˜λŠ” 클래슀λ₯Ό λͺ»λ§Œλ“€κ²Œ ν•˜κ³ 
    # 객체 없이 class method 와 static method 만 μ‚¬μš©ν•˜κ²Œ ν•˜κΈ° μœ„ν•œ μ½”λ“œ
    # class κ°€ 객체λ₯Ό μƒμ„±ν•˜λ €κ³  ν•˜λŠ” μ‹œμ μ— 호좜 
    def __call__(self, *args, **kwargs):
        raise TypeError("Can't instantiate directly")

class Spam(metaclass=NoInstances):
    @staticmethod
    def grok(x):
        print('Spam.grok')

if __name__ == '__main__':
    try:
        s = Spam()
    except TypeError as e:
        print(e)

    Spam.grok(42)
  • meta-class 의 init 은 class μžμ²΄κ°€ 생성될 λ•Œ 호좜되고 class λ‚΄λΆ€μ˜ init 은 객체가 μƒμ„±λ˜κ³  μ΄ˆκΈ°ν™” ν•  λ•Œ ν˜ΈμΆœλœλ‹€.
    • μˆœμ„œμƒμœΌλ‘œ 보면, class μžμ²΄κ°€ 생성될 λ•Œ meta-class 의 init
    • β†’ ν΄λž˜μŠ€κ°€ () λ₯Ό λ§Œλ‚¬μ„ λ•Œ meta-class 의 call
    • β†’ μΈμŠ€ν„΄μŠ€κ°€ λ‹€ λ§Œλ“€μ–΄μ§€κ³  μ΄ˆκΈ°ν™” 될 λ•Œ class λ‚΄λΆ€μ˜ init
    • meta-class λŠ” class 생성과 κ΄€λ ¨λ˜μ–΄ 있고, class λ‚΄λΆ€λŠ” μΈμŠ€ν„΄μŠ€ 생성과 관련이 있음
class Singleton(type):
    # class κ°€ 생성될 λ•Œ 호좜
    def __init__(self, *args, **kwargs):
        self.__instance = None
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        # class κ°€ μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜λ €κ³  ν•  () κ°€ μžˆμ„ λ•Œ 호좜 
        if self.__instance is None:
            self.__instance = super().__call__(*args, **kwargs)
            return self.__instance
        else:
            return self.__instance

class Spam(metaclass=Singleton):
    # class 의 객체가 λ‹€ λ§Œλ“€μ–΄μ§€κ³  κ·Έ μΈμŠ€ν„΄μŠ€κ°€ μ΄ˆκΈ°ν™” λ˜λŠ” μ‹œμ μ— 호좜 
    def __init__(self):
        print('Creating Spam')

if __name__ == '__main__':
    a = Spam()
    b = Spam()
    print(a is b)

10. 클래슀 속성 μ •μ˜ μˆœμ„œ μˆ˜μ§‘

  • meta class λ₯Ό 상속받은 μ–˜λ„ meta class κ°€ λœλ‹€.
    • meta class 의 init 은 class κ°€ 생성될 λ•Œ 생성. *__call__은 calling 을 ν•  λ•Œ 생성
    • init 은 μ΄ˆκΈ°ν™”, __new__λŠ” 객체 (클래슀) κ°€ λ©”λͺ¨λ¦¬μ— 생성될 λ•Œ.
    • 즉, new β†’ init
    • prepare : class 에 λŒ€ν•œ 정보, 상속 정보에 λŒ€ν•œ 것듀. 이게 __new__λ₯Ό 호좜
    • 즉, prepare β†’ new β†’ init (10. 03:41 λΆ€ν„° λ“£κΈ°)

μ°Έκ³ 

  • μ œλ„ˆλ ˆμ΄ν„° (generator) : iterator λ₯Ό μƒμ„±ν•΄μ£ΌλŠ” ν•¨μˆ˜
    • iterator λŠ” class 에 iter, next λ˜λŠ” getitem λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•΄μ•Ό ν•˜μ§€λ§Œ, generator λŠ” ν•¨μˆ˜ μ•ˆμ— yield ν‚€μ›Œλ“œλ§Œ μ‚¬μš©ν•˜λ©΄ 됨.
    • iterator λŠ” next method μ•ˆμ— 직접 return λ°˜ν™˜ν•΄μ•Ό ν•˜μ§€λ§Œ generator 은 yield μ§€μ •ν•œ 값이 next λ°˜ν™˜κ°’μœΌλ‘œ λ‚˜μ˜΄
    • iterator λŠ” StopIteration μ˜ˆμ™Έλ₯Ό 직접 λ°œμƒμ‹œν‚€μ§€λ§Œ generator λŠ” ν•¨μˆ˜ λκΉŒμ§€ λ„λ‹¬ν•˜λ©΄ StopIteration μ˜ˆμ™Έκ°€ μžλ™μœΌλ‘œ λ°œμƒ.
    • λ™μž‘μ€ iterator 와 동일
    • 참고둜 μ œλ„ˆλ ˆμ΄ν„° κ°μ²΄μ—μ„œ __iter__λ₯Ό ν˜ΈμΆœν•˜λ©΄ selfλ₯Ό λ°˜ν™˜ν•˜λ―€λ‘œ 같은 객체가 λ‚˜μ˜΄
      • (μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜ 호좜 > μ œλ„ˆλ ˆμ΄ν„° 객체 > __iter__λŠ” self λ°˜ν™˜ > μ œλ„ˆλ ˆμ΄ν„° 객체).
    • 그런데 generateλΌλŠ” ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•˜λ©΄ λ˜μ§€ μ™œ yield라고 이름을 μ§€μ—ˆμ„κΉŒ?
      • yieldλŠ” μƒμ‚°ν•˜λ‹€λΌλŠ” 뜻과 ν•¨κ»˜ μ–‘λ³΄ν•˜λ‹€λΌλŠ” λœ»λ„ 가지고 μžˆμ–΄, yieldλ₯Ό μ‚¬μš©ν•˜λ©΄ 값을 ν•¨μˆ˜ λ°”κΉ₯으둜 μ „λ‹¬ν•˜λ©΄μ„œ μ½”λ“œ 싀행을 ν•¨μˆ˜ λ°”κΉ₯에 양보.
      • λ”°λΌμ„œ yieldλŠ” ν˜„μž¬ ν•¨μˆ˜λ₯Ό μž μ‹œ μ€‘λ‹¨ν•˜κ³  ν•¨μˆ˜ λ°”κΉ₯의 μ½”λ“œκ°€ μ‹€ν–‰λ˜λ„λ‘ λ§Œλ“­λ‹ˆλ‹€.
    • λ©”μ„œλ“œ λͺ©λ‘μ€ dir(ν•¨μˆ˜μ΄λ¦„) 으둜 확인할 수 있음.
    • generator μ•ˆμ—μ„œ return ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜λ©΄ StopIteration μ˜ˆμ™Έ λ©”μ‹œμ§€λ‘œ return λ°˜ν™˜κ°’μ΄ 지정됨