Загружаем каталог…
Загружаем каталог…
Python_Day.6 패키지와 import, 예외 처리( try / except / else / finally , raise , 사용자 정의 예외), 클로저, 데코레이터(함수형/클래스형). Day.5에서 파일 하나를 모듈로 만들어 import 하는 것까지 했다. 오늘은 그걸 폴더 단위(패키지) 로 확장하는 것에서 시작해서, 프로그램이 죽지 않게 하는 예외 처리 , 그리고 함수를 값처럼 다루는 클로저와 데코레이터 까지 정리했다. 1. 패키지 파이썬 프로그램은 패키지로 나뉜 개별 모듈 들로 구성된다. 패키지는 모듈( .py )을 담은 폴더 이고, 폴더 안에 폴더(하위 패키지)를 둘 수 있다. 실습에서는 이런 구조를 만들었다. (폴더 구조 스크린샷은 Notion에서 읽을 수 없어서, 코드에 맞춰 구조를 다시 적었다.) project/ ├── main.py └── pro_test/ # 패키지 ├── __init__.py ├── test2/ # 하위 패키지 │ ├── __init__.py │ └── module1.py # mod1_test(), mod1_test2() └── test3/ # 하위 패키지 ├── __init__.py └── module2.py # mod2_test(), mod2_test2() 각 폴더의 __init__.py 는 "이 폴더는 패키지다"라는 표시다. (파이썬 3.3부터는 없어도 동작하지만, 패키지임을 분명히 하고 초기화 코드를 넣을 수 있어서 두는 편이 일반적이다.) import 하는 세 가지 방법 1) import 패키지.하위패키지.모듈 : 호출할 때도 전체 경로를 다 써야 한다. import pro_test.test2.module1 import pro_test.test3.module2 pro_test.test2.module1.mod1_test() pro_test.test3.module2.mod2_test() 2) from 패키지.하위패키지 import 모듈 : 모듈 이름만 쓰면 된다. as 로 별명도 붙일 수 있다. from pro_test.test2 import module1 module1.mod1_test() from pro_test.test3 import module2 as m2 m2.mod2_test() 3) from 패키지.하위패키지 import * : 패키지 안의 것을 한꺼번에 가져온다. from pro_test.test2 import * from pro_test.test3 import * module1.mod1_test() module2.mod2_test() 3번은 주의가 필요하다. 필기 코드에서는 위에서 이미 module1 을 import 해 둔 상태라 동작했다. import * 만 단독으로 쓰면 NameError: name 'module1' is not defined 가 난다. 패키지에서 * 로 가져올 모듈은 __init__.py 에 __all__ = ['module1'] 처럼 목록을 적어 줘야 한다. 이름 충돌 위험도 있어서 1번이나 2번을 권장한다. 상대 경로 import 필기 맨 위에 .. (부모 디렉토리), . (현재 디렉토리)라고 적어 둔 것이 상대 경로 import 표기다. 패키지 안의 모듈끼리 서로 가져올 때 쓴다. # pro_test/test3/module2.py from ..test2 import module1 # .. : 부모(pro_test) 아래의 test2 from . import helper # . : 현재 패키지(test3) def mod2_test2(): module1.mod1_test() helper.hello() . 은 현재 패키지, .. 은 한 단계 위 패키지를 뜻한다. 상대 경로 import는 패키지의 일부로 import 될 때만 동작한다. 그 파일을 python module2.py 처럼 직접 실행하면 에러가 난다. 직접 실행하는 파일은 앞 장에서 본 __name__ == "__main__" 이 되고, 패키지 소속이 아니기 때문이다. 2. 예외 처리 2-1. 자주 만나는 예외 에러가 나면 파이썬은 종류에 맞는 예외(Exception) 를 던진다. 각각 어떤 코드에서 나는지 실제로 확인해 보았다. 예외 코드 메시지 SyntaxError print('hi')) unmatched ')' NameError print(z) (정의 안 한 변수) name 'z' is not defined IndexError a=[1,2,3]; a[5] list index out of range ZeroDivisionError 4/0 division by zero KeyError {'a':1}['c'] 'c' ValueError [1,2,3].remove(5) , int('abc') list.remove(x): x not in list FileNotFoundError open('b.txt','r') No such file or directory: 'b.txt' TypeError [1,2] + 'python' can only concatenate list (not "str") to list TypeError 는 x + list(y) 처럼 자료형을 맞춰 주면 해결된다. SyntaxError 는 실행하기도 전에 코드를 읽는 단계에서 나기 때문에, 이 오류가 있는 줄 위쪽의 print 도 실행되지 않는다. 2-2. try / except / else / finally 예외가 나더라도 프로그램이 멈추지 않게 처리하는 문법이다. li = ['db', 'python', 'react'] try: # 예외가 날 수도 있는 코드 x = 'java' y = li.index(x) # 예외가 나면 바로 except로 넘어간다 print('try 블럭 수행') except Exception as e: # 예외가 났을 때 실행 print(e) # 'java' is not in list else: # 예외가 없을 때만 실행 print('else') finally: # 예외 여부와 상관없이 항상 실행 print('finally') 실행 결과는 이렇다. 'java' is not in list finally x 를 'db' 로 바꾸면 이렇게 된다. try 블럭 수행 else finally 블록 실행 시점 try 예외가 날 수 있는 코드. 예외가 나는 순간 나머지는 건너뛴다 except 예외가 발생했을 때 else 예외가 발생하지 않았을 때 finally 무조건 (파일 닫기 같은 마무리 작업에 쓴다) except ValueError: 처럼 특정 예외만 잡을 수도 있고, except Exception as e: 로 대부분의 예외 를 잡을 수도 있다. Exception 은 거의 모든 예외의 부모 클래스다. e 에 예외 내용이 들어 있어서 print(e) 로 메시지를 볼 수 있다. 필기의 앞부분 코드는 except ValueError: 로 잡고 있었다. list.index() 로 없는 값을 찾으면 ValueError 가 나기 때문이다. 예외끼리도 상속 관계가 있다. KeyError 와 IndexError 는 LookupError 의 자식이고, FileNotFoundError 는 OSError 의 자식이며, 전부 Exception 의 자손이다. 2-3. raise : 일부러 예외 발생시키기 def calc(msg, *args): result = 0 try: if msg == 'add': for i in args: result += i print(result) elif msg == 'mul': result = 1 for i in args: result *= i print(result) elif msg == 'avg': for i in args: result += i print(result / len(args)) else: raise Exception('잘못된 연산이다') # 일부러 예외 발생 except Exception as e: print(e) calc("add", 1, 2, 3) # 6 calc("mul", 1, 2, 3, 4) # 24 calc("avg", 10, 20, 30) # 20.0 calc("al", 10, 20, 30) # 잘못된 연산이다 calc("avg") # division by zero (인자가 없으면 len(args)가 0) raise 는 조건에 맞지 않을 때 직접 예외를 던지는 명령어다. 던져진 예외는 바깥의 except 가 받는다. *args 는 인자를 튜플로 받는다. (Day.3) 필기 코드에는 except 안에 return None 이 있었는데, 파이썬 함수는 return 이 없으면 어차피 None 을 반환하므로 빼도 같다. 마지막 줄처럼 인자 없이 avg 를 호출하면 ZeroDivisionError 가 나는데, except Exception 이 이것도 같이 잡아 준다. 2-4. 사용자 정의 예외 Exception 을 상속해서 내 예외 클래스 를 만들 수 있다. class MyError(Exception): def __str__(self): # 예외를 출력할 때의 문자열을 정한다 return '허용되지 않는 별명입니다.' def say_nick(nick): if nick == '바보': raise MyError() # 금지어면 예외 발생 print(nick) try: say_nick('천사') # 천사 say_nick('바보') # 여기서 예외 발생 except MyError as e: print(e) # 허용되지 않는 별명입니다. __str__ 은 print(객체) 를 했을 때 보일 문자열을 정하는 특수 메소드 다. 예외 객체 e 를 print(e) 하면 이 문자열이 나온다. 2-5. 사용자 정의 예외에서 헷갈린 부분 나이를 입력받아 0 이하면 예외를 내는 코드를 두 가지로 만들어 봤다. 첫 번째는 이렇다. class MyError(Exception): def __str__(self): return '숫자를 입력해주세요' try: age = int(input('나이를 입력해주세요')) if age <= 0: raise MyError('나이는 0 이상이어야 해') except Exception: raise MyError() else: print('입력완료') 이 코드는 -5 를 입력하든 abc 를 입력하든 둘 다 같은 숫자를 입력해주세요 에러 로 끝난다. 이유는 세 가지다. raise MyError('나이는 0 이상이어야 해') 로 넘긴 메시지는 쓰이지 않는다. __str__ 을 오버라이딩해서 항상 같은 문자열을 반환하기 때문이다. (메시지는 e.args 에는 들어 있지만 출력되지 않는다.) MyError 도 Exception 의 자식이라서, try 안에서 던진 MyError 를 바로 아래 except Exception 이 다시 잡고 , raise MyError() 로 새로 던진다. 결국 -5 를 입력해도 "숫자를 입력해주세요"가 나온다. except 안에서 다시 raise 했으니 처리되지 않은 예외가 되어 프로그램이 트레이스백과 함께 종료 되고, else 도 실행되지 않는다. 두 번째 방식은 메시지를 __init__ 에서 부모에게 넘긴다. class MyError(Exception): def __init__(self): super().__init__('0보다 커야해') # 부모(Exception)에 메시지 전달 try: age = int(input('나이를 입력해주세요')) if age <= 0: raise MyError() except ValueError as e: print('숫자가 아니야') else: print('입력완료') 이쪽은 abc 를 입력하면 숫자가 아니야 가 잘 나온다. 그런데 -5 를 입력하면 except ValueError 는 MyError 를 잡지 못하므로 MyError: 0보다 커야해 와 함께 프로그램이 종료된다. MyError 를 처리하는 except 가 없기 때문이다. 두 가지를 모두 처리하도록 정리하면 이렇게 된다. class AgeError(Exception): def __init__(self): super().__init__('나이는 0보다 커야 한다') try: age = int(input('나이를 입력해주세요')) if age <= 0: raise AgeError() except ValueError: print('숫자가 아니다') # abc 입력 except AgeError as e: print(e) # -5 입력 → 나이는 0보다 커야 한다 else: print('입력완료') # 5 입력 입력 출력 abc 숫자가 아니다 -5 나이는 0보다 커야 한다 5 입력완료 예외 종류마다 except 를 나눠 쓰면 상황별로 다른 메시지 를 줄 수 있다. 또 except Exception 을 쓸 때는 그 안에서 내가 던진 예외까지 같이 잡히는지 확인해야 한다. 사용자 정의 예외에서 메시지를 정하는 방법은 두 가지다. 방법 코드 __str__ 오버라이딩 출력 문자열을 고정한다. 던질 때 넘긴 메시지는 무시된다 __init__ 에서 super().__init__(메시지) 부모에게 메시지를 넘긴다 3. 클로저 클로저 는 함수 안에서 함수를 정의하고, 그 안쪽 함수를 결과로 반환 하는 구조다. 콜백 함수나 데코레이터에 쓰인다. def add(n1): def wrapper(n): # 내부 함수 (inner function) return n1 + n # 바깥 함수의 n1을 사용 return wrapper # 내부 함수를 반환 a1 = add(10) # n1=10을 저장한 채로 wrapper가 a1에 들어감 print(a1(10)) # 20 (wrapper(10) → 기억해 둔 10 + 10) a2 = add(20) # n1=20을 기억하는 또 다른 wrapper print(a2(10)) # 30 흐름을 풀어 보면 이렇다. add(10) 이 실행되면 n1 = 10 이 만들어진다. 안쪽 wrapper 함수가 만들어져서 반환 되고, a1 이 그 함수를 가리킨다. add 는 이미 끝났지만, wrapper 는 n1 = 10 을 기억하고 있다. 이것이 클로저의 특징이다. a1(10) 은 wrapper(10) 이므로 기억하던 10과 더해 20이 된다. add(10) 과 add(20) 이 서로 다른 n1 을 각자 기억하는 함수를 만든다는 점이 핵심이다. 기억해 둔 값은 a1.__closure__[0].cell_contents 로 확인할 수 있고, 값은 10 이다. 기억한 값을 바꾸고 싶을 때: nonlocal 내부 함수에서 바깥 함수의 변수를 읽는 것은 되지만, 값을 바꾸려면 nonlocal 을 선언 해야 한다. (Day.4의 global 과 같은 역할이고 범위가 바깥 함수다.) def counter(): c = 0 def inc(): nonlocal c # 바깥 함수의 c를 쓰겠다 c += 1 return c return inc f = counter() print(f(), f(), f()) # 1 2 3 호출할 때마다 c 가 유지되며 늘어난다. 전역 변수를 쓰지 않고도 상태를 함수에 숨겨 두는 방법이다. 4. 데코레이터 데코레이터 는 "장식하는 도구"로, 기존 함수의 코드를 건드리지 않고 기능을 확장 할 때 쓴다. 함수 위에 @이름 을 붙이는 문법이고, 안에서 클로저를 사용한다. 4-1. 클로저로 직접 만들어 보기 def trace(func): def wrapper(): print('시작') func() # func는 함수 자체를 받았으므로 뒤에 ()를 붙여 호출 print('끝') return wrapper # 함수를 반환 def hi(): print('hi') def hello(): print('hello') t1 = trace(hi) t1() # 시작 # hi # 끝 t2 = trace(hello) t2() # 시작 # hello # 끝 trace 는 함수를 인자로 받고, 함수를 반환 한다. 반환된 wrapper 가 원래 함수 앞뒤에 시작 / 끝 출력을 덧붙인다. 4-2. @ 문법 위의 t1 = trace(hi) 과정을 @ 로 줄일 수 있다. @trace # hi = trace(hi) 와 같다 def hi(): print('hi') @trace # hello = trace(hello) 와 같다 def hello(): print('hello') hi() # 시작 # hi # 끝 hello() # 시작 # hello # 끝 필기에는 " hi() 를 호출하면 hi = trace(hi) 를 자동으로 실행해준다"고 적어 뒀는데, 정확히는 함수를 정의하는 시점 에 hi = trace(hi) 가 한 번 실행된다. 그 뒤로 hi 라는 이름은 원래 함수가 아니라 wrapper 를 가리킨다. 그래서 hi() 를 호출하면 wrapper() 가 실행되는 것이다. 4-3. 인자와 반환값이 있는 함수에 쓰기 인자의 개수나 형태가 제각각인 함수에도 쓰려면 *args , **kwargs 로 받는다. def trace(func): def wrapper(*args, **kwargs): # *args : 위치 인자를 튜플로 받는다 # **kwargs: 키=값 인자를 딕셔너리로 받는다 result = func(*args, **kwargs) print(args, kwargs, result) return result # 원래 함수의 반환값을 그대로 돌려준다 return wrapper @trace def big(*args): return max(args) @trace def mina(**kwargs): return min(kwargs.values()) print(big(10, 20)) # (10, 20) {} 20 # 20 print(mina(x=20, y=30, z=40)) # () {'x': 20, 'y': 30, 'z': 40} 20 # 20 흐름은 이렇다. @trace 가 big = trace(big) 를 실행해서 big 이 wrapper 가 된다. big(10, 20) 은 wrapper(10, 20) 이 되어 args = (10, 20) 로 들어온다. func(*args, **kwargs) 로 원래 big 을 호출해 max(10, 20) 인 20을 얻는다. 인자와 결과를 출력하고, result 를 반환해서 호출한 곳에서도 20을 받는다. wrapper 에서 return result 를 빼면 원래 함수의 반환값이 사라져서 print(big(10, 20)) 이 None 이 된다. 4-4. 데코레이터의 부작용과 functools.wraps 데코레이터를 붙인 함수는 이름이 바뀐다. @trace def hi(): print('hi') print(hi.__name__) # wrapper (hi가 아니다) hi 가 실제로는 wrapper 이기 때문이다. 디버깅하거나 함수 이름을 쓸 때 헷갈릴 수 있어서, 실무에서는 functools.wraps 를 붙여 원래 함수의 이름과 설명을 유지한다. import functools def trace(func): @functools.wraps(func) # func의 이름 등 정보를 wrapper에 복사 def wrapper(*args, **kwargs): result = func(*args, **kwargs) print(args, kwargs, result) return result return wrapper @trace def big(*args): return max(args) print(big.__name__) # big 4-5. 클래스형 데코레이터와 __call__ 데코레이터는 함수만이 아니라 클래스 로도 만들 수 있다. class Tr: def __init__(self, func): self.func = func def __call__(self): print(self.func.__name__, '시작') self.func() print(self.func.__name__, '끝') @Tr # hi = Tr(hi) def hi(): print('hi') hi() # hi 시작 # hi # hi 끝 동작 순서를 따라가 보면 이렇다. @Tr 이 함수를 정의하는 시점에 hi = Tr(hi) 를 실행한다. Tr.__init__(self, func) 가 호출되고 원래 hi 함수가 self.func 에 저장된다. 이제 hi 라는 이름은 함수가 아니라 Tr 의 인스턴스 다. type(hi) 를 찍으면 <class '__main__.Tr'> 가 나온다. hi() 처럼 인스턴스에 괄호를 붙여 호출하면, 파이썬이 __call__ 을 자동으로 호출 한다. __call__ 안에서 self.func.__name__ 으로 이름( hi
То, что RADAR обнаружил и классифицировал для этой возможности. Это опубликованный источником текст, а не подтверждение, что предложение ещё действует.
Python_Day.6. Python_Day.6 패키지와 import, 예외 처리( try / except / else / finally , raise , 사용자 정의 예외), 클로저, 데코레이터(함수형/클래스형). Day.5에서 파일 하나를 모듈로 만들어 import 하는 것까지 했다. 오늘은 그걸 폴더 단위(패키지) 로 확장하는 것에서 시작해서, 프로그램이 죽지 않게 하는 예외 처리 , 그리고 함수를 값처럼 다루는 클로저와 데코레이터 까지 정리했다. 1. 패키지 파이썬 프로그램은 패키지로 나뉜 개별 모듈 들로 구성된다. 패키지는 모듈( .py )을 담은 폴더 이고, 폴더 안에 폴더(하위 패키지)를 둘 수 있다. 실습에서는 이런 구조를 만들었다. (폴더 구조 스크린샷은 Notion에서 읽을 수 없어서,…
Открыть источник