Lucian Log
Blog
Computer Science
Algorithm
DB
Network
OS
General
AI
Blockchain
Concurrency
ETC
Git
Infrastructure
AWS
Docker
Java-Ecosystem
JPA
Java
Spring
JavaScript-Ecosystem
JavaScript
Next.js
React
TypeScript
Python-Ecosystem
Django
FastAPI
Python
SQLAlchemy
Software Engineering
Architecture
Culture
Test
Home
Contact
Copyright © 2024 |
Yankos
Home
>
Python-Ecosystem
> Python
Now Loading ...
Python
Pyenv setting 방법 (VS Code)
Pyenv setting 방법 (VS Code) brew install pyenv code ~/.zshrc .zshrc 안에 export PYENV_ROOT="$HOME/.pyenv" export PATH="$PYENV_ROOT/bin:$PATH" eval "$(pyenv init --path)" eval "$(pyenv init -)" # 설치 가능한 Python 버전 $ pyenv install --list # 특정한 버전 Python 설치 $ pyenv install 3.9.0 # 특정한 버전 Python 삭제 $ pyenv uninstall 3.9.0 # 설치된 Python list $ pyenv versions # 해당 Python 버전을 기본으로 설정 $ pyenv global 3.9.0
Python-Ecosystem
· 2024-02-05
Empty 자료형의 type annotation에 관하여
Intro Mypy와 친하게(?) 지내다보면 문득 어떤 type annotation를 줘야할지 모호한 경우가 왕왕 발생합니다. (안 발생한다면 mypy랑 베프인 분들 :thumbsup:) 그 중 재밌었던 부분은 빈 리스트 []는 어떤 typing을 주어야할지에 대한 고민입니다. 보통 비어있다고 생각하면 typing 모듈의 Optional을 생각하게 되는데, 사실 약간 불편한 느낌이 있습니다. 예를 들어, Optional[str]은 Union[str, None]과 동일하며 그 의미는 “None을 허용한다”이므로, list[Optional[str]]는 리스트의 element로 str이 오거나 None이 올 수 있다는 말이 됩니다. 즉, [None]도 허용한다는 의미가 포함되게 됩니다. 그렇다면 [None]은 제외하고 순수하게 empty list []만 허용하고 싶을 때는 어떻게 해야 할까요? 결론만 이야기하면, list[str]으로 충분합니다. 굳이 Optional을 사용해 [None]의 경우까지 허용시킬 필요가 없습니다. 실제로 이것이 맞는지 PEP와 Mypy로 함께 확인해봅시다 :smiley: Empty list의 타입 기본적인 list 타입은 위와 같이 선언할 수 있습니다. list[str]는 str 타입의 element로 구성된 리스트를 허용한다는 의미죠. Mypy로 체킹해봐도 dogs: list[str] = ["Welsh Corgi", "Golden Retriever", "Bulldog"]가 문제없이 허용됩니다. []도 허용하고 싶을 때는 어떻게 해야할까요? list[Optional[str]]은 ["Welsh Corgi", "Golden Retriever", "Bulldog"], [None], [] 3가지 경우를 허용합니다. 보통 우리는 [None]에 대한 허용을 필요로 하지 않죠. 따라서, 통상적인 의미의 빈 리스트를 허용 type annotation은 단순히 list[str]을 사용하면 됩니다. list[str]은 ["Welsh Corgi", "Golden Retriever", "Bulldog"], [] 2가지 경우를 허용합니다. list[int], list[float], list[bool] 역시 동일하게 []를 허용합니다. Data type에 따른 분류 기본 자료형 기본 자료형의 경우는 Optional을 사용해주는 것이 본래 의도와 맞을 것입니다. Optional[int] example: 2, None Optional[float] example: 3.14, None Optional[bool] example: True, None 컬렉션 자료형 컬렉션 자료형의 경우, Optional 없이 본래의 타입을 사용하는 것이 의도에 맞을 것입니다. list[str] example: [Welsh Corgi, 'Poodle'], [] dict[int, str] example: {1: "Barking", 2: "Running"}, {} set[int] example: {1, 2}, set() 다만 튜플은 길이가 고정되는 자료형이기 때문에, 빈 튜플을 표현하거나 튜플의 길이를 가변적으로 표현하고 싶다면 다른 방법을 사용해야합니다. tuple[int] example: (4,) tuple[()] example: () (=empty tuple) Union[tuple[()], tuple[int]] example: (), (4,) tuple[int, ...] example: (), (4,), (3, 4, 5) (=Arbitrary-length homogeneous tuple) PEP 484 & Mypy docs 빈 자료형을 어떤 타입으로 표현해야 하는지만을 따로 설명한 챕터는 없습니다. 다만, 이에 대해 신빙성있게 명시된 부분들은 PEP 484 – Type Hints와 Mypy docs - Type inference and type annotations에서 직간접적으로 찾아볼 수 있습니다. PEP 484의 type comments 설명을 보면, empty list를 어떤 타입으로 명시할 수 있는지가 간접적으로 드러나 있습니다. PEP 484의 The typing Module 챕터에서는 empty tuple은 tuple[()], arbitrary-length homogeneous tuple은 tuple[int, ...]를 사용하라고 명확히 설명해주었네요. Mypy docs에서도 collection 자료형의 타입에 관하여 명시된 부분이 있습니다. 이에 따르면, empty list는 list[int], empty dict는 dict[str, int], empty set은 set[int] 등으로 표현 가능합니다. Outro Empty 자료형에 대해 온전히 설명하는 PEP가 있다면 좀 더 좋았을텐데라는 생각이 들지만, 한편으로는 여러 reference에서 이에 대한 증거들을 찾아가는 과정도 꽤 흥미로웠습니다. Empty 자료형은 type annotation을 조금 헷갈리게 할 수 있습니다. 하지만 충분히 직관적으로 타입을 표현할 수 있으니, 이를 염두해서 type annotation을 사용하면 좋을 것 같습니다 :) P.S. Tuple의 type annotation은 직관적인가…? 자료형에 특성에 따른 예외이니까 kindly하게 받아들여야겠다…! Reference PEP 484 - Type hinting #type-comments PEP 484 - Type hinting #the-typing-module Mypy docs - Explicit types for collections
Python-Ecosystem
· 2022-05-30
Python zoneinfo - UTC 시간대를 더욱 쉽게 적용합시다!
이전에 python에서 UTC 시간대를 적용할 때는 pytz 라이브러리가 주로 사용되었습니다. 특히 aware 타입과 naive 타입을 비교하기 어렵기 때문에, pytz를 사용해 datetime 객체를 aware 타입으로 바꾸고 비교하는 것은 매우 유용했습니다. (aware 타입은 timezone 정보가 포함된 datetime이고 naive 타입은 timezone 정보가 포함되지 않은 datetime입니다.) 그러나 pytz는 2018년 서울과 평양시간을 UTC+9 시간이 아닌 UTC+08:30으로 표현하는 버그, 실수를 유발할 수 있는 사용 방법 등 이슈도 공존했습니다. 이를 보완하기 위해, Python은 3.9 버전부터 표준 라이브러리로 zoneinfo 모듈을 제공합니다. 덕분에, 따로 pytz를 인스톨하지 않고도 datetime에 쉽게 원하는 시간대를 적용할 수 있습니다. Python official document - zoneinfo https://docs.python.org/ko/3/library/zoneinfo.html ZoneInfo 클래스 ZoneInfo(key: str) ZoneInfo는 key를 생성자의 인자로 받는 클래스입니다. 예를 들어, “America/New_York”, “Europe/London”를 key로 던지면, 해당 시간대 정보를 가지는 인스턴스를 생성합니다. 시간대 적용은 이 인스턴스를 활용합니다. 현재 시간에 ZoneInfo 적용하기 from zoneinfo import ZoneInfo from datetime import datetime dt = datetime.now(ZoneInfo('UTC')) # datetime.now(tz=ZoneInfo('UTC'))와 동일 print(dt) # 2022-01-09 11:05:40.133971+00:00 zoneinfo는 기존 datetime 객체에 그대로 적용할 수 있습니다. UTC 시간대를 적용한 현재 시간을 알고 싶다면, datetime.now(ZoneInfo('UTC'))을 사용합니다.(datetime.now(tz=ZoneInfo('UTC'))와 동일합니다.) 반환된 dt는 aware 타입 객체가 될 것입니다. dt = datetime.now(ZoneInfo('Asia/Seoul')) print(dt) # 2022-01-09 20:05:40.133971+09:00 서울의 시간대로 현재 시간을 알고 싶다면, ZoneInfo의 인자로 ‘Asia/Seoul’ key를 적용합니다. 임의의 datetime에 ZoneInfo 적용하기 from zoneinfo import ZoneInfo from datetime import datetime, timedelta dt = datetime(2020, 10, 31, 12, tzinfo=ZoneInfo("America/Los_Angeles")) print(dt) # 2020-10-31 12:00:00-07:00 원하는 시간에 ZoneInfo를 적용하고 싶다면, datetime의 tzinfo에 ZoneInfo 정보를 줍시다. dt_add = dt + timedelta(days=1) print(dt_add) # 2020-11-01 12:00:00-08:00 datetime끼리의 연산 역시 summer time을 고려해 알아서 계산됩니다. Windows와 tzdata zoneinfo는 Python의 표준 라이브러리에 포함되기 때문에, 따로 인스톨없이 사용할 수 있습니다. 다만, 윈도우의 경우 zoneinfo 모듈을 사용할 때 다음과 같은 에러가 발생할 수 있습니다. ModuleNotFoundError: No module named ‘tzdata’ zoneinfo는 기본적으로 시스템의 시간대 데이터를 사용합니다. 하지만, 윈도우는 시간대를 다루는 시스템이 다른 OS와 조금 달라서, zoneinfo와 호환되지 않는다고 합니다. (PEP 615) However, not all systems ship a publicly accessible time zone database — notably Windows uses a different system for managing time zones — and so if available zoneinfo falls back to an installable first-party package, tzdata, available on PyPI. [d] If no system zoneinfo files are found but tzdata is installed, the primary ZoneInfo constructor will use tzdata as the time zone source. - Sources for time zone data (PEP 615) 이 때는, CPython 핵심 개발자가 유지 보수하는 first-party 패키지인 tzdata를 인스톨합시다. (pip install tzdata) zoneinfo는 참고할 수 있는 시간대 데이터가 없을 시 자동으로 tzdata를 시간대 데이터로 사용하므로, 인스톨 시 문제가 해결됩니다. 개발 시 최대한 신뢰할 수 있는 라이브러리를 사용하고 이외의 라이브러리에 대한 의존성을 줄일 필요가 있습니다. 고마웠던 pytz지만, 가능하다면 표준 라이브러리에 포함된 zoneinfo 사용을 지향해봐야겠습니다. Reference Python 3.10 document - zoneinfo PEP 615 - Support for the IANA Time Zone Database in the Standard Library PYTHON 3.9에 등장한 상큼한 8가지 FEATURES 평양 및 서울의 timezone관련 pytz 이슈
Python-Ecosystem
· 2022-01-09
비동기 프로그래밍을 돕는 asyncio 라이브러리
asyncio(Asynchronous I/O) 파이썬은 인터프리터 언어의 특성과 함께 속도가 느린 언어로 알려져있습니다. 그렇기에 비동기로 처리해 속도를 높이는 방법은 파이썬의 단점을 극복하는 하나의 해답이 됩니다. 다만, 파이썬에는 멀티스레드에서 발생하는 복잡한 문제들을 막기 위한 GIL(Global Interpreter Lock)이 존재하고, 이로 인해 항상 한 번에 하나의 스레드만 작업을 수행할 수 있어 진정한 의미의 멀티스레딩은 실현되기 어렵습니다. GIL은 보다 복잡한 문제를 막기 위한 심플하고 효과적인 방법이지만, 파이썬의 한계점이자 파이썬이 태생적으로는 비동기 프로그래밍에 적합하지 않은 언어임을 보여주죠. 한계가 뚜렷함에도 파일 읽기 및 쓰기, Http 통신 대기와 같은 Blocking I/O 상황에서는 비동기 프로그래밍이 여전히 파이썬에서 위력을 발휘합니다. 이러한 상황의 비동기 프로그래밍을 좀 더 간편하게 하기 위해 나온 모듈이 asyncio입니다. 그리고 asyncio로 인한 변화 덕분에, 파이썬에서도 점점 비동기 프로그래밍 사용이 용이해지고 있습니다. asyncio는 비동기 프로그래밍을 위한 모듈로, async/await 구문을 사용해 CPU 작업과 I/O 작업을 병렬로 처리할 수 있도록 도와줍니다. asyncio 모듈은 파이썬 3.4에 새로이 추가되었고, 3.5 부터 async def와 await 구문이 지원되었습니다. 그래서 파이썬 3.4 미만에서는 비동기 프로그래밍을 @asyncio.coroutine 데코레이터와 yield from을 사용해 구현해야 합니다. 3.3의 경우 pip install asyncio로 모듈을 설치하고 데코레이터와 yield from을 사용하면 됩니다. 동기(synchronous) 처리 특정 작업이 끝나면 다음 작업을 처리하는 순차처리 방식입니다. (프로그램의 코드가 순차적으로 처리되는 방식의 프로그래밍을 말합니다.) 아래 코드처럼 main 함수의 코드들이 작성된 순서대로 처리되는 경우 동기적으로 처리되었다고 말합니다. 특정 작업을 멈출 때도 비동기 프로그래밍에서 사용하는 asyncio.sleep과 대비되게 time 모듈을 사용합니다. import time def main(): print('time') foo('text') print('finished') def foo(text): print(text) time.sleep(2) main() # 실행 결과 # time # text # finished 비동기(asynchronous) 처리 여러 작업을 처리하도록 예약한 뒤 작업이 끝나면 결과를 받는 방식입니다. (프로그램의 코드가 여러 프로세스 여러 스레드로 나뉘어 처리되는 방식을 말합니다.) 코드 아래에서 이어 설명하겠습니다. 비동기 함수, 네이티브 코루틴 코루틴은 필요에 따라 일시정지할 수 있는 함수를 말합니다. 코루틴은 다양한 언어에 존재하고 여러 형태로 구현될 수 있습니다. 특히, 파이썬에서는 제너레이터에 기반한 코루틴과 구분하기 위해, async def로 만든 코루틴을 네이티브 코루틴이라고 부릅니다. 이러한 코루틴 함수를 비동기 함수라고도 부르며, 네이티브 코루틴은 앞서 이야기한 것처럼 파이썬 3.5에서부터 등장합니다. import asyncio async def main(): # async def로 네이티브 코루틴을 만듦 print('Hello, world!') asyncio.run(main()) # main 코루틴 함수를 실행 # 실행 결과 # Hello, world! 간단한 네이티브 코루틴을 구현했습니다. 먼저 async def로 네이티브 코루틴을 만듭니다. 그리고 async def 함수 범위 바깥에서 코루틴 함수를 실행하기 위해, asyncio.run(코루틴 객체)을 사용합니다. 네이티브 코루틴 함수를 호출하면 코루틴 객체를 생성하므로, 이를 asyncio.run()에 넣어주면 됩니다. 이렇게 하면 비동기 함수의 실행이 완료되고 생각했던 출력 결과를 얻습니다. 하지만 아직 코드에는 비동기적인 느낌이 없습니다. await으로 네이티브 코루틴 실행하기 이번엔 조금 더 비동기적인 느낌을 내어 프로그램을 짜보겠습니다. 이를 위해, await이 필요합니다. await은 네이티브 코루틴 함수 내에서만 사용할 수 있으며, 두 가지 기능을 수행합니다. 첫 번째 기능은 코루틴 함수를 실행(execute)하는 것입니다. 원래 async def 구문에서는 await이 코루틴 함수를 실행하는 키워드입니다. 하지만 앞서 말했듯 await은 async def 함수 내부에서만 사용이 가능하기 때문에, 코루틴 함수 밖에서 코루틴 함수를 실행할 때는 앞에서 봤던 asyncio.run()을 사용합니다. 두 번째 기능은 await 키워드 의미 그대로 await에 지정된 코루틴 함수가 종료될 때까지 기다리는 것입니다. 실제로 await 뒤에 코루틴 객체, 퓨처 객체, 태스크 객체를 지정할 수 있으며, 해당 객체가 끝날 때까지 기다린 뒤 결과를 반환합니다. (3가지 객체는 코루틴과 관련된 객체들이며 보통 어웨이터블(awaitable) 객체로 불립니다. 여기서는 코루틴 함수를 호출하면 리턴되는 코루틴 객체와 이후 만들 태스크 객체만 다루겠습니다.) 용법은 다음과 같고 변수에 할당하지 않아도 되지만, 할당한다면 해당 코루틴 함수가 return하는 값이 담깁니다. 변수 = await 코루틴객체 변수 = await 퓨처객체 변수 = await 태스크객체 await을 사용해 네이티브 코루틴을 실행해보겠습니다. import asyncio async def main(): print('time') await foo('test') print('finished') async def foo(text): await asyncio.sleep(1) print(text) asyncio.run(main()) # 출력 결과 # time # test # finished 이 경우 ‘time’이 출력되고 1초 후 ‘ test’와 ‘finished’가 출력되면서 네이티브 코루틴이 잘 실행됨을 확인할 수 있습니다. 다만, 실제 비동기 프로그래밍이라면 프로그램의 코드가 여러 스레드로 동시에 작업을 수행하기 때문에, foo 함수에서 1초를 기다리는 동작은 수행하더라도, 실제로 1초도 되기전에 ‘time’, ‘test’, ‘finished’가 모두 출력되며 프로그램이 마무리될 것입니다. (foo 함수에서 1초 기다리는 코드 asyncio.sleep(1)이 완료되기 전에 main 함수가 먼저 종료되기 때문에 foo 함수 종료 이전에 프로그램 자체가 먼저 종료될 것입니다!) 그래서 실제 비동기 실행을 위해서는 task 객체를 사용해야 합니다. Task 객체를 생성해 비동기 실행하기 Task를 사용하면 실제로 비동기 실행을 할 수 있습니다. import asyncio async def main(): print('time') asyncio.create_task(foo('test')) print('finished') async def foo(text): asyncio.create_task(asyncio.sleep(1)) print(text) asyncio.run(main()) # 출력 결과 # time # finished # test await은 코루틴을 실행하는 역할과 해당 코루틴 함수가 종료될 때까지 기다리는 역할을 수행합니다. 그러나 await만으로는 여러 스레드에서 동시에 프로그램이 실행되는 비동기적인 실행이 되지 않습니다. 이러한 비동기적 실행을 위해서 task 객체를 사용합니다. asyncio.create_task(코루틴 객체)를 사용하면 해당 코루틴 객체에 대한 task 객체를 생성함과 동시에 해당 코루틴 함수를 비동기적으로 실행합니다. 즉, await을 사용하지 않아도 실제 비동기적으로 코루틴 함수를 실행합니다. 따라서, 위 코드는 앞서 이야기한 것처럼 ‘time’을 출력한 후 1초를 기다리지 않고 ‘finished’와 ‘test’가 출력됩니다. ‘finished’가 ‘test’보다 먼저 출력된 이유는 context switch에 의한 것으로 예상됩니다. 정확한 알고리즘은 알 수 없지만, 스레드가 서로 교차하다가 ‘finished’ 출력 스레드가 먼저 완료되고 그 다음 ‘test’ 출력 스레드가 완료되며 함수가 종료되었을 것입니다. 또한, asyncio.create_task(asyncio.sleep(1))에 해당하는 스레드는 아직 수행 중이겠지만, 1초가 지나기 이전에 main 함수가 종료되면서 프로그램은 종료됩니다. 만일 asyncio.create_task(asyncio.sleep(1)) 코드의 수행을 온전히 완료하고 프로그램을 종료하고 싶다면, 다음과 같이 원하는 위치에서 await으로 함수 종료를 기다리면 됩니다. import asyncio async def main(): print('time') await asyncio.create_task(foo('test')) print('finished') async def foo(text): await asyncio.create_task(asyncio.sleep(1)) print(text) asyncio.run(main()) # 출력 결과 # time # test # finished 이렇게 되면, 프로그램은 비동기적으로 실행했지만 원하는 위치에서 임의로 함수의 종료를 기다린 후 다음 동작을 실행하게끔 할 수 있습니다. 위 경우 비동기적으로 함수를 실행했음에도 ‘time’ 출력 후 1초 기다린 다음 ‘test’와 ‘finished’가 출력됨을 확인할 수 있습니다. 조금 더 심화된 비동기 예제 살펴보기 앞의 내용들을 적용하여 조금 더 심화된 비동기 코드를 살펴보겠습니다. import asyncio async def main(): print('time') asyncio.create_task(foo('test')) await asyncio.sleep(0.5) print('finished') await asyncio.sleep(1) async def foo(text): await asyncio.sleep(1) print(text) await asyncio.sleep(1) print(text) asyncio.run(main()) # 출력 결과 # time # finished # test 위 코드는 task 객체를 생성해 비동기적으로 foo 함수를 실행합니다. 코드 수행이 여러 스레드로 나뉘어 동시에 이뤄지므로, 위 코드의 경우 main 함수와 foo 함수가 동시에 병렬적으로 실행되는 상황입니다. 시간 단위로 살펴보면 다음과 같습니다. 약 0초: ‘time’이 출력되면서 main과 foo 함수가 분기됩니다. 약 0.5초: main 함수의 await asyncio.sleep(0.5) 코루틴이 종료되고 ‘finished’가 출력됩니다. 약 1초: foo 함수의 await asyncio.sleep(1) 코루틴이 종료되고 ‘test’가 출력됩니다. 약 1.5초: main 함수의 await asyncio.sleep(1) 코루틴이 종료되면서 main 함수 종료와 함께 프로그램이 종료됩니다. 따라서 foo 함수의 마지막 ‘test’는 출력되지 않습니다. Reference Python3.8 asyncio, async/await 기초 - 코루틴과 태스크 asyncio 사용하기 Python 3, asyncio와 놀아보기
Python-Ecosystem
· 2021-05-28
파이썬 데코레이터 (Decorator)
파이썬의 함수는 일급 시민이자 일급 객체 일급 객체(First-class object)란 다음과 같은 몇 가지 조건을 갖춤으로 인해서, 해당 객체를 사용할 때 다른 요소들과 아무런 차별이 없는 객체를 의미합니다. 다음은 Robin Popplestone이 정의한 일급 객체의 일반적인 조건입니다. 모든 일급 객체는 함수의 실질적인 매개변수가 될 수 있다. 모든 일급 객체는 함수의 반환값이 될 수 있다. 모든 일급 객체는 할당의 대상이 될 수 있다. (변수 대입) 모든 일급 객체는 비교 연산(==, equal)을 적용할 수 있다. 일급 객체는 자바스크립트에서 파생된 개념이지만 지금은 대다수 프로그래밍 언어에 적용되는 개념입니다. 파이썬에서는 모든 것이 객체이자 일급객체여서, 함수 역시 위 조건을 만족하는 일급 객체에 해당합니다. 데코레이터란? 데코레이터란 기존 함수를 수정하지 않은 상태에서 새로운 기능을 추가할 때 사용하는 장식자입니다. 함수 위에 @를 붙인 것들이 모두 데코레이터에 해당됩니다. def basic_latte(func): def wrapper(): print('Milk') func() print('Espresso') return wrapper def vanilla(): print('Vanilla Syrup') def caramel(): print('Caramel Syrup') vanilla_latte = basic_latte(vanilla) vanilla_latte() print() caramel_latte = basic_latte(caramel) caramel_latte() # 출력 결과 # Milk # Vanilla Syrup # Espresso # # Milk # Caramel Syrup # Espresso 데코레이터의 이해를 위해 다양한 시럽을 베이스로 라떼를 제조해보는 예제로 데코레이터의 기본 구조를 살펴보겠습니다. 위의 basic_latte 함수는 우유와 에스프레소를 추가해주는 함수입니다. 특이한 점은 함수를 인자로 받고 내부에서 새로 정의한 함수 wrapper를 리턴하는 부분인데, 이렇게 하면 기존 함수 func을 매개변수로 사용해 추가 기능을 자유롭게 덧입힐 수 있습니다. 위와 같은 경우 vanilla 함수, caramel 함수에 각각 에스프레소와 우유를 덧입혀 출력한 것이죠! 파이썬의 closure의 개념을 알고 있다면, 이 예제 역시 closure의 일종으로 이해할 수 있습니다. 이 같은 구현이 가능한 이유는 파이썬의 함수가 일급 객체이기 때문입니다. 함수를 인자로 받고 리턴하고 변수에 할당하는 것이 가능함으로 인해 앞으로 강력하게 사용될 데코레이터가 탄생할 수 있었던 것이죠. def basic_latte(func): def wrapper(): print('Milk') func() print('Espresso') return wrapper @basic_latte def vanilla(): print('Vanilla Syrup') @basic_latte def caramel(): print('Caramel Syrup') vanilla() print() caramel() # 출력 결과 # Milk # Vanilla Syrup # Espresso # # Milk # Caramel Syrup # Espresso 데코레이터를 사용하면 위에서 살펴본 라떼 제조를 간단히 실행할 수 있습니다. 단순히 원하는 함수 위에 @추가기능함수이름을 달아주면, 굳이 basic_latte(vanilla)를 하지 않고 vanilla()만 실행해도 원하는 결과를 확인할 수 있습니다. 만일 여러개의 데코레이터를 지정하고 싶다면 다음과 같이 호출하면 됩니다. def espresso(func): def wrapper(): func() print('Espresso') return wrapper def milk(func): def wrapper(): func() print('Milk') return wrapper @espresso @milk def vanilla(): print('Vanilla Syrup') vanilla() # 출력 결과 # Vanilla Syrup # Milk # Espresso 에스프레소와 우유를 각각 덧입혀 바닐라 라떼 제조에 성공했습니다! @를 쓰지 않았을 때의 코드 동작은 espresso(milk(vanilla))() 와 동일합니다. 데코레이터에서 매개변수와 반환값을 처리하기 이번에는 매개변수와 반환값을 처리하는 데코레이터를 만들어 보겠습니다. def make_latte(func): def wrapper(espresso, milk): latte = func(espresso, milk) print(f'{func.__name__}(espresso={espresso}ml, milk={milk}ml) -> latte={latte}ml') return latte return wrapper @make_latte def mix(espresso, milk): return espresso + milk print(mix(60, 200)) # 출력 결과 # mix(espresso=60ml, milk=200ml) -> latte=260ml # 260 데코레이터가 매개변수를 처리할 수 있게끔 만드려면, 안쪽 wrapper 함수를 mix와 똑같은 형태로 매개변수를 받을 수 있게 만들어줘야 합니다. (결국 wrapper 함수가 인자를 받아 실행될 것이기 때문이죠!) 그리고 wrapper 함수 안에서 추가하고 싶은 기능을 만들어 줍니다. 여기서는 mix 함수를 실행한 리턴값을 변수로 저장하고 라떼 레시피와 제조 과정을 출력했습니다. 마지막으로 mix 함수는 에스프레소와 우유의 용량을 합친 수를 리턴해야 하므로, wrapper 함수에서 mix 함수의 반환값을 리턴해주도록 합니다. 만일 이를 잊어버리면, mix 함수를 호출해도 리턴값이 나오지 않으므로 유의해야 합니다. 이로써 매개변수와 반환값을 잘 처리하는 라떼 제조 데코레이터 구현에 성공했습니다. 만일 가변 인수 함수에 기능을 추가하고 싶은 상황이라면 데코레이터 안쪽 wrapper 함수에 *arg, **kwarg를 사용해주면 됩니다. 이렇게 만든 가변 인수 데코레이터는 고정 인수를 사용하는 일반적인 함수에도 사용할 수 있습니다. 매개 변수가 있는 데코레이터 만들기 데코레이터의 또 하나 강력한 점은 인자를 받아 동적으로 적용되는 추가 기능을 덧입힐 수 있다는 것입니다. def make_variation(syrup_name): # 데코레이터의 인자를 추가하는 부분 def make_latte(func): # 실제 데코레이터 부분 def wrapper(espresso, milk): latte = func(espresso, milk) print(f'{func.__name__}(espresso={espresso}ml, milk={milk}ml) with {syrup_name} syrup') print(f'-> {syrup_name}_latte={latte}ml') return latte return wrapper return make_latte # 실제 데코레이터 함수 반환 @make_variation('green_tea') def mix(espresso, milk): return espresso + milk print(mix(60, 200)) # 출력 결과 # mix(espresso=60ml, milk=200ml) with green_tea syrup # -> green_tea_latte=260ml # 260 보통 기본 베이직 커피에 무언가를 더 가미해 다양한 맛을 낸 커피를 베리에이션(variation)이라고 하는데, 여기선 데코레이터의 인자로 시럽의 이름을 받아 기본 라떼의 베리에이션을 만들어 보겠습니다. 코드를 보면 기존에 만들었던 데코레이터와 큰 차이 없이, 단순히 데코레이터의 인자를 받을 함수를 하나 더 덧입혀 삼중으로 처리하고 wrapper 함수의 출력문을 조금 바꿨습니다. 그리고 mix 함수 위에는 새로 덧입힌 함수를 데코레이터로 사용하고 인자로 녹차시럽(green_tea)을 받았습니다. 이렇게 하면, 녹차시럽을 가미한 베리에이션으로 녹차 라떼가 완성됩니다. 데코레이터의 인자를 바닐라 시럽이나 카라멜 시럽으로 바꾸면 동적으로 다른 베리에이션을 만드는 것도 가능합니다. 여러 개의 데코레이터를 지정하다가 원래 함수의 이름이 나오지 않을 때 여러 베리에이션을 만들면 다음과 같이 원래 함수의 이름이 나오지 않을 수 있습니다. # 실제 동작: make_variation('green_tea')(make_variation('vanilla')(mix))(60, 200) @make_variation('green_tea') @make_variation('vanilla') def mix(espresso, milk): return espresso + milk print(mix(60, 200)) # 결과 출력 # mix(espresso=60ml, milk=200ml) with vanilla syrup # -> vanilla_latte=260ml # wrapper(espresso=60ml, milk=200ml) with green_tea syrup # -> green_tea_latte=260ml # 260 참고로 위 함수의 실제 동작은 make_variation('green_tea')(make_variation('vanilla')(mix))(60, 200)으로 실행됩니다. 이 때 원하지 않는 출력 결과로 wrapper 함수의 이름이 나타났는데, 이를 개선하려면 wrapper 함수 위에 functools 모듈의 wraps 데코레이터를 사용해야 합니다. import functools def make_variation(syrup_name): def make_latte(func): @functools.wraps(func) # @functools.wraps에 func을 인자로 넣은 뒤 wrapper 함수 위에 지정 def wrapper(espresso, milk): latte = func(espresso, milk) print(f'{func.__name__}(espresso={espresso}ml, milk={milk}ml) with {syrup_name} syrup') print(f'-> {syrup_name}_latte={latte}ml') return latte return wrapper return make_latte @make_variation('green_tea') @make_variation('vanilla') def mix(espresso, milk): return espresso + milk print(mix(60, 200)) # 결과 출력 # mix(espresso=60ml, milk=200ml) with vanilla syrup # -> vanilla_latte=260ml # mix(espresso=60ml, milk=200ml) with green_tea syrup # -> green_tea_latte=260ml # 260 @functools.wraps 데코레이터를 사용하면 출력 결과가 원하는대로 나오는 것을 확인할 수 있습니다. @functools.wraps 데코레이터는 원래 함수의 정보를 유지시켜 디버깅을 용이하게 합니다. 따라서 데코레이터를 만들 때 함께 사용하는 것이 유용합니다. 클래스로 데코레이터 만들기 기존에 함수로 만들던 데코레이터는 클래스로도 만들 수 있습니다. 다만, 클래스로 데코레이터를 만들 때는 인스턴스를 함수처럼 호출하게 도와주는 __call__ 매직 메서드를 사용해야 합니다. class basic_latte: def __init__(self, func): self.func = func def __call__(self): print('Milk') self.func() print('Espresso') @basic_latte def vanilla(): print('Vanilla Syrup') vanilla() # basic_latte(vanilla)() 형태로 동작해 인스턴스가 생성되고, ()로 인해 __call__ 메서드가 호출됨 # 출력 결과 # Milk # Vanilla Syrup # Espresso 이렇게 코드를 짜면 기존의 함수로 만든 데코레이터와 동일한 결과를 얻을 수 있습니다. 데코레이터로 인해 basic_latte(vanilla)가 먼저 동작해 basic_latte 클래스의 인스턴스가 생성되고 해당 인스턴스에 ()가 붙어 __call__ 메서드가 수행되어 추가로 구현한 기능이 동작하게 됩니다. 클래스로 만든 데코레이터로 매개변수와 반환값도 처리할 수 있습니다. class make_latte: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): latte = self.func(*args, **kwargs) print('{}(espresso={}ml, milk={}ml) -> latte={}ml'.format(self.func.__name__, *args, latte)) return latte @make_latte def mix(espresso, milk): return espresso + milk print(mix(60, 200)) # 출력 결과 # mix(espresso=60ml, milk=200ml) -> latte=260ml # 260 __call__ 메서드에 mix 함수가 받을 인자를 똑같이 받도록 만들고 mix 함수의 리턴 값을 __call__메서드에서 반환해주면, 기존의 함수 데코레이터와 동일한 결과를 얻는 데코레이터를 클래스로 만들 수 있습니다. 매개 변수가 있는 데코레이터도 클래스로 구현해보겠습니다. class make_variation: def __init__(self, syrup_name): self.syrup_name = syrup_name def __call__(self, func): def wrapper(*args, **kwargs): latte = func(*args, **kwargs) print('{}(espresso={}ml, milk={}ml) with {} syrup'.format(func.__name__ , *args, self.syrup_name)) print(f'-> {self.syrup_name}_latte={latte}ml') return latte return wrapper @make_variation('green_tea') def mix(espresso, milk): return espresso + milk print(mix(60, 200)) # 출력 결과 # mix(espresso=60ml, milk=200ml) with green_tea syrup # -> green_tea_latte=260ml # 260 __init__ 메서드에서 데코레이터의 인자를 초깃값으로 받으면서, 인스턴스 속성으로 저장합니다. 그리고 __call__ 메서드에서 함수를 인자로 받도록 하고, 메서드 내부에 wrapper 함수를 새로 만들어 호출할 함수와 똑같은 형태로 매개변수를 받을 수 있도록 만들어 줍니다. 추가할 기능 역시 wrapper 함수에 구현하고 __call__ 메서드가 wrapper 함수를 리턴하도록 합니다. 그리고 mix 함수의 반환 값을 wrapper 함수가 리턴하도록 만들면 인자를 받는 데코레이터 구현이 완료됩니다. 똑같이 녹차 라떼가 제조됨을 확인할 수 있죠! 데코레이터의 의의 이로써 파이썬에서 데코레이터를 만드는 다양한 형태와 방법을 살펴봤습니다. 클로저 개념에서 발전되어 등장한 데코레이터는 기존 함수를 변형하지 않고 새로운 기능을 추가하는 목적으로 사용하지만, 디버깅에서도 훌륭한 수단이 됩니다. 함수의 성능 측정이나 함수 실행 전 데이터 확인 같은 다양한 목적으로도 사용되므로, 데코레이터에 익숙해지는 것은 효과적인 프로그래밍에 큰 도움이 될 것입니다. Reference python의 함수 decorators 가이드 파이썬 코딩 도장 - 데코레이터 1급 객체(first-class object)란?
Python-Ecosystem
· 2021-05-18
pipenv로 가상환경 설정하기
가상환경이란? 가상 환경은 특정 버전 파이썬 설치와 여러 추가적인 패키지를 포함하는 독립된 디렉토리 트리입니다. 파이썬 공식 문서 설명: https://docs.python.org/ko/3/tutorial/venv.html 여러가지 프로젝트를 진행하다보면 프로젝트마다 라이브러리와 파이썬의 버전이 달라지는데, 이를 각각 관리하기 위해 필요합니다. 협업에 있어서도 동일한 파이썬 환경을 공유하는데 도움이 됩니다. pipenv란? 패키지를 프로젝트 단위로 관리할 수 있도록 도와주는 고급 패키지 관리 도구입니다. 프로젝트 별로 격리된 가상 환경(virtual environment)과 프로젝트 단위의 패키지 관리 매커니즘을 제공합니다. Request 모듈로 잘 알려진 Kenneth Reitz(케네스 레이츠)가 만들었습니다. pipenv의 주요 특징 파이썬 패키지 라이브러리를 관리하는 pip와 가상환경 구축을 지원하는 virtualenv를 동시에 사용할 수 있습니다. Pipfile을 찾으면서 자동으로 프로젝트의 흠을 찾아줍니다. Pipfile과 Pipfile.lock을 통해 자동으로 최신 업데이트할 모듈 및 패키지와 버전을 고정할 모듈 및 패키지를 분류해 관리합니다. 패키지를 설치/삭제하면, 자동으로 Pipfile에서 추가/삭제합니다. 가상환경을 생성할 때, 필요한 python도 자동으로 설치합니다. pipenv 설치 과정 (Window) 내 필요에 맞는 로컬의 파이썬 버전을 업데이트합니다. pip 버전 확인하기 pip --version 이 때 나오는 파이썬 버전이 python3인지(구체적으로는 내 필요에 맞는 파이썬 버전인지) 확인하고 진행합니다. pipenv 설치하기 pip install --user pipenv pipenv 환경변수 설정하기 pip install --user pipenv 코드 실행하면 어떤 주소를 환경변수에 추가하라는 메시지가 나오므로, 그대로 수행합니다. 윈도우 시작메뉴 - 설정 - 검색 - ‘시스템 환경 변수 편집’ - 환경 변수 - ‘<username>에 대한 사용자 변수’ 카테고리의 Path를 누르고 편집 버튼 누르기 - 새로만들기 - 주소 입력 입력하는 주소는 아래의 2개와 같은 모습일 것입니다. (환경 변수를 추가하지 않은 상태에서 pipenv를 cmd에 입력하면 다음 주소를 환경변수에 추가하라는 메시지가 뜹니다.) C:\Users\<username>\AppData\Roaming\Python\Python38\Scripts C:\Users\<username>\AppData\Roaming\Python\Python38\site-packages 터미널을 종료시키고 다시 실행합니다. pipenv --version 코드로 버전을 확인하며, pipenv가 무사히 다운로드되었는지 체크합니다. pipenv 사용법 원하는 파이썬 버전으로 가상환경 만들기 pipenv --python 3.7.5 프로젝트를 진행할 디렉토리에서 실행할 것을 유의합니다! 가상환경 활성화하기 pipenv shell 가상 환경을 생성한 디렉토리에서 실행합니다. 특정한 패키지 다운로드 하기 pipenv install <패키지이름> - 최신 버전으로 다운로드 pipenv install <패키지이름>==<버전> - 지정된 버전으로 다운로드 다운로드 후, Pipfile.lock 파일이 생성되거나 수정됩니다. 해당 프로젝트의 디렉토리에서 실행합니다. (가상환경은 활성화되어도 안되어도 괜찮습니다.) 프로젝트에 사용되는 모든 패키지 다운 받기 pipenv install Pipfile과 Pipfile.lock 파일이 있으면, 여기에 명시되어 있는 모든 패키지를 지정된 버전대로 한 번에 다운 받습니다. 가상환경 비활성화하기 exit 가상환경 삭제하기 pipenv --rm 해당 가상환경이 활성화 되어있는 상태에서 실행합니다. 해당 프로젝트에서 사용하는 가상환경의 실제 위치 확인하기 pipenv --venv 해당 프로젝트의 디렉토리에서 실행합니다. (가상환경은 활성화되어도 안되어도 괜찮습니다.) 해당 프로젝트에서 사용하는 파이썬 인터프리터의 실제 위치 확인하기 pipenv --py 해당 프로젝트의 디렉토리에서 실행합니다. (가상환경은 활성화되어도 안되어도 괜찮습니다.) Pipfile과 Pipfile.lock Pipfile: 해당 프로젝트에서 1차적으로 필요한 패키지들만 포함합니다. 개발자는 새로운 패키지를 추가하거나 삭제할 때, Pipfile만 편집하면 됩니다. Pipfile.lock: 각각의 하위 패키지가 요구하는 패키지를 전부 포함합니다. 프로그램의 정상 동작을 보장하는 만큼의 상세한 패키지 정보는 Pipfile.lock 파일이 자동으로 관리합니다. Reference Pipenv 로 파이썬 가상환경 설정 pipenv 란 무엇인가 pipenv로 패키지 관리하기 좌충우돌 pipenv 도입기
Python-Ecosystem
· 2021-05-13
파이썬 클래스 개념 조각 모음
클래스(Class)를 사용하는 이유 관련 있는 데이터를 묶기 위해 배열이, 데이터 묶음 요소마다 의미를 부여하기 위해 딕셔너리가, 의미를 확장해 다양한 정보와 동작들을 한데 묶어 표현하기 위해 클래스가 탄생했다. 보안상의 이슈를 다루기 위해 코드들을 바깥과 분리하여 감싸는 encapsulation 기능이 필요했다. __main__을 사용하는 이유 C, C++ 같은 언어의 main 함수 영향을 받았다. 프로그램의 중심이 되는 코드들을 한 곳에 정리하기 위한 관리상 요인이 작용했다. (덕분에 프로그래밍의 시작점 파악이 용이) 속성(Attribute)의 종류 인스턴스 속성 인스턴스를 통해 접근할 수 있는 속성 (클래스 바깥에서는 인스턴스.속성, 클래스 내부에서는 self.속성으로 접근) __init__ 메서드 안에 정의한 속성 인스턴스 별로 독립되어 있는 속성이며, 각 인스턴스가 값을 따로 저장해야 할 때 사용 인스턴스를 생성한 후에도 자유롭게 속성을 추가할 수 있음 인스턴스.속성 = something (방법 1) 클래스 내 메서드에 속성을 정의하고, 인스턴스 생성 후 호출 (방법 2) __slots__ 메서드로 특정 속성만 추가를 허용하도록 지정 가능 __slots__ = ['속성이름1, '속성이름2'] (속성 이름은 문자열로 지정) >>> class Person: ... __slots__ = ['name', 'age'] # name, age만 허용(다른 속성은 생성 제한) ... >>> maria = Person() >>> maria.name = '마리아' # 허용된 속성 >>> maria.age = 20 # 허용된 속성 >>> maria.address = '서울시 서초구 반포동' # 허용되지 않은 속성은 추가할 때 에러가 발생함 Traceback (most recent call last): File "<pyshell#32>", line 1, in <module> maria.address = '서울시 서초구 반포동' AttributeError: 'Person' object has no attribute 'address' 클래스 속성 클래스에 바로 만든 속성 클래스 내부, 클래스 바깥 모두에서 접근 가능하다. (언더스코어 2개를 사용해 비공개 속성으로도 만들 수 있음) 모든 인스턴스가 공유하는 속성이며, 인스턴스 전체가 사용해야 하는 값을 저장할 때 사용 class Person: bag = [] def put_bag(self, stuff): Person.bag.append(stuff) # self.bag.append(stuff)라고 써도 되지만, 클래스 이름을 쓰는 것이 명확 james = Person() james.put_bag('책') maria = Person() maria.put_bag('열쇠') print(james.bag) # ['책', '열쇠'] print(maria.bag) # ['책', '열쇠'] 속성과 메서드 이름을 찾는 순서 파이썬에서 속성, 메서드 이름을 찾을 때, 인스턴스, 클래스 순으로 찾는다. 위 예에서도 마치 인스턴스 속성을 사용한 것 같지만, 인스턴스 속성이 없으면 클래스 속성을 찾게 되므로 실제로 클래스 속성을 리턴한 것이다. 인스턴스나 클래스에서 __dict__ 속성을 출력해보면 현재 인스턴스와 클래스의 속성을 딕셔너리로 확인할 수 있다. >>> james.__dict__ {} >>> Person.__dict__ mappingproxy({'__module__': '__main__', 'bag': ['책', '열쇠'], 'put_bag': <function Person.put_bag at 0x028A32B8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}) 메서드(Method)의 종류 인스턴스 메서드 인스턴스를 통해 접근할 수 있는 메서드 대부분의 일반적인 메서드에 해당되며 첫 번째 파라미터로 self를 지정하는 메서드 (self는 instance 그 자체를 받음) 정적 메서드 인스턴스를 통하지 않고 클래스에서 바로 호출 가능 메서드 위에 @staticmethod를 붙이고 파라미터로 self를 지정하지 않는 메서드 self를 받지 않기 때문에 인스턴스 속성에 접근할 수 없음 그래서 보통 인스턴스 속성, 인스턴스 메서드가 필요없는 메서드, 인스턴스의 상태를 변화시키지 않는 순수함수를 만들 때 사용 class Calc: @staticmethod def add(a, b): print(a + b) @staticmethod def mul(a, b): print(a * b) Calc.add(10, 20) # 클래스에서 바로 메서드 호출 / 30 Calc.mul(10, 20) # 클래스에서 바로 메서드 호출 / 200 클래스 메서드 인스턴스를 통하지 않고 클래스에서 바로 호출 가능 메서드 위에 @classmethod를 붙이고 첫번째 파라미터로 cls를 지정하는 메서드 (cls는 class 그 자체를 받음) cls를 받기 때문에 클래스 속성, 클래스 메서드에 접근할 수 있음 메서드 안에서 클래스 속성, 클래스 메서드에 접근하거나 메서드 안에서 현재 클래스의 인스턴스를 만들 때 사용 class Person: count = 0 # 클래스 속성 def __init__(self): Person.count += 1 # 인스턴스가 만들어질 때 # 클래스 속성 count에 1을 더함 @classmethod def print_count(cls): print('{0}명 생성되었습니다.'.format(cls.count)) # cls로 클래스 속성에 접근 james = Person() maria = Person() Person.print_count() # 2명 생성되었습니다. 비공개 속성과 비공개 메서드 비공개 속성 (Private Attribute) 클래스 바깥에서는 접근할 수 없고 클래스 안에서만 사용할 수 있는 속성 클래스 바깥에 드러내고 싶지 않은 값에 사용한다. __속성으로 사용 class Person: def __init__(self, name, age, address, wallet): self.name = name self.age = age self.address = address self.__wallet = wallet # 변수 앞에 __를 붙여서 비공개 속성으로 만듦 maria = Person('마리아', 20, '서울시 서초구 반포동', 10000) maria.__wallet -= 10000 # 클래스 바깥에서 비공개 속성에 접근하면 에러가 발생함 클래스 내 메서드에서는 접근 가능 class Person: def __init__(self, name, age, address, wallet): self.name = name self.age = age self.address = address self.__wallet = wallet # 변수 앞에 __를 붙여서 비공개 속성으로 만듦 def pay(self, amount): self.__wallet -= amount # 비공개 속성은 클래스 안의 메서드에서만 접근할 수 있음 print('이제 {0}원 남았네요.'.format(self.__wallet)) maria = Person('마리아', 20, '서울시 서초구 반포동', 10000) maria.pay(3000) 비공개 메서드 (Private Method) 클래스 바깥에서는 접근할 수 없고 클래스 안에서만 사용할 수 있는 메서드 클래스 바깥에 드러내고 싶지 않고 보통 내부에서만 호출되어야 할 때 사용한다. __메서드로 사용 class Person: def __greeting(self): print('Hello') def hello(self): self.__greeting() # 클래스 안에서는 비공개 메서드를 호출할 수 있음 james = Person() james.__greeting() # 에러: 클래스 바깥에서는 비공개 메서드를 호출할 수 없음 파이썬 접근 제어 다른 언어와 달리 파이썬은 접근제어자 키워드가 따로 존재하지 않기 때문에 네이밍(naming)을 통해 접근 제어를 수행한다. 다만, 파이썬에서는 네이밍을 사용해 접근을 제어해도 완벽하게 차단할 수는 없다. public, protected, private은 상황별로 다음과 같은 양상을 보인다. public 언더스코어(_)없이 시작하는 속성, 메서드 어디서나 접근 가능 protected 언더스코어 1개로 시작하는 속성, 메서드 어디서나 접근 가능하지만, 암묵적 규칙에 의해 해당 클래스 내부와 파생 클래스에서만 접근해야 함 (파이썬은 protect 기능이 X) private 언더스코어 2개로 시작하는 속성, 메서드 해당 클래스 내부에서만 접근 가능 주요 Dunder Method (=Magic method) __repr__ 해당 class의 string representation을 설정 객체를 출력하면 미리 설정된 사용자가 이해할 수 있는 문자열을 반환 self 파라미터 하나만 받고, 반드시 문자열을 리턴해야 한다. class Employee(): def __init__(self, name): self.name = name def __repr__(self): return self.name argus = Employee("Argus Filch") print(argus) # prints "Argus Filch" __add__ + 기호에 대응하는 메서드 더하는 메서드로서 self 파라미터와 여기에 더할 인자 하나를 받는다. class Color: def __init__(self, red, green, blue): self.red = red self.green = green self.blue = blue def __repr__(self): return "Color with RGB = ({red}, {green}, {blue})".format(red=self.red, green=self.green, blue=self.blue) def __add__(self, other): """ Adds two RGB colors together Maximum value is 255 """ new_red = min(self.red + other.red, 255) new_green = min(self.green + other.green, 255) new_blue = min(self.blue + other.blue, 255) return Color(new_red, new_green, new_blue) red = Color(255, 0, 0) green = Color(0, 255, 0) blue = Color(0, 0, 255) # Color with RGB: (255, 0, 255) magenta = red + blue # Color with RGB: (0, 255, 255) cyan = green + blue # Color with RGB: (255, 255, 0) yellow = red + green # Color with RGB: (255, 255, 255) white = red + green + blue __len__ len() 함수를 호출했을 때의 결과 값을 임의로 설정해 리턴할 수 있는 메서드 __iter__ iterator 객체를 반환해 반복가능한 객체로 만들어 주는 메서드 __contains__ 멤버 연산자 in을 사용할 수 있게 해주는 메서드 클래스 관련 메서드 특정 클래스의 인스턴스인지 확인하기 isinstance(인스턴스, 클래스) True, False 반환 해당 객체가 특정 속성을 가지고 있는지 여부 확인하기 hasattr(객체, '속성') True, False 반환 hasattr(attributeless, "fake_attribute") # returns False 해당 객체에서 특정 속성의 값을 가져오기 getattr(객체, '속성', default) 속성이 있으면 속성의 값 반환, 없으면 디폴트 값 반환 getattr(attributeless, "other_fake_attribute", 800) # returns 800, the default value 특정 클래스 A가 클래스 B의 subclass인지 확인하기 issubclass(클래스 A, 클래스 B) True, False 반환 Reference 파이썬 코딩 도장 Codecademy - learning python 3 private, proteted, public 의 차이 인스턴스 메소드의 종류와 용법 (Instance methods): Public, Protected, Private 접근제어자 (Access Modifiers)
Python-Ecosystem
· 2021-05-10
순간 놓치기 쉬운 파이썬 개념들 정리
2진수, 8진수, 16진수로 정수 표현하기 >>> 0b110 # 2진수 6 >>> 0o10 # 8진수 8 >>> 0xF # 16진수 15 보다 정교한 계산으로 부동소수점 오류를 피하는 자료형 Decimal from decimal import Decimal cost_of_gum = Deciaml('0.10') cost_of_gumdrop = Decimal('0.35') cost_of_transaction = cost_of_gum + cost_of_gumdrop print(cost_of_transaction) # Returns 0.45 instead of 0.44999999999999996 빈 변수 만들기 >>> x = None # 다른 언어의 null 값 >>> print(x) None del 키워드가 사용되는 경우 변수 삭제, 리스트 요소 삭제, 딕셔너리 요소 삭제 언더스코어 변수( _ ) 파이썬 셸에서 코드를 실행했을 때 결과는 _(밑줄 문자) 변수에 저장됩니다. 따라서 _를 사용하면 직전에 실행된 결과를 다시 가져올 수 있습니다. 단락 평가(short-circuit evalution) 단락 평가란 첫 번째 값만으로 결과가 확실할 때 두 번째 값은 확인(평가)하지 않는 방법을 말합니다. 논리 연산에서 단락 평가는 중요합니다. 예를 들어, Fasle and True는 and 앞이 False이기 때문에 뒷 부분을 고려할 필요없이 결과가 False가 됩니다. 실제 연산에서도 False and True는 단락 평가를 진행해 앞 부분만 확인하여 결과를 리턴합니다. 따라서, 복잡한 논리 연산일수록, 전체 결과를 빠르게 판단할 수 있는 식이 있다면 최대한 앞으로 빼서 효율적으로 연산이 동작하게끔 작성해야 합니다. 또한, 파이썬에서 논리 연산자는 마지막으로 단락 평가를 실시한 값을 그대로 반환하는 점을 유의해야 합니다. 논리 연산자는 무조건 불을 반환하지 않습니다. True and 'Welsh Corgi' # 'Welsh Corgi' 리턴 'Welsh Corgi' and True # True 리턴 'Welsh Corgi' and False # False 리턴 False and 'Welsh Corgi' # False 리턴 0 and 'Welsh Corgi' # 0 리턴 자료형(객체) 구분 1. 시퀀스 자료형 리스트, 튜플, range, 문자열처럼 값이 연속적으로 이어진 자료형을 시퀀스 자료형(sequence types)라고 부릅니다. 수행 가능 연산: in 연산자 +, * 연산 (range 제외) len() 함수 인덱싱([] ∝ getitem 메서드) & 슬라이싱(∝ 슬라이스 객체 생성 후 [] 또는 getitem 메서드에 삽입) 인덱스로 값 할당 및 del 삭제 (list만 가능, 다만 범위를 벗어나면 안됨) 슬라이싱으로 값 할당 및 del 삭제 (list만 가능) 슬라이싱으로 값 할당 및 del 삭제 슬라이싱으로 범위를 지정해 값 할당 및 삭제를 진행할 때, 새 리스트를 생성하지 않고 기존 리스트를 변경합니다. 범위와 요소의 개수가 정확히 일치하지 않아도 됩니다. 예 1) a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90] a[2:5] = ['a'] # 인덱스 2부터 4까지에 값 1개를 할당하여 요소의 개수가 줄어듦 a [0, 10, 'a', 50, 60, 70, 80, 90] 예 2) a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90] a[2:5] = ['a', 'b', 'c', 'd', 'e'] # 인덱스 2부터 4까지 값 5개를 할당하여 요소의 개수가 늘어남 a [0, 10, 'a', 'b', 'c', 'd', 'e', 50, 60, 70, 80, 90] 예 3) a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90] a[2:5] = ['a', 'b', 'c'] # 인덱스 2부터 4까지 값 할당 a [0, 10, 'a', 'b', 'c', 50, 60, 70, 80, 90] 범위에 인덱스 증가폭을 설정해서 값을 할당할 수도 있습니다. (다만, 이 때는 범위에 해당하는 요소 개수와 할당할 요소의 개수가 일치해야 합니다.) 예) a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90] a[2:8:2] = ['a', 'b', 'c'] # 인덱스 2부터 2씩 증가시키면서 인덱스 7까지 값 할당 a [0, 10, 'a', 30, 'b', 50, 'c', 70, 80, 90] del을 사용해 일반적으로 값을 삭제할 수 있지만, 인덱스 증가폭을 사용해서 값을 삭제할 수도 있습니다. 예) a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90] del a[2:8:2] # 인덱스 2부터 2씩 증가시키면서 인덱스 6까지 삭제 a [0, 10, 30, 50, 70, 80, 90] 2. 반복 가능한(iterable) 객체 문자열, 리스트, 딕셔너리, 세트 같이, 요소가 여러 개 들어있고, 한 번에 하나씩 꺼낼 수 있는 객체입니다. 반복 가능한 객체는 iter 메서드를 포함하고 있으며, 이 메서드를 호출해 이터레이터(iterator)를 생성할 수 있습니다. 3. 변경 가능한(Mutable) 객체 객체의 값이나 요소가 변경 가능한지 아닌지에 따라 나뉘는 기준이다. Mutable한 객체 list, dict, set을 외워두는게 기억하기 편리하다. 참고하기 좋은 표 얕은 복사와 깊은 복사 얕은 복사 (=주소값 복사) 파이썬은 모든 변수에 주소값이 담기므로, 단순히 변수에 할당하는 방식으로는 새로운 객체를 복사하는 것이 아니라 동일한 객체를 가리키게 된다. a = [0, 0, 0, 0, 0] b = a a is b True 깊은 복사 (1차원) 파이썬 내장함수 copy() 슬라이싱을 통한 복사 ex) a[:] 깊은 복사 (다차원) copy 모듈의 deepcopy() 메서드 사용 튜플을 사용하는 이유 리스트가 언제든 요소를 추가할 수 있게 하기 위해 실제 데이터보다 큰 메모리를 사용하는데 반해, 튜플은 요소 변경이 없어 고정된 메모리를 사용합니다. 또한, 튜플의 구조는 간단해서 리스트보다 빠른 성능을 보여줍니다. 따라서, 요소 변경이 없는 상황에서는 튜플을 사용하는 것이 메모리를 아끼고 성능을 높이는 방법입니다. defaultdict를 사용해 기본값이 빈 리스트인 딕셔너리 생성하기 from collections import defaultdict a = defaultdict(list) a['x'] a['y'] print(a) defaultdict(<class 'list'>, {'x': [], 'y': []}) 딕셔너리에서 요소를 삭제하는 방법 딕셔너리에서 요소를 삭제하는 방법은 제한적이기 때문에 보통 다음과 같은 방법을 사용한다. 1. 키를 통해 삭제하기 del 예약어, pop(‘키’, 기본값) 메서드 사용합니다. popitem() 메서드를 사용하면, 파이썬 3.6 이상에서는 딕셔너리의 가장 마지막에 있는 키, 값 쌍을 삭제하여 튜플로 반환하고, 3.6 미만에서는 임의의 키, 값 쌍을 삭제하여 튜플로 반환합니다. 2. 값을 통해 삭제하기 (특정 값을 제외하여 새로 딕셔너리를 생성) 딕셔너리 표현식을 사용합니다. x = {'a': 10, 'b': 20, 'c': 30, 'd': 40} x = {key: value for key, value in x.items() if value != 20} x {'a': 10, 'c': 30, 'd': 40} 파일 객체는 이터레이터입니다! open을 통해서 가져오는 파일 객체는 이터레이기 때문에, for문에서 반복하거나 언패킹할 수 있습니다. file = open('welsh.txt', 'r') a, b, c = file a, b, c ('안녕하세요.\n', '멍멍!\n', '저는 웰시 코기입니다.\n') random 모듈에서 자주 사용되는 메서드들 import random # 0이상 1미만 범위의 난수 생성 random.random() # return: 0 <= x < 1에 해당하는 x 값 # 지정한 범위에 해당하는 정수 하나를 랜덤하게 가져오기 random.randint(1, 16) # return: 1 <= x <= 16에 해당하는 int 타입 x 값 # 지정한 범위에 해당하는 실수 하나를 랜덤하게 가져오기 random.uniform(1, 20) # return: 1.0 <= x < 20.0에 해당하는 float 타입 x 값 # range(start, stop, step) 함수로 만들어지는 정수들 중 랜덤하게 하나를 가져오기 random.randrange(1, 9, 2) # return: 1, 3, 5, 7 중 하나의 값 seq = ['a', 'b', 'c', 'd'] # 시퀀스 객체 내 요소 순서를 무작위로 변경하기 random.shuffle(seq) # seq: ['c', 'b', 'a', 'd'] # 시퀀스 객체에서 요소 하나를 랜덤하게 가져오기 random.choice(seq) # return: 'c', 'b', 'a', 'd' 중 하나의 값 # 시퀀스 객체에서 요소 여러 개를 랜덤하게 가져오기 random.sample(seq, 2) # return: seq 리스트에서 2개의 요소를 뽑아 리스트로 만들어 리턴 datetime 모듈 사용법 from datetime import datetime # datetime 객체 생성하기 # datetime(년, 월, 일, 시간, 분, 초) birthday = datetime(1994, 6, 27) # datetime.datetime(1994, 6, 27, 0, 0) birthday = datetime(1994, 6, 27, 6, 30, 27) # datetime.datetime(1994, 6, 27, 6, 30, 27) # year, month, day, hour, minute, second 속성에 접근 가능 birthday.year # 1994 birthday.month # 6 # weekday() 메서드를 사용하면 요일을 0(월) ~ 6(일) 인덱스로 반환 birthday.weekday() # 0 # 현재 시간으로 datatime 객체 생성하기 datetime.now() # datetime.datetime(2021, 5, 7, 23, 46, 7, 925228) # datetime 객체로 두 날짜 사이의 시간 차이 구하기 datetime(2021, 1, 2) - datetime(2020, 1, 1) # datetime.timedelta(days=367) datetime.now() - datetime(2021, 1, 1) # datetime.timedelta(days=126, seconds=86052, microseconds=468421) # 문자열로 된 시간을 datetime 객체로 파싱하기 parsed_date = datetime.strptime('Jan 15, 2019', '%b %d, %Y') parsed_date.month # 1 parsed_date.day # 15 parsed_date.minute # 0 # datetime 객체를 문자열로 만들기 date_string = datetime.strftime(datetime.now(), '%b %d, %Y') date_string # 'May 08, 2021' strftime() and strptime() Format Codes 는 다음 링크에서 확인 https://docs.python.org/3/library/datetime.html 함수에서 파라미터의 초깃값을 빈 리스트로 만들고 싶은 경우 함수 파라미터의 초깃값으로는 Immutable한 객체만 사용해야 한다. 만일 Mutable한 객체라면, 여러번 함수를 호출해도 처음에 초깃값으로 생성한 객체를 조작하게 된다. 따라서, 리스트를 인자로 받을 파라미터의 초깃값을 None으로 설정하고 함수 내부에서 if 조건문으로 체크하는 것이 바람직하다. def add_author(authors_books, current_books=None): if current_books is None: current_books = [] current_books.extend(authors_books) return current_books Reference 파이썬 코딩 도장 파이썬 del - 제타위키 파이썬 기초 Python, 파이썬 - Call by assignment, mutable, immutable, 파이썬 복사(Python Copy)
Python-Ecosystem
· 2021-05-06
<
>
Touch background to close