참고로 나도 오늘 처음 들은 개념이다. 


List comprehension-기본편

a = list(range(9))
print(a)

보통 리스트는 이런 식으로 생성하거나 

a = [0,1,2,3,4,5,6,7,8]

이렇게 생성한다. (같은 리스트다) 

이걸 리스트 컴프리헨션으로 생성하려면? 

a = list(i for i in range(9))
print(a)

이렇게 쓰거나

a = [i for i in range(100)]
print(a)

쿨하게 대괄호 안에 끼워넣으면 된다. 

 

For문 넣기

참고로 본인 신조가 'For문 가는데 While이 국룰'이었는데 얘는 예외다. While이 들어가질 못함... for문과 달리 while은 조건부 반복문이라 그런 듯 하다. 

 

a = list(2 ** i for i in range(9))
print(a)
[1, 2, 4, 8, 16, 32, 64, 128, 256]

걍 이렇게 쓰세여. 

a = list(i ** 2 for i in range(9))
print(a)
[0, 1, 4, 9, 16, 25, 36, 49, 64]

자매품(2제곱)

 

if문 넣기

def isprime(a):
    sqrt = int(a ** 0.5)
    if a == 1: 
        return False
    for i in range(2,sqrt+1):
        if a % i == 0:
            return False
    else: 
        return True

솔직히 홀짝은 너무 진부해서 가져와봤음. 이건 백준 함수파트 풀 때 개근했던 소수 찾는 함수다. 

 

a = [i for i in range(100) if isprime(i)]
print(a)
[0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

일단... 저 함수 자체를 좀 손봐야 하는 게, 백준 소수파트는 숫자가 작아봐야 1이다. 그래서 1보다 작은 수가 아니라 1일 때에 대한 처리를 한 거라 그 부분을 손봐야 한다. 

 

def isprime(a):
    sqrt = int(a ** 0.5)
    if a <= 1: 
        return False
    for i in range(2,sqrt+1):
        if a % i == 0:
            return False
    else: 
        return True
# a == 1을 a <= 1로 바꿨다. 
a = [i for i in range(100) if isprime(i)]
print(a)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

함수의 세번째 줄에 있는 ==를 <=로 바꾸면 2부터 나오는 것을 볼 수 있다. 

 

b = list(i for i in range(100) if i % 3 == 0)
print(b)
[0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99]

물론 이런 것도 된다. 

 

한번에 여러 개 쓰기

a = [i * j for i in range(1,11) for j in range(1,5,2)]
print(a)
[1, 3, 2, 6, 3, 9, 4, 12, 5, 15, 6, 18, 7, 21, 8, 24, 9, 27, 10, 30]

반복문이 여러개 있을 때 처리는 뒤에놈부터다. 즉, 이 코드는 뒤에 있는 j(1,3)를 앞에 있는 i에 순차적으로 곱한 결과. 

 

a = [i for i in range(1,101) if i % 2 == 0 and i % 4 == 2]
print(a)
[2, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50, 54, 58, 62, 66, 70, 74, 78, 82, 86, 90, 94, 98]

조건문도 여러 개 넣을 수 있다. 위 코드는 2의 배수이면서 4의 배수가 아닌 수를 찾는 코드. 

 

a = [i for i in range(1,101) if i % 2 == 0 if i % 3 == 0]
print(a)
[6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96]

2와 3의 공배수, 즉 6의 배수를 찾는 코드. ...근데 이게 이렇게 된다고? 저거 실화냐 


번외편-어? 이게 된다고? 

a = ['{},{}'.format(i,j) for i in range(1,10) for j in range(1,10,2)]
print(a)
['1,1', '1,3', '1,5', '1,7', '1,9', '2,1', '2,3', '2,5', '2,7', '2,9', '3,1', '3,3', '3,5', '3,7', '3,9', '4,1', '4,3', '4,5', '4,7', '4,9', '5,1', '5,3', '5,5', '5,7', '5,9', '6,1', '6,3', '6,5', '6,7', '6,9', '7,1', '7,3', '7,5', '7,7', '7,9', '8,1', '8,3', '8,5', '8,7', '8,9', '9,1', '9,3', '9,5', '9,7', '9,9']

포맷 줘서 문자열을 리스트에 넣는 것도 된다. 

 

a = ['{},{}'.format(i,j) for i in range(1,10) for j in 'pikachu']
print(a)
['1,p', '1,i', '1,k', '1,a', '1,c', '1,h', '1,u', '2,p', '2,i', '2,k', '2,a', '2,c', '2,h', '2,u', '3,p', '3,i', '3,k', '3,a', '3,c', '3,h', '3,u', '4,p', '4,i', '4,k', '4,a', '4,c', '4,h', '4,u', '5,p', '5,i', '5,k', '5,a', '5,c', '5,h', '5,u', '6,p', '6,i', '6,k', '6,a', '6,c', '6,h', '6,u', '7,p', '7,i', '7,k', '7,a', '7,c', '7,h', '7,u', '8,p', '8,i', '8,k', '8,a', '8,c', '8,h', '8,u', '9,p', '9,i', '9,k', '9,a', '9,c', '9,h', '9,u']

파이썬은 문자열도 시퀀스 데이터라 for문을 돌릴 수 있어서 저게 된다. 

 

a = [ord(i) for i in 'ABCDEFG']
print(a)
a = [hex(ord(i)) for i in 'ABCDEFG']
print(a)

알파벳의 ASCII 코드 번호를 뽑거나 그 코드 진수 바꾸는 것도 된다. 

 

번외편 2-튜플도 되나요? 

a = (i for i in 'pikachu')
print(type(a))
<class 'generator'>

아뇨 안되는데요. 저거 뽑을 수는 있는데 

a = (i for i in range(1,10))
for i in a:
    print(i)

for문 줘야 한다. (예전에 Biopython할 때 저런거 많이 봤는데 죄다 for문으로 뽑더라...)

 

a = 'Eternatus'
b = tuple(ord(i) for i in a)
print(b)

참고로 얘는 된다. 소괄호 말고 쟤. 

 

번외편 3-세트와 딕셔너리에도 해봤습니다

a = {i for i in range(10)}
print(a)
a = set(i for i in 'apple')
print(a)

세트의 경우 둘 다 생성하는 방식이다. 아무튼... 

 

a = {i:j for i in range(9) for j in range(9) if i % 2 == 0)
print(a)

딕셔너리는 되긴 되는데 키값이 중복되면 앞에걸 버리는 특성상 리스트나 튜플보다는 길이가 짧다. 

'Coding > Python' 카테고리의 다른 글

순열조합  (0) 2022.08.22
워드클라우드 코드에 기능 추가  (0) 2022.08.22
완전수 찾는 코드  (0) 2022.08.22
Project restriction enzyme: 패치노트  (0) 2022.08.22
Cutter, Finder 공통 패치: FASTA 파일 불러오는 기능  (0) 2022.08.22

이거 근데 우분투 놋북이 뭐가 불만인지 키보드가 안먹히데... 
커널 올려줘도 난리여... 

안그래도 오늘 진상 만나서 힘들었는데 너까지 왜그러냐... 


import sys
a = int(sys.stdin.readline())
yaksu = []
for i in range(1,a+1):
    if a % i == 0:
        yaksu.append(i)
yaksu_text = ''.join(str(yaksu))
print('{}의 약수는 {}입니다. '.format(a,yaksu_text))
if sum(yaksu) == 2 * a:
    print('{}는 자기 자신을 제외한 약수들의 합이 {}와 동일하므로 완전수입니다. '.format(a,a))

전체 코드는 뜯어보고 자시고 할 것도 없음. 완전수는 자기 자신을 제외한 모든 약수들을 더했을 때 자기 자신이 나와야 완전수이고 6과 28이 대표적인 완전수임. (1+2+3=6, 1+2+4+7+14=28) 여기까지만 봤을 때 그래서 곱하기 2를 넣은건가? 하고 감이 오시는 분도 계시겠지만 자기 자신을 제외하고 자기 자신과 합이 같다=모든 약수를 더했을 때 합이 자기 자신의 두 배라는 얘기임. 즉 6은 1+2+3+6=12(6+6=12), 28은 1+2+4+7+14+28=56(1+2+4+7+14=28)이라는 얘기. 참 쉽죠?

로직 자체는 쉬워요 갑자기 키보드가 안먹어서 글치...

 

진지하게 다음달에 월급타면 놋북 알아봐야 하나 고민했음... 이거 셋업도 귀찮은데.

NEB Filter 추가

이제 NEB에서 파는 제한효소들만 볼 수 있습니다. (NEB: 뉴 잉글랜드 바이오랩스. NEB cutter 만든 거기 맞음)

 

Cutter/Finder의 저장 형식 변경

일부 저장 형식이 변경되었습니다. 

Cutter
Finder
Searcher(하는김에 수정)

 

저장 디렉토리 출력

저장 시 현재 디렉토리를 출력해줍니다. 

 

FASTA파일 관련 문제 수정

1. FASTA 파일에 시퀀스가 소문자로 기록되어 있을 경우 제대로 못 찾던 문제를 수정했습니다. 
2. FASTA 파일에 >가 여러개 일 경우 에러가 뜨는 대신 맨 위에 있는 >로 진행합니다. 

참고로 왜 NEB냐면 거기껄 제일 많이 썼음. 

 

Genbank 파일 지원

문제가 하나 있는데 리드랑 파스랑 뭔 차인지 모르겠음. FASTA는 지에딧으로 열리는거라 보기라도 했지... Genbank는 gb파일이라 우분투에서 못열어요... 게임보이 어드밴스 파일이래... 

혹시 차이 아시는 분 제보 바랍니다. 일단 Read Parse는 FASTA랑 비슷하게 돌아감. 

 

기타 수정사항

1. 커터 파인더 공통으로 FASTA, Genbank를 불러올 경우 description영역에 있는 부분도 가져와서 기록합니다. 수동 입력의 경우 수동입력으로 따로 저장됩니다. 
2. 커터의 경우 효소 하나당 최소 두 줄 차지합니다. (이름/시퀀스/컷수 + 어디)

패치노트

1. 꺾쇠(>)가 하나인 FASTA 파일 한정으로 읽어올 수 있습니다. (Biopython이 꺾쇠 개수에 따라 불러오는 방식이 다름)

2. FASTA 파일을 불러올 경우, 시퀀스 이름란에 FASTA file의 ID영역이 들어갑니다. (사실 description 넣으려다가 너무 길어서...)

3. FASTA 파일을 불러오는 데 성공할 경우에도 멘트가 출력됩니다. (실패할때는 당연히 출력됨)


개고생의 흔적

from argparse import FileType
import tkinter
from tkinter import filedialog
from Bio import SeqIO
# 정신사나워서 불러오는거랑 표 분리했습니다...OTL

이쪽이 모듈이다. (뭐가 많음)

 

FASTA_open = input('FASTA 파일을 불러오시겠습니까? 불러오실거면 FASTA 혹은 fasta를 임력해주세요. ').upper()
if FASTA_open == 'FASTA':
    root = tkinter.Tk()
    root.withdraw()
    dir_path = filedialog.askopenfilename(parent=root,initialdir="/home/koreanraichu",title='Please select a directory',filetypes = (("*.fasta","*fasta"),("*.faa","*faa")))
    try: 
        fasta_read = SeqIO.read(dir_path,'fasta')
        sequence_name = fasta_read.id
        sequence = str(fasta_read.seq)
        # 단식으로만 가져오게 함. 
        print(dir_path,'FASTA 파일을 가져왔습니다! ')
    except: 
        print('이 FASTA파일은 한 파일에 여러 개가 기록되어 있어서 가져올 수 없습니다! ')
        # 그래서 parse로 가져와야 하는 파일이면 에러떠여 
else: 
    sequence_name = input("검색할 시퀀스의 이름을 입력해주세요: ")
    sequence = input("검색할 시퀀스를 입력해주세요: ")
    # 시퀀스 입력하는 란

Finder의 경우 효소 변수도 저 위로 빼버렸다. 

 

Cutter(with FASTA)
Finder(with FASTA)

이거 사용자가 FASTA 입력한건지 직접 입력한건지도 써 주면 좋을 듯 하다. 그거랑 별개로 Cutter에 NEB에서 파는효소 or 전체 효소로 필터 주는 것도 재밌을듯. NEB 의문의 홍보행 

정말! 개 노가다끝에... 드디어 해냈음... 


AccI, 정규식 필요
EcoRI, 정규식 불필요


일단 도입하는 것 자체는 커터에서 했기때문에 크게 어려운 부분은 없었고, 문제가 좀 있었다. 

1. 함수 정의하고 뺑뺑이를 돌렸는데 알파벳이 자꾸 하나만 바뀌는 문제(수정함) 
2. 정규식은 찾아바꾸기가 안된다. (sub()은 토씨 하나 안 틀리고 바꿔주는거라 G..T로 찾고 G..T/로 바꾸면 모든 시퀀스가 죄다 G..T/로 바뀐다)

첫 번째 문제는 해결했고 두번째 문제의 경우 찾아바꾸기가 안돼서 정규식이 필요한 효소들은 어떻게 자르는지가 위에만 표시되어 있다. 살려줘요. 

분량+저녁크리로 인해 2부작으로 나눠버림... 

 

이 글의 re모듈은 파이썬에서 사용하는거지만, 앞 글에 있는 기호와 메타문자는 언어를 불문하고 정규 표현식에서 다 쓰인다. 

https://koreanraichu.tistory.com/118

 

정규식(Regular Expression)-기호와 메타문자

문자 찾는 것 자체는 find()도 해주는데, 얘는 딱 정확하게 일치하는 문자열만 찾아준다. 그럼 정규식은? 그건 대충 와일드카드같은 거다. 그러니까 find()는 소라빵 찐빵 팥빵 붕

koreanraichu.tistory.com

다른 언어를 사용한다고 해도 앞 글은 한번정도는 읽어보자. 


re.search()

자... 전에도 서술했지만, find()는 정규식을 주면 그 정규식이랑 일치하는 걸 찾지 정규식으로 검색하질 않아요... 

 

print(re.search('G...',DNA))
<re.Match object; span=(1, 5), match='GAGG'>

단식

 

for i in range(len(df)):
    enzyme = df['Enzyme'][i]
    if re.search('A.....',enzyme):
        print(enzyme)
Acc36I
AccB1I
AccB2I
AccB7I
AceIII
AflIII
Alw21I
Alw26I
Ama87I
AmaCSI
ApaORI
AquIII
Asp10HII
Asp26HI
Asp27HI
Asp35HI
Asp36HI
Asp40HI
Asp50HI
Asp700I
Asp745I
Asp59I
AsuIII
AsuC2I
AsuHPI

복식

일단 자세한건 re.match()에서 서술하겠지만, 일단 re.search()가 정규식 검색할 때 쓰는 게 맞다. 

 

re.match()

print(re.match('G...',DNA))
None

re.search()와 달리 re.match()는 처음부터 정규식 형식이랑 일치하는 걸 찾아준다. 쟤가 그래서 왜 None이냐... 

DNA="AGAGGTTAAAGAACAAAGGCTTACTGTGCGCAGAGGAACGCCCATTTAGCGGCTGGCGTTTTGAATCCTCGGTCCCCCTTGTCTATCCAGATTAATCCAATTCCCTCATTTAGGACCCTACCAAGTCAACATTGGTATATGAATGCGACCTCGAAGAGGCCGCCTAAAAATGACAGTGGTTGGTGCTCTAAACTTCATTTGGTTAACTCGTGTATCAGCGCGATAGGCTGTTAGAGGTTTAATATTGTATGGCAAGGTACTTCCGGTCTTAATGAATGGCCGGGAAAGGTACGCACGCGGTATGGGGGGGTGAAGGGGCGAATAGACAGGCTCCCCTCTCACTCGCTAGGAGGCAATTGTATAAGAATGCATACTGCATCGATACATAAAACGTCTCCATCGCTTGCCCAAGTTGTGAAGTGTCTATCACCCCTAGGCCCGTTTCCCGCATATTAACGCCTGATTGTATCCGCATTTGATGCTACCGTGGTTGAGTCAGCGTCGAGCACGCGGCACTTATTGCATGAGTAGAGTTGACTAAGAGCCGTTAGATGCCTCGCTGTACTAATAGTTGTCGACAGATCGTCAAGATTAGAAAACGGTAGCAGCATTATCGGAGGTTCTCTAACTAGTATGGATAGCCGTGTCTTCACTGTGCTGCGGCTACCCATCGCCTGAAAACCAGTTGGTGTTAAGCGATCCCCTGTCCAGGACGCCACACGTAGTGAAACATACACGTTCGTCGGGTTCACCCGGGTCGGATCTGAGTCGACCAAGGACACACTCGAGCTCCGATCCCTACTGTCGAGAAATTTGTATCCCGCCCCCGCAGCTTGCCAGCTCTTTGAGTATCATGGAGCCCATGGTTGAATGAGTCCAATAACGAACTTCGACATGATAAAGTCCCCCCCTCGCGACTTCCAGAGAAGAAGACTACTGAGTTGAGCGTTCCCAGCACTTCAGCCAAGGAAGTTACCAATTTTTAGTTTCCGAGTGACAC"

원본 시퀀스가 이거였음. G로 시작하는 게 첫 번째 글자가 아니라 두 번째 글자기때문에 첫 빠따부터 에이 뭐야 없어가 되버린다. 

 

print(re.match('.G...',DNA))
print(re.search('.G...',DNA))
<re.Match object; span=(0, 5), match='AGAGG'>
<re.Match object; span=(0, 5), match='AGAGG'>

이거는 둘 다 똑같은 결과가 나온다. (정규식 형식은 어쨌든 일치하므로)

 

re,match()의 메소드들

이거 참고로 단독소환 쌉가능. 

 

records = re.finditer('A.{4}C',DNA)
for r in records:
    print(r.group(),r.start(),r.end(),r.span())

finditer에 대해서는 밑에서 또 설명할 예정이니까 이것부터 보고 갑시다. 저기 r.뭐시기 된 저거요. 저게 re.match의 메소드인데 순서대로 

1) 그룹(문자열)
2) 시작
3) 끝
4) span

이렇게 된다. 출력은 

AAGAAC 8 14 (8, 14)

이렇게 된다. 

 

re.findall()

이름을 보면 아시겠지만 그냥 다 찾아주는 애다. 반복문 데려 올 필요도 없다. 

 

print(re.findall('G...',DNA))
['GAGG', 'GAAC', 'GGCT', 'GTGC', 'GCAG', 'GGAA', 'GCCC', 'GCGG', 'GGCG', 'GAAT', 'GGTC', 'GTCT', 'GATT', 'GGAC', 'GTCA', 'GGTA', 'GAAT', 'GCGA', 'GAAG', 'GGCC', 'GCCT', 'GACA', 'GTGG', 'GGTG', 'GGTT', 'GTGT', 'GCGC', 'GATA', 'GGCT', 'GTTA', 'GAGG', 'GTAT', 'GGCA', 'GGTA', 'GGTC', 'GAAT', 'GGCC', 'GGGA', 'GGTA', 'GCAC', 'GCGG', 'GGGG', 'GGGT', 'GAAG', 'GGGC', 'GAAT', 'GACA', 'GGCT', 'GCTA', 'GGAG', 'GCAA', 'GTAT', 'GAAT', 'GCAT', 'GCAT', 'GATA', 'GTCT', 'GCTT', 'GCCC', 'GTTG', 'GAAG', 'GTCT', 'GGCC', 'GTTT', 'GCAT', 'GCCT', 'GATT', 'GTAT', 'GCAT', 'GATG', 'GTGG', 'GAGT', 'GCGT', 'GAGC', 'GCGG', 'GCAT', 'GAGT', 'GAGT', 'GACT', 'GAGC', 'GTTA', 'GATG', 'GCTG', 'GTTG', 'GACA', 'GATC', 'GTCA', 'GATT', 'GAAA', 'GGTA', 'GCAG', 'GGAG', 'GTTC', 'GTAT', 'GGAT', 'GCCG', 'GTCT', 'GTGC', 'GCGG', 'GCCT', 'GAAA', 'GTTG', 'GTGT', 'GCGA', 'GTCC', 'GGAC', 'GCCA', 'GTAG', 'GAAA', 'GTTC', 'GTCG', 'GGTT', 'GGGT', 'GGAT', 'GAGT', 'GACC', 'GGAC', 'GAGC', 'GATC', 'GTCG', 'GAAA', 'GTAT', 'GCCC', 'GCAG', 'GCCA', 'GCTC', 'GAGT', 'GGAG', 'GGTT', 'GAAT', 'GAGT', 'GAAC', 'GACA', 'GATA', 'GTCC', 'GCGA', 'GAGA', 'GAAG', 'GAGT', 'GAGC', 'GTTC', 'GCAC', 'GCCA', 'GGAA', 'GTTA', 'GTTT', 'GAGT', 'GACA']

근데 위치까지 준다고는 안 했다. 

 

re.finditer()

print(re.finditer('A..C',DNA))
<callable_iterator object at 0x7f009c5f8100>

얘는 다른 메소드들처럼 걍 쌩으로 print문에 넣으면 안된다. 저렇게 뜬다. 

 

records = re.finditer('A..C',DNA)
for r in records:
    print(r)
<re.Match object; span=(16, 20), match='AGGC'>
<re.Match object; span=(37, 41), match='ACGC'>
<re.Match object; span=(63, 67), match='AATC'>
<re.Match object; span=(84, 88), match='ATCC'>
<re.Match object; span=(93, 97), match='AATC'>
<re.Match object; span=(99, 103), match='ATTC'>
<re.Match object; span=(114, 118), match='ACCC'>
<re.Match object; span=(123, 127), match='AGTC'>
<re.Match object; span=(142, 146), match='ATGC'>
<re.Match object; span=(156, 160), match='AGGC'>
<re.Match object; span=(189, 193), match='AAAC'>
<re.Match object; span=(205, 209), match='ACTC'>
<re.Match object; span=(224, 228), match='AGGC'>
<re.Match object; span=(290, 294), match='ACGC'>
<re.Match object; span=(294, 298), match='ACGC'>
<re.Match object; span=(323, 327), match='AGAC'>
<re.Match object; span=(327, 331), match='AGGC'>
<re.Match object; span=(340, 344), match='ACTC'>
<re.Match object; span=(350, 354), match='AGGC'>
<re.Match object; span=(366, 370), match='ATGC'>
<re.Match object; span=(370, 374), match='ATAC'>
<re.Match object; span=(381, 385), match='ATAC'>
<re.Match object; span=(388, 392), match='AAAC'>
<re.Match object; span=(428, 432), match='ACCC'>
<re.Match object; span=(434, 438), match='AGGC'>
<re.Match object; span=(455, 459), match='ACGC'>
<re.Match object; span=(467, 471), match='ATCC'>
<re.Match object; span=(478, 482), match='ATGC'>
<re.Match object; span=(493, 497), match='AGTC'>
<re.Match object; span=(507, 511), match='ACGC'>
<re.Match object; span=(542, 546), match='AGCC'>
<re.Match object; span=(551, 555), match='ATGC'>
<re.Match object; span=(596, 600), match='AAAC'>
<re.Match object; span=(639, 643), match='AGCC'>
<re.Match object; span=(665, 669), match='ACCC'>
<re.Match object; span=(678, 682), match='AAAC'>
<re.Match object; span=(693, 697), match='AAGC'>
<re.Match object; span=(698, 702), match='ATCC'>
<re.Match object; span=(712, 716), match='ACGC'>
<re.Match object; span=(717, 721), match='ACAC'>
<re.Match object; span=(727, 731), match='AAAC'>
<re.Match object; span=(731, 735), match='ATAC'>
<re.Match object; span=(750, 754), match='ACCC'>
<re.Match object; span=(766, 770), match='AGTC'>
<re.Match object; span=(778, 782), match='ACAC'>
<re.Match object; span=(782, 786), match='ACTC'>
<re.Match object; span=(794, 798), match='ATCC'>
<re.Match object; span=(817, 821), match='ATCC'>
<re.Match object; span=(857, 861), match='AGCC'>
<re.Match object; span=(873, 877), match='AGTC'>
<re.Match object; span=(901, 905), match='AGTC'>
<re.Match object; span=(930, 934), match='AGAC'>
<re.Match object; span=(961, 965), match='AGCC'>
<re.Match object; span=(996, 1000), match='ACAC'>

Biopython에서 parse로 불러오는것마냥 for문이 필요하다. 

 

전방탐색

text='http://localhost:8888/notebooks/re.search.ipynb'

이런 텍스트가 있을 때 

p = re.compile(".+:")
print(p.search(text))

보통은 이렇게 찾는다. 근데 이 방식에는 문제가 있어요... 정규식이 복잡해지면 정규식 짜다가 

이러고 밥상 엎는다. 

 

긍정형 전방 탐색

p = re.compile(".+(?=:)")
print(p.search(text))
<re.Match object; span=(0, 16), match='http://localhost'>

?=가 들어가는 것. 저 식의 경우 :가 있으면 응 있네 여이따 하고 찾아는 주지만 출력을 안 한다. (저거는 Jupyter URL로 해서 그렇다)

 

부정형 전방 탐색

여러분들은 불신의 아이콘에 대해 아시는가? 

얘 맞다. 

그러니까 이런 거. 

 

블로그에서 뭔가를 검색했는 데 저게 보이면 일단 거르고 보잖음. 근데 쟤랑 그거랑 뭔 상관이냐고? 자, 생각해봅시다... 일반적으로 맛집을 찾을 때 저런 바이럴들이 많이 나오니까 우리는 저것들을 피하기 위한 키워드를 정해서 맛집을 찾잖음? (오빠랑 맛집 뭐 이런걸로다가) 부정형 전방 탐색은 맛집 검색을 하면서 아예 저 불신의 아이콘을 빼버리고 찾는 거라고 보면 됨. 

 

file_list=['buchu.jpg','moon.jpg','text.txt','py.py','BOJ.ipynb','jemok.png','pyo.csv']

그러니까 이런 파일들이 있을 때 jpg파일을 빼려면 txt, py, ipynb, png, csv파일을 묶어야 하는데 정규식에 어느 세월에 저걸 다 쓰고 앉았음? 

for i in file_list:
    if re.match('.*[.](?!jpg$).*$',i):
        print(i)
text.txt
py.py
BOJ.ipynb
jemok.png
pyo.csv

이럴 때 부정형 전방탐색으로 jpg를 빼버리면 된다. 정확히는 jpg가 포함되지 않은 것만 보여준다. 

 

sub(), subn()

p=re.compile('W')
p.sub('[A|T]','CCWGG')

이런 식으로 쓰면 W가 A or T로 바뀌어서 나온다. 

 

p=re.compile('N')
p.sub('.','GATNNNATCNNNNNNNN',count=3)

이런 식으로 바꿀 횟수를 지정하는 것도 가능하다. (count=n)

 

('GAT...ATC........', 11)

subn()도 쓰는 방법은 비슷한데, 얘는 반환 형태가 튜플이고 몇 글자 바꿨는지가 나온다. 

 

re.compile()

정규식 일일이 쓰기 귀찮을 때 쓰면 좋다. 

 

p = re.compile('G....C')
print(p.search(DNA))

compile에 옵션을 지정하고, 그 다음 match나 search, 파인드올 돌리면 된다. 

p = re.compile('t.....t',re.I)
print(p.findall(DNA))

옵션은 이런 식으로 붙고, 총 네 개가 있다. 

 

re.compile()의 옵션

1)Dotall(S):와일드카드를 ㄹㅇ 와일드카드로 쓸 수 있다. 저 옵션이 없으면 개행문자는 와일드카드로 대체가 안되는데, 저 옵션을 붙여벼리면 아 그런건 모르겠고 와일드카드! 가 된다. 

2) Ignorecase(I): 대소문자를 무시하고 그냥 일치하는 문자열을 찾아준다. 그러니까 저 옵션을 주면 A랑 a랑 같아진다. (원래 파이썬은 대소문자 칼같이 구별한다) 

3) Multiline(M): 여러 줄로 된 문자열을 줄단위로 인식한다. 그러니까 이게 무슨 말이냐... 

나 보기가 역겨워
가실 때에는
말없이 고이 보내 드리오리다
 
영변에 약산
진달래꽃
아름 따다 가실 길에 뿌리오리다
  
가시는 걸음걸음
놓인 그 꽃을
사뿐히 즈려밟고 가시옵소서
 
나 보기가 역겨워
가실 때에는
죽어도 아니 눈물 흘리오리다

이 시는 김소월의 진달래꽃이다. 마야 아니다 우리가 볼 때는 4연이고 각 연마다 3행인데, Multiline이 없는 정규식 처리에서는 저걸 그냥 한 줄로 본다. 행이고 연이고 그런건 모르겠고 걍 한줄이여! 이렇게 되버리는데 Multiline 옵션이 들어가면 12줄짜리 텍스트(연과 연 사이 개행문자 포함 15줄)로 본다. 

 

4) Verbose(X): 정규식에 주석을 넣을 수 있다. 코드와 마찬가지로 정규식도 복잡해지면 

이렇게 된다. (아니면 머리에 블루스크린이 뜨거나) Verbose 옵션을 주면 그걸 방지하고자 주서을 넣을 수 있게 된다. 

문자 찾는 것 자체는 find()도 해주는데, 얘는 딱 정확하게 일치하는 문자열만 찾아준다. 그럼 정규식은? 그건 대충 와일드카드같은 거다. 그러니까 find()는 소라빵 찐빵 팥빵 붕어빵 이런 식으로 딱딱 키워드를 찾는거고 정규식은 *빵으로 소라빵 찐빵 붕어빵 팥빵 다 찾는 거지. 

참고로 정규식을 쓰려면 re를 불러야 한다. 

import re

ㄱㄱ


정규식 문자

정규식에서 쓰는 문자들과 이게 뭐 하는건지를 간단히 알아보자. 

 

.

Wildcard(구글 검색에서 *)

 

for i in range(len(pokemon)):
    if re.search('김.',pokemon[i]):
        print(pokemon[i])
김부추
김후추
김양상추
박알타리김치
고등어김치찜
김배추

참고로 전부 본인 포켓몬 이름임. 왜요 

 

^

~로 시작하는 단어

 

for i in range(len(pokemon)):
    if re.search('^김',pokemon[i]):
        print(pokemon[i])
김부추
김후추
김양상추
김배추

꺾쇠가 들어가면 ~로 시작하는 단어를 찾아달라는 얘기. 꺾쇠를 단어 앞에 쓴다. 

 

$

for i in range(len(pokemon)):
    if re.search('!$',pokemon[i]):
        print(pokemon[i])
앗!잘못던짐!
고객님!

~로 끝나는 단어를 찾아달라는 얘기. 달러를 뒤에 쓴다. 이거 ^랑 헷갈리면 안된다. 

 

|

for i in range(len(df)):
    enzyme = df['Enzyme'][i]
    sequence = df['sequence'][i]
    if re.search('W|S',sequence):
        print(enzyme,sequence)

OR. 햄버거|피자는 햄버거 or 피자라는 얘기. 참고로 대문자 I나 소문자 L이 아니고 쉬프트+백슬래시(\)이다. 

 

[ ]

for i in range(len(df)):
    enzyme = df['Enzyme'][i]
    sequence = df['sequence'][i]
    if re.search('[NSW]',sequence):
        print(enzyme,sequence)

일단 하는 역할 자체는 OR이랑 비슷하다. 대괄호 안에 있는것들 중 하나를 포함하고 있는 것. Cutter에 있는 제한효소 인식 시퀀스도 전부 대괄호로 치환한 것. 

 

[^]

for i in range(len(df)):
    enzyme = df['Enzyme'][i]
    sequence = df['sequence'][i]
    if re.search('^Ba[^c]',enzyme):
        print(enzyme,sequence)
BaeI ACNNNNGTAYC
Bal228I GGNCC
BamNII GGWCC
BanI GGYRCC
BanII GRGCYC
BasI CCANNNNNTGG
BavAII GGNCC
BavBII GGNCC

쓰는 법은 일반적인 ^ 쓰는것처럼 앞에 붙이면 된다. 단, 대괄호 안에 있는 꺾쇠는 [] 안에 있는것들 중 쟤는 빼라는 얘기. 

 

[-]

어디부터 어디까지라고 지정하는 것.

[a-z] : 영어 문자의 모든 범위를 지정한다.
[가-힣] : 한글 문자의 모든 범위를 지정한다.
[0-9] : 숫자의 모든 범위를 지정한다.

 

( )

for i in range(len(df)):
    enzyme = df['Enzyme'][i]
    sequence = df['sequence'][i]
    if re.search('(Eco)',enzyme):
        print(enzyme,sequence)
Eco24I GRGCYC
Eco31I GGTCTC
Eco47I GGWCC
Eco57I CTGAAG
Eco64I GGYRCC
Eco81I CCTNAGG
Eco88I CYCGRG
Eco91I GGTNACC
Eco130I CCWWGG
Eco1831I CCSGG
EcoA4I GGTCTC
EcoHI CCSGG
EcoHK31I YGGCCR
Eco75KI GRGCYC
Eco57MI CTCRAG
EcoNI CCTNNNNNAGG
EcoO44I GGTCTC
EcoO65I GGTNACC
EcoO109I RGGNCCY
EcoO128I GGTNACC
EcoP15I CACGAG
EcoR124II GAANNNNNNNRTCG
EcoRII CCWGG
EcoT14I CCWWGG
EcoT38I GRGCYC
Eco13kI CCNGG
Eco21kI CCNGG
Eco27kI CYCGRG
Eco137kI CCNGG

괄호 안에 있는 걸 그룹으로 묶어서 찾는다. [붕어빵]은 붕 아님 어 아님 빵 들어간건 다 찾아주는데 (붕어빵)은 붕어빵만 찾는다. 

 

메타 문자

백슬래시 뒤에 뭔가 오는 것. 

 

\d, \D

숫자 포함, 숫자 제외

 

for i in range(len(pokemon)):
    if re.search('\d',pokemon[i]):
        print(pokemon[i])
대포동1호
김2번척추씨

대포동1호 뭐예요 제 파이어로요

 

for i in range(len(df)):
    enzyme = df['Enzyme'][i]
    sequence = df['sequence'][i]
    if re.search('^A..\D',enzyme):
        print(enzyme,sequence)
AarI CACCTGC
AccI GTMKAC
AccB1I GGYRCC
AccB2I RGCGCY
AccB7I CCANNNNNTGG
AceI GCWGC
AceIII CAGCTC
AclWI GGATC
AcoI YGGCCR
AcpII CCANNNNNTGG
AcrII GGTNACC
AcsI RAATTY
AcuI CTGAAG
AcyI GRCGYC
AdeI CACNNNGTG
AeuI CCWGG
AfiI CCNNNNNNNGG
AflI GGWCC
AflIII ACRYGT
AglI CCWGG
AgsI TTSAA
AhaI CCSGG
AhaII GRCGYC
AhdI GACNNNNNGTC
AjnI CCWGG
AjuI GAANNNNNNNTTGG
AlfI GCANNNNNNTGC
AloI GAACNNNNNNTCC
AlwI GGATC
AlwNI CAGNNNCTG
AlwXI GCAGC
AmaCSI GCTCCA
AocI CCTNAGG
AocII GDGCHC
AorI CCWGG
AosII GRCGYC
ApaBI GCANNNNNTGC
ApaORI CCWGG
ApeKI GCWGC
ApoI RAATTY
ApyI CCWGG
ApyPI ATCGAC
AquI CTCGRG
AquII GCCGNAC
AquIII GAGGAG
AquIV GRGGAAG
ArsI GACNNNNNNTTYG
AseII CCSGG
AspI GACNNNGTC
AspAI GGTNACC
AspEI GACNNNNNGTC
AspHI GWGCWC
AspNI GGNNCC
AstWI GRCGYC
AsuI GGNCC
AsuIII GRCGYC
AsuC2I CCSGG
AsuHPI GGTGA
AtsI GACNNNGTC
AvaI CYCGRG
AvaII GGWCC
AvcI GGNCC

소문자 d는 숫자 포함이고 대문자 D는 제외이다. 근데 이 케이스의 경우 ^A..\D보다는 .에 반복 주고 \D로 쓰는 쪽이 더 나을듯... 알파벳 두 글자 이후로 숫자가 오는 제한효소는 리스트업이 안됐는데, AsuC2I처럼 알파벳 세글자 다음에 숫자가 있는 제한효소는 나왔다. 

 

\s, \S

for i in range(len(list_n)):
    if re.search('a\s',list_n[i]):
        print(list_n[i])
a bc
a b c

이게 \s(공백 포함)

 

for i in range(len(list_n)):
    if re.search('a\S',list_n[i]):
        print(list_n[i])
abc
ab c
abab

얘는 \S(공백 제외)이다. 

 

\b, \B

이건 설명하기가 좀 애매한디... 

 

text='he said checkmate for my class mate, before she move her knight.'

이 구문이 있을 때 

 

p = re.compile(r'\bmate')
p.search(text)

이걸 치면 class mate의 mate가 나온다. 

p = re.compile(r'\Bmate')
p.search(text)

이렇게 주면 checkmate의 mate가 나온다. 얘는 단어와 단어 사이의 공백을 따지기 때문. 

 

\w, \W

각각 숫자와 알파벳, 숫자와 알파벳 제외. 

 

for i in range(len(df)):
    enzyme = df['Enzyme'][i]
    sequence = df['sequence'][i]
    if re.search('^X..\w',enzyme):
        print(enzyme,sequence)
XagI CCTNNNNNAGG
XapI RAATTY
XceI RCATGY
XcmI CCANNNNNNNNNTGG
XhoII RGATCY
XmiI GTMKAC
XmnI GAANNNNTTC

이게 \w고 

 

for i in range(len(df)):
    enzyme = df['Enzyme'][i]
    sequence = df['sequence'][i]
    if re.search('^X..\W',enzyme):
        print(enzyme,sequence)

이게 \W. 제한효소 이름은 숫자와 알파벳만으로 되어 있으므로 저렇게 주고 찾으면 검색결과가 없는 게 정상이다. 

 

\A, \Z

문자열의 시작과 끝. ^과 $에 대응하지만 Multiline 옵션을 주더라도 얘는 문자열의 시작과 끝만 출력한다. 

 

*, *?

반복을 나타내는 문자. 0회 이상 반복이면 다 찾는다. 

 

for i in range(len(pokemon)):
    if re.search('김*추',pokemon[i]):
        print(pokemon[i])
김부추
김후추
김양상추
김배추
김2번척추씨

김과 추 사이에 뭐 들어가면 다 나온다. 

 

+, +?

얘는 한 번 이상 반복되면 다 나온다. 

 

for i in range(len(df)):
    enzyme = df['Enzyme'][i]
    sequence = df['sequence'][i]
    if re.search('^Ca+',enzyme):
        print(enzyme,sequence)
Cac8I GCNNGC
CaiI CAGNNNCTG
CauI GGWCC
CauII CCSGG

C로 시작하면서 a가 한 번 이상 들어가는 제한효소를 출력한다. 

 

?

for i in range(len(df)):
    enzyme = df['Enzyme'][i]
    sequence = df['sequence'][i]
    if re.search('^Ca?',enzyme):
        print(enzyme,sequence)
Cac8I GCNNGC
CaiI CAGNNNCTG
CauI GGWCC
CauII CCSGG
CbrI CCWGG
CcuI GGNCC
CelII GCTNAGC
CfrI YGGCCR
Cfr10I RCCGGY
Cfr13I GGNCC
CfrBI CCWWGG
CjeI CCANNNNNNGT
CjePI CCANNNNNNNTC
CpoI CGGWCCG
CspI CGGWCCG
CspCI CAANNNNNGTGG
Csp68KI GGWCC
CstMI AAGGAG
CthII CCWGG
CviBI GANTC
CviJI RGCY
CviTI RGCY
CvnI CCTNAGG

0회 or 1회 이상 반복. ...이럴거면 그냥 C로 시작하는 거 뽑고 말지... 

 

{ }

중괄호를 주면 반복 횟수를 지정할 수 있다. 

 

for i in range(len(pokemon)):
    if re.search('김.{1}추',pokemon[i]):
        print(pokemon[i])

아무거나 1회 반복

 

for i in range(len(pokemon)):
    if re.search('김.{1,3}추',pokemon[i]):
        print(pokemon[i])

아무거나 1~3회 반복

 

for i in range(len(df)):
    enzyme = df['Enzyme'][i]
    sequence = df['sequence'][i]
    if re.search('[GATC]{3}N{3}[GATC]',sequence):
        print(enzyme,sequence)
AdeI CACNNNGTG
AlwNI CAGNNNCTG
AspI GACNNNGTC
AtsI GACNNNGTC
BstZ316I CACNNNGTG
CaiI CAGNNNCTG
DraIII CACNNNGTG
PflFI GACNNNGTC
PsyI GACNNNGTC
TelI GACNNNGTC
Tth111I GACNNNGTC

GATC 중 아무거나 3개 반복되고 N이 3개 온 다음 GATC가 오는 패턴

Finder는 도입하려면 좀 걸립니다... 얘는 아예 정규식+찾아바꾸기가 필요한거라...... 


정규식 도입

import pandas as pd
import re
from datetime import datetime
enzyme_table = pd.read_csv('/home/koreanraichu/restriction.csv')
enzyme_table2 = pd.read_csv('/home/koreanraichu/restriction_RE.csv')
# 정규식 도입을 위해... 어쩔 수 없이 합쳤음... 
enzyme_table = pd.concat([enzyme_table,enzyme_table2])
enzyme_table = enzyme_table.sort_values('Enzyme')
enzyme_table.reset_index(inplace=True)
# 합쳤다... 
print(enzyme_table)

뭐가 좀 많은데, RE 들어가는 csv파일이 정규식 처리가 필요한 효소들. 인식 시퀀스에 N, S, B같은 게 있는 효소들이다. 정규식 없이 find를 쓰면 못 찾기때문에 뺐다. 

 

class RE_treatment:
    def RE_wildcard(self,before_seq):
        self.before_seq = before_seq
        before_seq = before_seq.replace("N",".")
        return before_seq
    # Wildcard: 시퀀스 데이터에 N이 있을 경우 Wildcard로 바꾼다. 
    def RE_or(self,before_seq):
        self.before_seq = before_seq
        if "B" in before_seq:
            before_seq = before_seq.replace("B","[CGT]")
        elif "D" in before_seq:
            before_seq = before_seq.replace("D","[AGT]")
        elif "H" in before_seq:
            before_seq = before_seq.replace("H","[ACT]")
        elif "K" in before_seq:
            before_seq = before_seq.replace("K","[GT]")
        elif "M" in before_seq:
            before_seq = before_seq.replace("M","[AC]")
        elif "R" in before_seq:
            before_seq = before_seq.replace("R","[AG]")
        elif "S" in before_seq:
            before_seq = before_seq.replace("S","[CG]")
        elif "V" in before_seq:
            before_seq = before_seq.replace("V","[ACG]")
        elif "W" in before_seq:
            before_seq = before_seq.replace("W","[AT]")
        elif "Y" in before_seq:
            before_seq = before_seq.replace("Y","[CT]")
        return before_seq
    # Or: 시퀀스 데이터에 N 말고 ATGC 말고 다른 알파벳이 있을 경우, 해당하는 정규식 문법으로 바꾼다.

클래스. 대충 쿠키틀 해당 클래스는 N, D, B와 같은 알파벳들을 정규식 처리 하는 코드를 담고 있다. 

 

def convert (a):
    RE = RE_treatment()
    while True:
        if "N" in res_find:
            res_find_after = RE.RE_wildcard(res_find)
        elif "B" in res_find or "D" in res_find or "H" in res_find or "K" in res_find or "M" in res_find or "R" in res_find or "S" in res_find or "V" in res_find or "W" in res_find or "Y" in res_find: 
            res_find_after = RE.RE_or(res_find)
        else: 
            break
        return res_find_after

무식하게 조건문 때려박은 if문... 저 While True가 없으면 GCDGHC처럼 알파벳이 여러글자일 때에 대한 처리가 안된다. (N은 아예 일괄적으로 wildcard화 함)

 

if "N" in res_find or "B" in res_find or "D" in res_find or "H" in res_find or "K" in res_find or "M" in res_find or "R" in res_find or "S" in res_find:
    res_find_after = str(convert(res_find))
else: 
    res_find_after = res_find
# 정규식 처리(문자가 두 개 이상일때에 대한 처리가 필요함)
Findall = re.findall(res_find_after,sequence)
if Findall: 
    count += 1
    site_count = len(Findall)
    if site_count == 1:
    once_cut_list.append(enzyme)
elif site_count == 2: 
    two_cut_list.append(enzyme)
else: 
    multi_cut_list.append(enzyme)
res_loc_list = ', '.join(res_loc_list)
f.write("{0}: {1} {2},{3} times cut. Where(bp): {4} \n".format(enzyme,res_find,feature,site_count,res_loc_list))

(그래서 대충 문제의 코드)

 

어디 자르는지 세 주는 기능

얘는 쉽다. 

 

def cut_func (a,b):
    global res_loc_list
    locs = re.finditer(a,b)
    for i in locs:
        loc = i.start()
        res_loc_list.append(str(loc))
    return res_loc_list
# 여기가 위치 관련 함수입니다.

finditer()로 다 찾은 다음, 해당 레코드에서 start(시작 지점)을 리스트화하면 된다.

결과 

이제 Finder에 도입한 다음 FASTA까지 하면 어지간한건 다 된다. 

일단... 

해당 기능 추가 결과물이고요... 정규식 얘기는 나중에 입 털어드림... 


import re # 정규식용 모듈

정규식은 얘가 있어야 쓸 수 있다. 

 

elif keyword == "name":
    enzyme_RE = input("효소의 이름이 뭘로 시작하나요? ")
    enzyme_RE_2 = '^' + enzyme_RE

물론 if문에도 관련 코드를 추가했다. (^ 붙으면 그걸로 시작하는 걸 찾아준다)

 

else: 
    print("Enzyme with start with {0}".format(enzyme_RE))
    for i in range(len(enzyme_table)):
        DB_enzyme = str(enzyme_table['Enzyme'][i]).strip()
        DB_seq = str(enzyme_table['sequence'][i]).strip().upper()
        DB_site = str(enzyme_table['restriction_site'][i]).strip().upper()
        if re.search(enzyme_RE_2,DB_enzyme):
            print("{0} | {1} | {2}".format(DB_enzyme,DB_seq,DB_site))
# 간단 검색(머릿글자)

물론 처리하는 코드도 추가했지... 

'Coding > Python' 카테고리의 다른 글

정규식(Regular Expression)-기호와 메타문자  (0) 2022.08.22
Cutter 기능 추가: 정규식 도입  (0) 2022.08.22
cutter, finder, searcher에 앞으로 추가할 기능  (0) 2022.08.21
For vs While  (0) 2022.08.21
Finder & Cutter 패치  (0) 2022.08.21

FASTA 파일 지원

1) 일단 사용자가 FASTA파일을 읽어오게 되면 따로 시퀀스 정보 입력을 받을 필요가 없어서 그거 관련도니 처리가 필요함. (시퀀스 이름은 Biopython SeqIO로 FASTA파일 ID 가져올 예정)

2) Biopython의 경우 FASTA파일에 꺾쇠가 하나면 read, 여러개면 parse로 가져오는데 parse로 읽을 걸 read로 가져오면 에러뜸. 에러에 대한 처리가 필요하고... parse로 가져오는 파일은 꺾쇠가 여러개인데, 이게 다르게 말하면 ID랑 시퀀스라 여러개라 그거 읽어오면 여러개를 돌리고 저장까지 해야 하는거라 이거는 Jupyter단에서는 힘듭니다... OTL 그래서 단식만 읽을거임... 

3) FASTA 관련된 기능이긴 한데, 본인은 애초에 경로가 고정되어 있으니까 상관 없는데... 그리고 파일이 저장되는 위치가 Jupyter 코드가 있는 경로기도 하고(홈), 리눅스는 경로가 간단간단해요 생각보다... usr 이런거 아니면. 그래서 경로가 고정되어 있지만, 사용자들 입장에서 이러면 상당히 불편하기때문에... FASTA 파일 열 때 뿐 아니라 파일 저장할 때 저장 경로를 선택할 수 있는 창을 띄울 예정입니다... 아니 누가 자기 파일 경로를 다 외워... 

 

Searcher 기능 관련

1) Wildcard 기능(시퀀스 혹은 효소 이름)

2) restriction site에 N이나 W같은 알파벳이 있을 때 처리법... 이건 finder보다 cutter단에서 처리하는 게 쉽습니다. finder는 찾아서 텍스트를 대체해야 하니... 이거는 둘 다 정규식 찾아서 직접 해 보고 적응해야 합니다. N은 차라리 wildcard니까 적용하기 쉽지... W M 이런거는 or이라;;;; 

사실 언제 추가될지는 모르겠지만 일단 우선적으로 해보겠음. 

'Coding > Python' 카테고리의 다른 글

Cutter 기능 추가: 정규식 도입  (0) 2022.08.22
Searcher 기능 추가: 그 뭐더라 그 D로 시작하는 그거  (0) 2022.08.21
For vs While  (0) 2022.08.21
Finder & Cutter 패치  (0) 2022.08.21
Searcher 만들었음  (0) 2022.08.21

Profile

Lv. 34 라이츄

요즘 날씨 솔직히 에바참치김치꽁치갈치넙치삼치날치기름치준치학꽁치임..