#!/usr/bin/env python # coding: utf-8 # # **Python Book 4** # **[효율적 개발로 이끄는 파이썬 실천기술 Jupyter Notebook](https://nbviewer.org/github/Jpub/fulfillPython/tree/main/)** # # - [How to print colored text to the terminal](https://stackoverflow.com/questions/287871/how-to-print-colored-text-to-the-terminal) # # # # ## **[Chapter 12 단위 테스트](https://nbviewer.org/github/Jpub/fulfillPython/tree/main/12-unittest/)** # ## **1 unittest** # ── 표준 단위 테스트 라이브러리로 `assert` 메서드로 점검한다 # - `assert` 메서드가 여럿일 때, 한개라도 실패하면 Test Fail을 출력한다 # - `assertEqual(a,b)`, `assertTrue(x:bool)`, `assertIsNone(x:None)` # - `assertIn(a:list, b)`, `assertAlmostEqual(a:round(a-b,7)==0, b)` # ### 01 **[Jupyter Notebook](https://www.wrighters.io/unit-testing-python-code-in-jupyter-notebooks/)** 에서 Unit Test 구현 # In[1]: import unittest def booksearch(): return {} class BookSearchTest(unittest.TestCase): def test_booksearch(self): self.assertEqual({}, booksearch()) # $ python -m unittest -v file.py test_result = unittest.main(argv=[''], verbosity=3, exit=False) # assert len(test_result.result.failures) == 0 # ### 02 **Unit Test 예제** # In[2]: import pathlib import unittest from unittest.mock import patch from urllib.error import URLError from tempfile import TemporaryDirectory THUMBNAIL_URL = ( 'http://books.google.com/books/content' '?id=OgtBw76OY5EC&printsec=frontcover' '&img=1&zoom=1&edge=curl&source=gbs_api' ) class SaveThumbnailsTest(unittest.TestCase): def setUp(self): # 임시 디렉터리 작성 self.tmp = TemporaryDirectory() def tearDown(self): # 임시 디렉터리 정리 self.tmp.cleanup() def test_save_thumbnails(self): from data.booksearch.core import Book book = Book({'id': '', 'volumeInfo': { 'imageLinks': { 'thumbnail': THUMBNAIL_URL }}}) # 처리를 실행하고 파일이 작성되었음을 확인 filename = book.save_thumbnails(self.tmp.name)[0] self.assertTrue(pathlib.Path(filename).exists()) # 테스트 대상의 save_thumbnail()가 이용할 참조 이름을 지정 @patch('booksearch.core.get_data') def test_save_thumbnails(self, mock_get_data): from data.booksearch.core import Book # 앞에서 얻은 섬네일 이미지 데이터를 목의 반환값으로 설정 data_path = pathlib.Path(__file__).with_name('data') with open(data_path / 'YkGmfbil6L4C_thumbnail.jpeg', 'rb') as f: data = f.read() mock_get_data.return_value = data book = Book({'id': '', 'volumeInfo': { 'imageLinks': { 'thumbnail': THUMBNAIL_URL }}}) filename = book.save_thumbnails(self.tmp.name)[0] # get_data() 호출 시의 인수 확인 mock_get_data.assert_called_with(THUMBNAIL_URL) # 저장된 데이터 확인 with open(filename, 'rb') as f: self.assertEqual(data, f.read()) class GetBooksTest(unittest.TestCase): def test_get_books_no_connection(self): from data.booksearch.core import get_books # 임시로 네트워크 접속 단절 with patch('socket.socket.connect') as mock: # connect()가 호출되면 정확하지 않은 값을 반환함 mock.return_value = None with self.assertRaisesRegex(URLError, 'urlopen error'): # 예외가 발생하는 처리를 with 블록 안에서 실행 get_books(q='python') # $ python -m unittest -v file.py test_result = unittest.main(argv=[''], verbosity=3, exit=False) # assert len(test_result.result.failures) == 0 # ## **[Chapter 13 Typing](https://www.daleseo.com/python-typing/)** # ## **1 Annotation** # - 파이썬은 인터프린터 언어로, 동적 타입변수를 사용합니다. # - 함수와, 변수에 `annotation` 을 추가 할 수 있지만, 실질적인 제약은 기능하지 않습니다 # - 작성한 어노테이션을 확인하려면 `__annotations__` 내장 객체를 사용 합니다. # In[3]: age: int = 25 num: int = 1 name: str = "John Doe" emails: list = ["john1@doe.com", "john2@doe.com"] address: dict = { "street": "54560 Daugherty Brooks Suite 581", "city": "Stokesmouth", "state": "NM", "zip": "80556" } __annotations__ # In[4]: def repeat(message: str, times: int = 2) -> list: return [message] * times repeat.__annotations__ # ## **2 [Typing](https://www.daleseo.com/python-typing/)** # ── Python 3.5 부터 타입힌트(Type Hint)를 언어 차원에서 지원하고 있습니다. # - 복잡한 타입의 Annotation 을 추가 하지만, 기능적 제약은 없다. # - 따라서 `! pip install mypy` [모듈을 사용해야 디버깅 내용을 명확하게 안내 한다](https://www.daleseo.com/python-mypy/) # ### 01 **List, Dict, Tuple, Set** # - 파이썬 내장 자료구조에 대한 타입을 명시 가능합니다 # - `python -m mypy --strict example.py` 와 같이 외부 모듈을 사용 합니다 # In[5]: from typing import List, Set, Dict, Tuple nums: List[int] = [1] unique_nums: Set[int] = {6, 7} vision: Dict[str, float] = {'left': 1.0, 'right': 0.9} john: Tuple[int, str, List[float]] = (25, "John Doe", [1.0, 0.9]) from typing import Set chars: Set[str] = {"A", "B", "C"} __annotations__ # ### 02 **Final** # 재할당이 불가능한 상수변수에 타입을 추가하는 경우에 활용 # In[6]: from typing import Final TIME_OUT: Final[int] = 10 # ### 03 **Union** # 여러 개의 타입이 허용될 수 있는 상황에서는 typing 모듈의 Union을 사용할 수 있습니다 # In[7]: from typing import Union def toString(num: Union[int, float]) -> str: return str(num) toString(1), toString(1.5), toString('python') # ### 04 **Optional** # `None` 이 허용되는 함수의 매개 변수에 대한 타입을 명시할 때 유용합니다. # In[8]: from typing import Optional def repeat(message: str, times: Optional[int] = None) -> list: if times: return [message] * times else: return [message] # ### 05 **Callable** # 함수에 대한 타입 어노테이션을 추가할 때 사용합니다. # - 예시로 작성한 `repeat` 함수는 첫 번째 매개 변수 `greet` 인자를 갖는다. # - 매개 변수에 타입 어노테이션 `Callable[[str], str]` 를 추가 한다. # - `str` 타입의 인자를 하나 받고, 결과값은 `str` 로 반환하는 함수가 된다. # - 람다 함수를 작성할 때도 동일한 타입 어노테이션을 사용할 수 있습니다. # In[9]: from typing import Callable greet: Callable[[str], str] = lambda name: f"Hi, {name}!" def repeat( greet: Callable[[str], str], name: str, times: int = 2) -> None: for _ in range(times): print(greet(name)) repeat(greet, "Dale") # ### 06 **타입 추상화** # - 함수의 매개 변수에 대한 타입 어노테이션을 `타입을 추상적으로 명시` 해주는 것이 유리한 경우가 많습니다. # - 아래 toStrings() 함수는 nums 매개 변수의 타입을 List[int] 대신에 Iterable[int]로 명시 합니다 # In[10]: from typing import Iterable, List def toStrings(nums: Iterable[int]) -> List[str]: return [str(x) for x in nums] # ## **2 [VPN 우회 크롤링](https://jvvp.tistory.com/1114)** # - https://surpassing.tistory.com/917 # - https://free-proxy-list.net/ # - https://jvvp.tistory.com/1114 # In[11]: # 140.227.238.217:3128 JP Japan elite proxy no no 12 mins ago # 140.227.201.6:3128 JP Japan anonymous no yes 12 mins ago url = 'https://javgg.net' proxy = 'socks5://92.42.109.187:1080' proxies = {"http": proxy, 'https': proxy} headers = { "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36", "content-type": "application/json", } import requests # response = requests.get(url, headers=headers, proxies=proxies, timeout=10) # response