2020-04-26

나를 위한 파이썬 Thread 사용법 간단 정리

0. 파이썬의 Thread는 사용할 때마다 헷갈린다.

그래서 정리해보았다.



1. 기본 틀은 이렇게 생겼다.

from threading import Thread
import time


def printer(text):
    print(text)
    time.sleep(2)
    print(text, 'end')

for i in range(10):
    Thread(target=printer, args=(i, )).start()
    time.sleep(0.5)





위의 코드를 실행시키면 결과는 이렇게 나온다.


2. 주의할 점들

 - Thread의 파라미터 중 target은 함수 자체를 넣어줘야 한다.
 - Thread의 파라미터 중 args는 tuple 객체만 받다보니, arg가 하나여도 (i, ) 처럼 쉼표를 넣어서 튜플로 만들어주어야 한다.


구글 검색 활용, 화합물 한글 명칭으로 Cas No 찾기

안전보건공단피셜

0. 필요성

제각기인 화합물 한글명칭의 Cas No가 필요해졌다.

예를 들자면

에탄올을 '에틸 알코올', 'EtOH', 'Ethyl alcohol', 'Alcohol'(?) 등으로 쓰는데

각종 DB에서 이러한 다양한 명칭 및 다양한 띄어쓰기로 검색할 시

결과가 제대로 나오지 않는 문제 등이 있었다. (위의 사진 참조)

그래서 항상 우리는 구글링을 한다.

그래서 구글링을 자동화할 아이디어를 떠올렸다.



1. 컨셉

구글에 해당 명칭 + "cas no" 의 형식을 기반으로 자동 검색 코드를 짠다.

Cas No가 ???-???-??? 형식을 취한다는 점을 이용,

구글의 검색결과 html에서 위의 형식을 정규식으로 매칭시킨 다음,

파이썬의 Counter 클래스로 개수를 세서,

가장 많이 등장하는 일련번호가 곧 Cas No일 것이라고 추측한다.



2. 결과


'암모니아'와 '암모니아수'가 서로 다른 Cas No로 구분되고 있는 모습과

'염소'와 '염소(Cl2)'가 같은 Cas No를 가리키고 있는 모습 및

차아염소산나트륨이 '염소산소다', '염소산나트륨'으로도 검색되지만

정확성에서 안습한 모습을 보여주고 있는 모습에 주목하라.



참고로 9-2-2는 검색결과 html에서 잘못 매칭되고 있는 결과이다.

이것만 없애도 정확도가 상승할 것 같긴 하지만(염소산포타슘의 예처럼)

정확하게 검색된 염산같은 물질들이

검색 결과에서 총 14번이나 등장하면서 '9-2-2'를 압도하는 모습이 마치

'이 정도는 반복되어야 믿을 수 있지 않을까'라는 심정..

이 값을 기준 역치값으로 사용해버렸다.

(이 정도도 이기지 못하는 일련번호는 쓸모없다!)



3. 코드

from selenium import webdriver
import time
import re
import traceback
from collections import Counter


def searching(query):
    try:
        driver = webdriver.Chrome('chromedriver.exe')
        time.sleep(1)
        driver.get('http://www.google.com')
        time.sleep(1)
        inputElement = driver.find_element_by_name("q")
        time.sleep(1)
        inputElement.send_keys(f"{query} cas no")

        inputElement.submit()
        time.sleep(1)

        cass = re.findall('(\d+-\d+-\d+)', driver.page_source)
        print(query.strip(), Counter(cass).most_common())
        return Counter(cass).most_common()

        driver.quit()
    except Exception as ex:
        print('---------에러 발생---------')
        print(traceback.format_exc())
    finally:
        driver.quit()


with open(r'##열고 싶은 파일 경로.txt##', 'r', encoding='utf-8') as f:
    result = []
    lists = f.readlines()
    for compound in lists[150:170]:
        result.append({'compound': compound.strip(), 'counter': searching(compound.strip())})
    for i in result:
        print(i['compound'], i['counter'][0][0], f"{i['counter'][0][1]}회", sep='★')

실행할 코드파일.py에 셀레니움 실행을 위한 chromedriver.exe 가 필요하다.

'열고 싶은 파일 경로'는 한 줄에 물질명 하나씩 담겨 있는 텍스트 파일로 하였다.

이렇게 ↓


그리고 결과창에 추가로 출력되는 맨 아랫줄의 코드는

★로 각 단어를 분리해, 엑셀에 붙여넣어 작업을 편리하게 하려고

급하게 끼워넣어 사용한 코드임을 보여준다. (...)



4. 한계점

이상한 명칭(?)들에는 제대로 작동하지 않을 수 있다.

애초에 '가장 자주 등장하는 매칭'을 끌어오는 방식이기 때문에

구글의 검색결과에 절대적으로 의존하고 있다.
(Cas No를 통해 추가 검증을 하지 않았다는 의미)

또한 구글에서 당신을 로봇으로 인식하여

'휴먼입니까 휴먼?'이라며 아래의 페이지를 띄우기도 한다.

(휴먼?)

그래서 셀레니움을 headless로 설정하지 않았다.

휴먼이 실시간으로 모니터링 하는 편이 더 나을 것이라고 생각했다.



5. 고찰

역시 작업은 자동화해야 제맛이다.

300개 물질의 Cas No를 일일히 검색하려니 여간 귀찮은게 아니었는데

이 코드로 80% 이상은 반복 작업을 줄였으니

이 정도면 쓸 만하다고 생각한다.

하지만 이런 조악한 코드조차도 빠르게 짤 수 없는 사람이라면

코딩보다 손수 작업하는 편이 더 빠를지도 모른다.

가장 효율적이게 문제를 해결할 방법을 고민하는 습관을 들이면 좋을 것 같다.

2020-04-16

openpyxl 파일 불러오기 에러 TypeError: expected <class 'str'>

0. 증상


wb = openpyxl.open()

TypeError: expected <class 'str'>



1. 원인

엑셀 파일의 버전이 맞지 않아서이다.

아마 외부에서 가져온 파일일 가능성이 높다.

현재 필자가 사용하고 있는 openpyxl의 버전은 3.0.3이고,

엑셀의 버전은 Microsoft Office 365 ProPlus 이다.
(Office 365는 구독형 서비스로, 항상 최신 버전을 사용한다고 함)

내가 작성한 파일 혹은 내가 저장한 파일은 정상적으로 열리는걸 확인했는데

인터넷에서 가져온 파일들은 정상적으로 openpyxl로 open되지 않았다.



온라인의 글에 따르면 openpyxl의 버전을 2.5.x 이하로 낮춰서

해당 문제를 해결할 수 있다고 뻥카를 치고 있는데 하지만

필자가 설치 가능한 모든 openpyxl 버전을 다운그레이드하며 시도해본 바에 의하면

해당 방법은 이제 소용이 없다.

시간 날렸다.



2. 해결법


해당 엑셀 파일을 openpyxl이 아닌, Excel로 열어서 '다른 이름으로 저장'하며

Excel 통합 문서 (*.xlsx)로 저장해준다.

(혹은 그냥 파일을 열었다가 Ctrl + S 로 저장해준 뒤 파일을 닫아도 되는 경우가 있다)



이 해결법 외에도, 파일의 버전을 조절할 수 있는 방법이 있다면

그 방법을 사용해도 된다.



우리는 늘 그랬듯이 답을 찾을 것이다.

더 편한 엑셀 작업을 위해..

파이썬에서 객체의 사용 가능한 메서드가 type hint로 뜨지 않을 때

0. 궁금증의 발단은 이러했다.



내가 하던 짓 :

엑셀 작업을 반복적으로 처리해야 할 일이 생겼는데

엑셀의 자체 매크로 기능을 사용하기엔 한계점이 많다보니

해당 작업을 파이썬으로 진행해보려고 openpyxl을 사용해보던 중이었다.



위의 사진 설명 :

엑셀 파일을 wb(workbook)이라는 변수명으로 open한 뒤,

wb 중에서 active sheet를 ws(worksheet)라는 변수명으로 담았다.

wb.active로 ws를 만들었을 때에나, wb['작업할 시트 이름']으로 불러올 때에나

둘 다 type()을 찍어보면 아래의 사진처럼 같은 클래스라는 것을 알 수 있었다.

openpyxl.워크시트.워크시트.크시트...

PyCharm과 같은 IDE에서 ws.를 입력했을 때 사용 가능한 객체의 메서드들이 나오는 것

(들을 줄여서 type hinting이라고 한다)

들을 한 번에 볼 수 있는 (built-in 함수) dir()도 있었다.

근데 불편하다. 언제 그걸 하나하나 print(dir(o))를 찍어보고 앉아있는가..



대체 왜 wb.active와 wb['작업할 시트 이름']의 반환 객체는 같은데

하나는 type hinting이 뜨고, 다른 하나는 뜨지 않는걸까.

+ 여담으로, wb.get_sheet_by_name() 메서드도 사용 가능했지만

DeprecationWarning: Call to deprecated function get_sheet_by_name (Use wb[sheetname]).
--> 당신이 쓰는건 낡은 방식이니까 새로운 방식을 사용하라는 권고문

이라는 권고 아닌 경고문이 뜨다 보니.. 편하게 딕셔너리처럼 다루는 방식을 차용했다.



1. 그래서 패키지를 뜯어보았다.


빨간색 밑줄 친 부분을 파이썬에선 DocString이라고 부른다.

wb.active는 자체적으로 docstring에서 타입을 지정해주고 있었다.

하지만 딕셔너리처럼 직관적이게 불러오는 방식에선

해당 타입을 지정해주지 못해서 관련 메서드를 확인할 수 없었던 것이었다.



2. 그럼 손가락 빨고 지켜봐야 하는가? No!!



이 불편한 사태를 지켜봐야 하는가?

그래서 방법이 있다.



PyCharm의 공식 문서에 따르면

해당 엘리먼트에 대고 'Alt + Enter'를 눌러서 Add type hint for를 선택해서

수동으로 타입을 지정해주면 된다고 한다.



문법은 위의 사진처럼

ws: 뒤에 해당 클래스를 지정해주는 방식으로 사용하면 된다.

해당 클래스를 찾아 들어가는 경로가 좀 다른데
(type을 출력했을 땐 openpyxl.worksheet.worksheet.Worksheet 였지만
위의 사진은 openpyxl.workbook.workbook.Worksheet 으로 적었다.)

결과적으론 Worksheet라는 클래스를 사용한다는 점이 같기 때문에

크게 신경쓰지 않아도 된다.



3. 잡담

사실 이 문제를 굉장히 오래 고민해왔다.

numpy나 pandas, PIL 등

다른 패키지를 사용하면서도 겪었던 저 type hint가 뜨지 않았던 문제로 인해

documents를 정독해가며 작업 코드를 만드는게 번거로웠기 때문..

근데 의외로 간단한 문법으로 해당 문제를 해결했다.



작업을 편리하게 진행할 수 있도록 도와주는 type hinting

적극적으로 활용해보자.