[day-15] 파일 입출력, 비트연산, 동기/비동기, 정규표현식

Joohyung Park·2024년 1월 18일
0

[모두연] 오름캠프

목록 보기
15/95
post-thumbnail

파이썬 버전 별 변경사항

  • Python 3.5: async await
  • Python 3.6: Dict에 순서, f-string, 타입힌트
  • Python 3.7: dataclasses
  • Python 3.8: 왈러스 연산자(:=)
  • Python 3.9: 딕셔너리 결합 연산자(|)
  • Python 3.10: match

파일 입출력

파일을 생성하는 방법은 2가지가 있다.

# 첫번째 방법
# open(파일이름, 파일모드)
f = open('python.txt', 'w') # 파일모드 : r, w, a, r+,
f.write('hello world')
f.close()

close()를 하지 않으면 메모리 계속 살아있으니 조심해야 한다.

# 두번째 방법
with open('test.txt', 'w') as f:
    f.write('hello world!')

두번째 방법은 close가 필요 없어서 위처럼 쓰도록 하자.
예시를 한번 보자.

db_data = [
  {
    "_id": "7f5440ab-7e2f-4a42-Be13-89987fa65b32",
    "index": "1",
    "name": "등윤상",
    "email": "user-qe89s09@ut.io",
    "phone": "010-3542-7711",
    "country": "키리바시",
    "address": "방학로 74-9",
    "job": "일러스트레이터"
  },
  {
    "_id": "b4f7ade0-38bb-4e6f-B94a-18af1426eb30",
    "index": "2",
    "name": "노동규",
    "email": "user-nyr5mqw@ornare.com",
    "phone": "010-9743-6320",
    "country": "베냉",
    "address": "남부순환로 54-6",
    "job": "애완동물미용사"
  },
  {
    "_id": "5d788255-8d5e-4f03-B446-dbac6a147e37",
    "index": "3",
    "name": "경진아",
    "email": "user-n7pey4v@Etiam.biz",
    "phone": "010-4049-8718",
    "country": "아르메니아",
    "address": "강남대로 87-5",
    "job": "플로리스트"
  },
  {
    "_id": "083efce4-17ed-4e9f-Ad7c-f9aa0ac6cb79",
    "index": "4",
    "name": "복소유",
    "email": "user-ohaz2is@nibh.biz",
    "phone": "010-3400-5945",
    "country": "페루",
    "address": "양산로 78-1",
    "job": "응급구조사"
  },
  {
    "_id": "d54ce0fa-c025-4af6-Cd9a-b9d10aa8be5a",
    "index": "5",
    "name": "화예빈",
    "email": "user-mnkpdlp@vel.net",
    "phone": "010-3120-8720",
    "country": "허드 및 맥도널드 제도",
    "address": "한남대로 6-2",
    "job": "전문의사"
  }
]

위와 같은 db_data라는 json형식의 데이터가 있다.


미션1. 각각의 회원 이름_info.txt로 각각 정보 정리

list(map(lambda x: x['name'], db_data))
# ['등윤상', '노동규', '경진아', '복소유', '화예빈']
for i, name in enumerate(map(lambda x: x['name'], db_data)):
    with open(f'{name}_info.txt', 'w') as f:
        f.write(str(db_data[i]))

db_data에서 'name'부분만 따와서 그 이름으로 파일을 만드는 코드이다. 이때, map을 list로 묶지 않고 반복문 돌리는 것을 추천한다.

미션2. 엑셀로 각각 정보 정리

with open('data.csv', 'w') as f:
    s = '아이디,인덱스,이름,이메일,휴대폰,나라,주소,직업\n'
    for i in db_data:
        s += f'{i["_id"]},{i["index"]},{i["name"]},{i["email"]},{i["phone"]},{i["country"]},{i["address"]},{i["job"]}\n'
    f.write(s)

data.csv라는 파일이 없으면 만들고 db_data에 있는 내용을 반복하며 추가하는 코드이다. f.write()로 내용을 추가했다.

미션3. html로 table로 정리하기

for item in db_data:
    s += f'''
        <tr>
                <td>{item["_id"]}</td>
                <td>{item["index"]}</td>
                <td>{item["name"]}</td>
                <td>{item["email"]}</td>
                <td>{item["phone"]}</td>
                <td>{item["country"]}</td>
                <td>{item["address"]}</td>
                <td>{item["job"]}</td>
        </tr>
        '''

s += '</table>'

with open('test.html', 'w') as f:
    f.write(s)

위와 같은 방식으로 html형식의 데이터를 만들 수도 있다.


비트연산

실무에서 비트연산을 하는 경우가 거의 없지만 알고리즘 테스트에서 간혹 나오므로 알아두자.

# 10진법
# 1542 == 1 * (10**3) + 5 * (10**2) + 4 * (10**1) + 2 * (10**0)
# 0123456789 10

# 16진법
# 1542 == 1 * (16**3) + 5 * (16**2) + 4 * (16**1) + 2 * (16**0)
# 0123456789abcdef 10

# 8진법
# 1542 == 1 * (8**3) + 5 * (8**2) + 4 * (8**1) + 2 * (8**0)
# 01234567 10

# 01 10
# 3 & 9
# and연산은 곱하기

# 0011
# 1001
# ----
# 0001
# 3 | 9
# or연산은 더하기

# 0011
# 1001
# ----
# 1011 => 11

f-string

name = '이호준'
age = 10

'제 이름은 %s이고 제 나이는 %d입니다.'%(name, age) # % 용법
'제 이름은 {}이고 제 나이는 {}입니다'.format(name, age) # format 용법(이 문법도 아직 많이 사용한다.)
f'제 이름은 {name}이고 제 나이는 {age}입니다' 

f-string을 권고한다고 한다. 추가로, print문 안에 수식을 많이 넣지 않는게 좋다!

one_length = 5.343123123

print(f'{one_length} 입니다.')
print(f'{one_length:.1f} 입니다.')

.1f등의 문법으로 소수점을 짤라서 보여줄 수 있다.

age = 100
print('당신의 나이는 [{:^15}]세'.format(22))
# 당신의 나이는 [      22       ]세

로그 출력할때 위처럼 하면 중간에 공백을 만들어 출력할 수 있다. 이거는 많이 사용한다고 한다.

# 출력에 중괄호를 넣고싶다면 중괄호 2개
print(f"My set is {{1, 2, 3}}.")  # 출력: My set is {1, 2, 3}.

중괄호 2개로 중괄호 출력이 가능하다.

# 중괄호 3개를 사용하면 변수까지 출력할 수 있다.
x = 10
print(f"My set is {{{x}}}.")  # 출력: My set is {10}.
print(f"My set is {{{{x}}}}.")
# My set is {10}.
# My set is {{x}}.

컴프리헨션

기존 시퀀스(sequence), 이터러블(iterable), 또는 다른 표현식들로부터 새로운 시퀀스를 생성하는 간결하고 가독성 있는 방법

# 예시 1
even_squares_set = {x**2 for x in range(10) if x % 2 == 0}
even_squares_set
# 예시 2
even_squares_gen = [x**2 for x in range(10) if x % 2 == 0]
even_squares_gen

리스트 컴프리헨션은 c로 구현되어 있어 for문만 쓰는거보다 훨씬 빠르다


동기 프로그래밍

이미지
위의 예제에서 볼 수 있듯이 손님1을 처리하고, 손님2를 처리하고 손님3을 처리하는 것을 말한다. 즉, 특정 작업이 완료될 때까지 프로그램이 기다리는 방식이다. 보통의 경우는 동기 프로그래밍이다.

비동기 프로그래밍

동기프로그래밍과는 다르게 동시에 여러 작업을 진행할 수 있다. 이때, 이벤트 루프와 콜백 함수 등을 활용하여 작업을 관리한다. 함수에 async라는 키워드를 붙여 비동기 함수(코루틴)을 만들어 사용한다고 한다.


정규표현식

유효성 검증, 문자열 처리, 코딩테스트에 자주 등장한다고 한다

참고용 사이트

일반 문자열 패턴

hello world
Hello

^와 $

^hello -> hello로 시작하는 문자열(앞에 다른 문자 없음)
hello$ -> hello로 끝나는 문자열(뒤에 다른 문자 없음)
^hello$ -> hello로 시작하고 hello로 끝나는 문자열(hello만 들어있는)

.

. -> 줄바꿈 문자(\n)을 제외한 모든 문자
..... -> 5글자(.의 개수) 이상인 문자열을 5글자씩 끊어서 반환

[]

h[eao]llo -> h + e or a or o + llo인 문자열
.[eao]llo -> 아무 문자 1(\n제외) + e or a or o + llo인 문자열

^, $, [] 응용

^h[eao]llo$ -> h로 반드시 시작(앞에 다른 문자 없음) + e or a or o + llo로 반드시 끝나는 문자열(뒤에 다른 문자 없음)
h[a-zA-Z0-9]llo -> h로 반드시 시작(앞에 다른 문자 없음) + 알파벳 대소문자 or 0-9숫자 + llo
h[a-zA-Z]llo -> h로 시작 + 알파벳 대소문자 + llo

전화번호

010-[0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9] -> 010 + -(하이푼) + 숫자 4자리 + -(하이푼) + 숫자4자리
010.[0-9][0-9][0-9][0-9].[0-9][0-9][0-9][0-9] -> 010 + 아무 문자 1(\n제외) + 숫자 4자리 + -(하이푼) + 숫자4자리
010[- .][0-9][0-9][0-9][0-9][- .][0-9][0-9][0-9][0-9] -> 010 + - or 공백 or . + 숫자 4자리 + - or 공백 or . + 숫자4자리

[]안의 ^

[^a-zA-Z0-9]: 나머지 문자열을 찾음 #, ^는 대괄호 안에서는 '부정' -> 알파벳이 아니고 숫자가 아닌 문자열

앞의 문자가 n개 이상, 사이

* : 앞에 있는 문자가 0개 이상 = ({0,})
+ : 앞에 있는 문자가 1개 이상 = ({1,})
? : 앞에 있는 문자가 0~ 1= ({0,1})
010[- .]?[0-9][0-9][0-9][0-9][- .]?[0-9][0-9][0-9][0-9] -> 010 + - or 공백 or . + ?(앞에 문자가 있어도 되고 없어도 됨) + 숫자 4자리 + - or 공백 or . + ?(앞에 문자가 있어도 되고 없어도 됨) + 숫자 4자리

{3} : 3{3,} : 3개 이상
{1,3} : 1~ 3010[- .]?[0-9]{4}[- .]?[0-9]{4} -> 010 + - or 공백 or . + ?(앞에 문자가 있어도 되고 없어도 됨) + 0-9숫자 4+ - or 공백 or . + ?(앞에 문자가 있어도 되고 없어도 됨) + 숫자 4[0-9]{3}[-.* ][0-9]{4}[-.* ][0-9]{4} -> 0-9숫자 3+ - or . or * or 공백 + 숫자 4+ - or . or * or 공백 + 숫자 4

?의 또다른 쓰임 : 비탐욕적 수량자

수량자(, +, {n,} 등) 뒤에 붙는 ?는 해당 수량자를 비탐욕적(non-greedy) 또는 lazy으로 만든다. 기본적으로 수량자는 최대한 많은 문자를 매칭하려고 시도한다. 이를 탐욕적(greedy) 매칭이라고 하는데 ?가 붙은 비탐욕적 수량자는 첫 번째 만나는 패턴에서 종료한다. 예를 들어, .? 패턴은 가능한 한 적은 문자를 매칭하려고 시도한다.

전화번호 2

[0-9a-zA-Z]{2,3}[-.* ][0-9]{3,4}[-.* ][0-9]{4} -> 숫자 or 알파벳 2~3+ - or . or * or 공백 + 0-9숫자 3~4+ - or . or * or 공백 + 0-9 숫자 4[0-9]{2,3}[-.* ][0-9]{3,4}[-.* ][0-9]{4} -> 0-9 숫자 2~3+ - or . or * or 공백 + 0-9 숫자 3~4+ - or . or * or 공백 + 0-9 숫자 4

이메일

[0-9a-zA-Z]+@[0-9a-zA-Z]+.[a-zA-Z]+ -> 0-9 숫자 or 알파벳이 1번 이상 반복(+) + @ + 0-9 숫자 or 알파벳이 1번 이상 반복(+)
^[a-zA-Z0-9+-_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$ -> 알파벳 or 0-9숫자 or + or - or _ or .으로 무조건 시작(^)1번 이상 반복 + @ + 알파벳 or 0-9숫자 or -1번 이상 반복 + .(역슬래시가 붙으면 그 문자 그대로) + 알파벳 or 0-9숫자 or - or .1번 이상 반복 + 그게 반드시 마지막으로 끝나야 함
.....@----.... -> 아무 문자 5(\n제외) + @ + - 4+ 아무문자 4(\n제외)

역슬래시와 함께라면?

\w : 특수 문자 제외한 문자(word)
\w{5} : 5개의 글자(word)
\W : 특수 문자(공백 포함)
\d : 숫자(digit)
\D : not 숫자(digit)
\s : 스페이스(공백)
\S : not 스페이스(공백이 아닌 모든것)

역슬래시 응용

^\d{3}[- .]?\d{3,4}[- .]?\d{4}$ -> 숫자 3개로 반드시 시작함 + - or 공백 or .이 있어도 되고 없어도 됨(0or 1번 반복) + 숫자 3~4+ - or 공백 or .이 있어도 되고 없어도 됨(0or 1번 반복) + 숫자 4개로 반드시 끝나야 함
 
\[.*] : [로 시작하고 .(모든 문자열이) *(0번 이상 반복) 하며 ]로 끝남
\(.*\) : (로 시작하고 .(모든 문자열이) *(0번 이상 반복) 하며 )로 끝남
\\.*\/ : \로 시작하고 .(모든 문자열이) *(0번 이상 반복) 하며 /로 끝남
-.*- : - 사이에 0개 이상의 문자가 존재
\^\^ : ^^문자를 표기하기 위함
:\) : :) 문자 표시

자주 사용하는 메서드

# 사용되는 패턴
# 1
p = re.compile(r'([0-9]|10)([SDT])([\*\#]?)')
p.findall('1S2D*3T')

# 2
re.findall(r'([0-9]|10)([SDT])([\*\#]?)', '1S2D*3T')

# 자주 사용하는 메서드
# sub() : 매치된 부분을 치환 (str에 replace와 같은 역활)  # 가장 많이 사용
# findall() : 매치된 부분 모두 리스트 반환

findall

해당 정규표현식을 만족하는 문자(열)를 리스트로 반환한다.

import re

list(re.findall(r'([0-9]|10)([SDT])([\*\#]?)', '1S2D*3T'))
# [('1', 'S', ''), ('2', 'D', '*'), ('3', 'T', '')]

sub

데이터 안의 정규표현식 부분을 특정 문자로 바꾼다.

import re

def solution(my_string):
    return re.sub(r"[aeiou]", "", my_string)

문자열에서 replace와 같은 느낌이다.

import re

text = '''# This is a h1
## This is a h2
### This is a h3
#### This is a h4
##### This is a h5
###### This is a h6

* This is a bulleted list
  * This is a nested bulleted list

**This is bold text**

_This is italic text_

~~This text is strikethrough~~
'''

def markdown_to_html(markdown):
    html = re.sub(r'### (.*)', r'\1', markdown)
    html = re.sub(r'## (.*)', r'\1', html)
    html = re.sub(r'# (.*)', r'\1', html)
    html = re.sub(r'`(.*)`', r'\1', html)
    html = re.sub(r'\*\*(.*)\*\*', r'\1', html)
    html = re.sub(r'_(.*)_', r'\1', html)
    html = re.sub(r'~~(.*)~~', r'\1', html)
    html = re.sub(r'^(>+) (.*)', r'\1\2', html)
    # html = re.sub(r'^\* (.*)', r'\1', html, flags=re.M+re.S)
    return html

print(markdown_to_html(text))

위의 코드처럼 마크다운 문법의 텍스트를 html로 바꿀 수도 있다.


연습문제

1. 주어진 문자열에서 모든 이메일 주소를 user@weniv.co.kr으로 변경하시오.(sub사용)

  • 예시 입력: "저의 이메일 주소는 kim123@gmail.com입니다. 친구의 이메일 주소는 lee456@gmail.com입니다."
  • 예시 출력: "저의 이메일 주소는 user@weniv.co.kr입니다. 친구의 이메일 주소는 user@weniv.co.kr입니다."
import re

text =  "저의 이메일 주소는 kim123@gmail.com입니다. 친구의 이메일 주소는 lee456@gmail.com입니다."

my_text = re.sub(r'[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}','user@weniv.co.kr', text)
my_text

2. 주어진 HTML 문자열에서 모든 HTML 태그를 제거하고 텍스트만 남기시오.

  • 예시 입력: "

    이것은 예시 문장입니다.

    "
  • 예시 출력: "이것은 예시 문장입니다."
import re

text = "<p>이것은 <b>예시</b> 문장입니다.</p>"

my_text = re.sub(r'<[^>]+>','', text)
my_text

피드백

오늘도 꽤나 많은 내용을 진행했다. 이게 유익하긴 한데 중간중간 비동기 프로그래밍 같은? 부분에서 안드로메다로 가버렸다가 다시 돌아왔다가 했던 것 같다. 오늘 한 것 중에선 정규표현식이 가장 중요해 연습문제도 2개를 내주셨는데 이거를 확실히 내꺼로 만들고 가야겠다고 생각했다. 매번 생각하는 것이지만 강사님이 열정적이시고 파이썬을 엄청 좋아하시는게 느껴진다고 해야되나? 좋은 에너지를 많이 받고 있는 것 같다. 학교 수업이나 다른 강의같은 경우에는 안다뤄주는 심화 부분들 + 실무 경험 등을 알려주시니 너무 좋다. 포기하지 말고 끝까지 해보자.

profile
익숙해지기 위해 기록합니다

0개의 댓글