본문으로 건너뛰기

Python 기본 문법 요약 (내가 보려고 만든)

Dev Python
목차
thumbnail

점프 투 파이썬” 위키독스를 공부하며 요약한 글입니다.

“점프 투 파이썬” 도서는 기본 문법을 정립하는데 매우 큰 도움이 되었습니다.

누군가 파이썬을 처음 공부한다고 하면, 과감히 이 책을 추천해주고 싶습니다.


Python 기본 문법이 조금 더 기억에 남았으면 하는 바람에서 이 블로그에 기록을 하였습니다.

또한, 필요할 때 CheatSheet로써 사용할 수 도 있겠네요.


📗 자료형
#

f 문자열 Formatting 표현식 지원
#

>>> count = 3
>>> f'I have {count+3} apples.'
'I have 6 apples.'

딕셔너리 사용
#

>>> x = {'name':'Ruff', 'year':1990}
>>> f'My name is {x["name"]}. I was born in {x["year"]}.'
'My name is Ruff. I was born in 1996.'

왼쪽 공백 지우기: lstrip
#

>>> hey = "              hello    "
>>> hey.lstrip()
'hello    '

오른쪽 공백 지우기: rstrip
#

>>> hey = "              hello    "
>>> hey.rstrip()
'              hello'

양쪽 공백 지우기: strip
#

>>> a = " hi "
>>> a.strip()
'hi'

리스트 요소 삭제: del
#

>>> test = [4, 5, 6]
>>> del test[2]
>>> test
[4, 5]

>>> test = [4, 5, 6, 7, 8, 9, 10, 11]
>>> del test[1:4]
>>> test
[4, 8, 9, 10, 11]

리스트에 요소 삽입: insert()
#

>>> test = [4, 5, 6]
>>> test.insert(0, 10)
>>> test
[10, 4, 5, 6]

리스트 요소 제거: remove()
#

>>> test = [1, 2, 3, 4, 5, 6]
>>> test.remove(2)
>>> test
[1, 3, 4, 5, 6]

리스트 요소 꺼내기: pop()
#

>>> test = [1, 2, 3, 4, 5, 6]
>>> test.pop()
6
>>> test
[1, 2, 3, 4, 5]

리스트 확장: extend()
#

>>> test = [3, 4, 5]
>>> test.extend([6, 7])
>>> test
[3, 4, 5, 6, 7]
>>> test222 = [6, 7]
>>> test.extend(test222)
>>> test
[3, 4, 5, 6, 7, 6, 7]

튜플
#

t1 = ()
t2 = (1,)
t3 = (1, 2, 3)
t4 = 1, 2, 3
t5 = ('a', 'b', ('ab', 'cd'))

딕셔너리 Key 리스트 조회: keys()
#

>>> a = {'name': 'ruff', 'phone': '010-1111-2222', 'birth': '0102'}
>>> a.keys()
dict_keys(['name', 'phone', 'birth'])

>>> for k in a.keys():
...    print(k)
...
name
phone
birth

Value 리스트 만들기: values()
#

>>> a.values()
dict_values(['ruff', '010-1111-2222', '0102'])

Key, Value 쌍 얻기: items()
#

>>> a.items()
dict_items([('name', 'pey'), ('phone', '010-0000-1111'), ('birth', '0406')])

특정 Key의 딕셔너리 내 존재 여부 확인: in
#

>>> c = {'name':'pey', 'phone':'010-0000-1111', 'birth': '0406'}
>>> 'name' in c
True

>>> 'email' in c
False

집합
#

>>> set1 = set(['a', 1, 3])
>>> set1
{1, 3, 'a'}

>>> set2 = set("name")
>>> set2
{'m', 'e', 'a', 'n'}

교집합, 합집합, 차집합
#

>>> x1 = set([1, 2, 3, 4, 5, 6])
>>> x2 = set([4, 5, 6, 7, 8, 9])

# 교집합
>>> x1 & x2
{4, 5, 6}

>>> x1.intersection(x2)
{4, 5, 6}

# 합집합
>>> x1 | x2
{1, 2, 3, 4, 5, 6, 7, 8, 9}

>>> x1.union(x2)
{1, 2, 3, 4, 5, 6, 7, 8, 9}

# 차집합
>>> x1 - x2
{1, 2, 3}

>>> x2 - x1
{8, 9, 7}

>>> x1.difference(x2)
{1, 2, 3}

>>> x2.difference(x1)
{8, 9, 7}

📗 변수
#

리스트 얕은 복사
#

= 이용하여 대입 시 얕은 복사가 되어 동일한 리스트(메모리)를 가리킴

>>> a = [1,2,3]
>>> b = a
>>> a[1] = 4
>>> a
[1, 4, 3]

>>> b
[1, 4, 3]

리스트 깊은 복사
#

데이터를 통째로 복사, 별도의 메로리 차지

1. [:] 이용하기
#

리스트 전체를 가리키는 [:]을 사용해서 복사하는 방법이다. a 리스트 값을 바꾸더라도 b 리스트에는 영향이 없다.

>>> a = [1, 2, 3]
>>> b = a[:]
>>> a[1] = 4

>>> a
[1, 4, 3]

>>> b
[1, 2, 3]

2. copy 모듈 이용하기
#

b = copy(a)는 b = a[:]과 동일하다. 두 변수의 값은 같지만, 서로 다른 객체를 가리키고 있는지 확인해 보자.

>>> from copy import copy
>>> a = [1, 2, 3]
>>> b = copy(a)
>>> b is a
False

리스트 자료형의 자체 함수인 copy 함수를 사용해도 동일한 결과를 얻을 수 있다.

>>> a = [1, 2, 3]
>>> b = a.copy()
>>> b is a
False

튜플 생성
#

다음과 같이 튜플로 a, b에 값을 대입할 수 있다.

>>> a, b = ('python', 'life')
>>> (a, b) = 'python', 'life'
>>> [a, b] = ['python', 'life']
>>> a = b = 'python'

Swap 방법

>>> a = 3
>>> b = 5
>>> a, b = b, a
>>> a
5

>>> b
3

📗 반복문 : 리스트 컴프리헨션
#

리스트 안에 for 문을 포함하여 직관적으로 표현합니다.

[표현식 for 항목 in 반복_가능_객체 if 조건문]
>>> a = [1, 2, 3, 4]
>>> result = [num * 3 for num in a]
>>> print(result)
[3, 6, 9, 12]

짝수에만 3을 곱하기

>>> a = [1, 2, 3, 4]
>>> result = [num * 3 for num in a if num % 2 == 0]
>>> print(result)
[6, 12]

복잡한 예제 - 구구단의 모든 결과를 리스트에 담기:

>>> result = [x * y for x in range(2, 10) for y in range(1, 10)]
>>> print(result)
[2, 4, 6, 8, 10, 12, 14, 16, 18, 3, 6, 9, 12, 15, 18, 21, 24, 27, 4, 8, 12, 16,
20, 24, 28, 32, 36, 5, 10, 15, 20, 25, 30, 35, 40, 45, 6, 12, 18, 24, 30, 36, 42
, 48, 54, 7, 14, 21, 28, 35, 42, 49, 56, 63, 8, 16, 24, 32, 40, 48, 56, 64, 72,
9, 18, 27, 36, 45, 54, 63, 72, 81]

📗 함수
#

여러 개의 입력값
#

매개변수 앞에 *을 붙여서 모든 입력 값을 튜플로 받습니다.

def add_all(*args):
    result = 0
    for i in args:
        result += i
    return result
>>> result = add_all(1, 2, 3)
>>> print(result)
6

>>> result = add_all(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
>>> print(result)
55

매개변수 앞에 *을 붙이면 입력값을 튜플로 만들어 줍니다.

def add_mul(choice, *args):
    if choice == "add":
        result = 0
        for i in args:
            result += i
    elif choice == "mul":
        result = 1
        for i in args:
            result *= i
    return result
>>> result = add_mul('add', 1, 2, 3, 4, 5)
>>> print(result)
15
>>> result = add_mul('mul', 1, 2, 3, 4, 5)
>>> print(result)
120

가변 인자 *args
#

여러 개의 인자를 받을 수 있습니다.

def sum_nums(*args):
    return sum(args)
>> print(sum_nums(1, 2, 3)) 
# Output: 6

키워드 매개변수 **kwargs
#

매개변수 앞에 **을 붙여 키워드 매개변수를 받을 수 있습니다.

**kwargs는 키워드 인수를 딕셔너리로 받아줍니다. (key:value)

def print_kwargs(**kwargs):
    print(kwargs)
>>> print_kwargs(a=1)
{'a': 1}

>>> print_kwargs(name='foo', age=3)
{'age': 3, 'name': 'foo'}

함수의 리턴값 항상 하나
#

함수는 여러 값을 리턴해도 하나의 튜플로 리턴합니다.

def add_and_mul(a, b):
    return a+b, a*b
>>> result = add_and_mul(1, 2)
>>> print(result)
(3, 2)

Default 값이 있는 매개변수
#

Default 값이 있는 인자는 없는 인자 뒤에 와야 합니다.

def iam(name, man=True, age=20):
    print(f"나의 이름은 {name}입니다.")
    print(f"나이는 {age}살입니다.")
    if man:
        print("남자입니다.")
    else:
        print("여자입니다.")

global 명령어 사용하기
#

함수 내부에서 전역 변수를 수정할 수 있습니다.

a = 1
def vartest():
    global a
    a += 1

vartest()
>>> print(a)
2

lambda 예약어
#

함수를 간결하게 만들 때 사용합니다.

add = lambda a, b: a + b
>>> result = add(3, 4)
>>> print(result)
7

lambda로 만든 함수는 return 명령어 없이도 표현식의 결괏값을 리턴합니다. 이는 다음과 같은 def 함수와 동일한 역할을 합니다.

def add(a, b):
    return a + b
>>> result = add(3, 4)
>>> print(result)
7

📗 입출력
#

한 줄에 결과 출력하기
#

print 함수의 end 매개변수를 사용하면 출력 후 줄바꿈을 하지 않고 이어서 출력할 수 있습니다.

for i in range(10):
    print(i, end=' ')

# Output
# 0 1 2 3 4 5 6 7 8 9

파일 읽기
#

with open("hello.txt", "r") as f:
    data = f.read()
    print(data)

파일 읽기 : readline 함수 이용하기
#

모든 줄을 읽기 위해 while 루프를 사용합니다.

with open("hello.txt", "r") as f:
    while True:
        line = f.readline()
        if not line:
            break
        print(line)

파일 읽기 : readlines 함수 사용하기
#

파일의 모든 줄을 읽어 리스트로 리턴합니다.

with open("hello.txt", "r") as f:
    for line in lines:
    print(line)

파일 읽기 : read 함수 사용하기
#

파일의 내용을 모두 읽습니다.

with open("hello.txt", "r") as f:
    data = f.read()
    print(data)

파일 객체를 for 문과 함께 사용
#

with open("hello.txt", "r") as f:
    for line in f:
        print(line)

파일 쓰기
#

with open("foo.txt", "w") as f:
    f.write("Life is too short, you need python")

파일 경로와 슬래시
#

파이썬 코드에서 파일 경로를 표시할 때 슬래시(/)나 이스케이프 문자(\\)를 사용할 수 있습니다.

"C:/ruff/hello.txt"
"C:\\\\ruff\\\\hello.txt"
r"C:\\ruff\\hello.txt"

파일에 새로운 내용 추가하기
#

파일을 추가 모드('a')로 열어 데이터를 추가합니다.

# add_data.py
f = open("C:/doit/새파일.txt", 'a')
for i in range(11, 20):
    data = f"{i}번째 줄입니다.\\n"
    f.write(data)
f.close()

with 문과 함께 사용하기
#

with 문을 사용하여 파일을 열고 자동으로 닫습니다.

# file_with.py
with open("foo.txt", "w") as f:
    f.write("Life is too short, you need python")

sys 모듈 사용하기
#

sys 모듈을 사용하여 프로그램에 인수를 전달할 수 있습니다.

import sys

args = sys.argv[1:]
for i in args:
    print(i)

실행 시 인수를 전달합니다.

python sys1.py arg1 arg2 arg3
# 출력:
# arg1
# arg2
# arg3

📗 클래스
#

메소드의 self 인자
#

클래스 메소드의 첫 번째 매개변수는 항상 객체 자신을 가리키는 self입니다.

a.setdata(4, 2)를 호출하면 self에는 객체 a가 자동으로 전달됩니다.

class FourCal:
    def setdata(self, first, second):
        self.first = first
        self.second = second
# 클래스 인스턴스 생성
>>> a = myFunc()

# 메소드 호출
>>> a.setdata(4, 2)

생성자
#

생성자는 객체 생성 시 초깃값을 설정하는 역할을 합니다.

class FourCal:
    def __init__(self, first, second):
        self.first = first
        self.second = second

    def add(self):
        return self.first + self.second

    def mul(self):
        return self.first * self.second

    def sub(self):
        return self.first - self.second

    def div(self):
        return self.first / self.second
>>> a = FourCal(4, 2)
>>> a.add()
6

>>> a.div()
2.0

클래스 상속
#

상속을 통해 기존 클래스의 기능을 확장합니다.

class FourCal:
    def setdata(self, first, second):
        self.first = first
        self.second = second

class myCal(FourCal):
    def pow(self):
        return self.first ** self.second
>>> a = myCal(4, 2)
>>> a.pow()
16

>>> a.add()
6

메서드 오버라이딩
#

부모 클래스의 메서드를 자식 클래스에서 재정의합니다.

class myCal(FourCal):
    def div(self):
        if self.second == 0:
            return 0
        return self.first / self.second
>>> a = myCal(4, 0)
>>> a.div()
0

클래스 변수
#

클래스 변수는 클래스 내 모든 인스턴스에서 공유됩니다.

class Family:
    lastname = "김"
>>> Family.lastname
'김'

>>> a = Family()
>>> b = Family()
>>> a.lastname
'김'

>>> b.lastname
'김'

>>> Family.lastname = "박"

>>> a.lastname
'박'

>>> b.lastname
'박'

클래스 변수와 동일한 이름의 객체 변수를 생성할 수 있습니다. 객체 변수는 클래스 변수와 독립적으로 존재합니다.

>>> a.lastname = "최"
>>> a.lastname
'최'

>>> Family.lastname
'박'

>>> b.lastname
'박'

📗 모듈 & 패키지
#

모듈 만들기
#

모듈은 파이썬 파일로, 함수와 변수를 포함할 수 있습니다.

이 파일을 C:\\ruff 디렉터리에 저장합니다.

def add(a, b):
    return a + b

def sub(a, b):
    return a - b

모듈 불러오기
#

모듈을 사용하려면 import 명령어를 사용합니다.

C:\\Users\\ruff>cd C:\\ruff
C:\\ruff>python
>>> import mod1
>>> print(mod1.add(3, 4))
7

>>> print(mod1.sub(4, 2))
2

모듈의 함수를 직접 import하여 사용할 수 있습니다. 함수 이름 앞에 모듈 이름을 붙이지 않아도 됩니다. 기존의 함수명과 중복되지 않도록 합니다.

>>> from mod1 import add, sub
>>> add(3, 4)
7

>>> sub(4, 2)
2

모든 함수를 import하려면 *를 사용합니다.

>>> from mod1 import *

if __name__ == "__main__" 의미
#

모듈이 직접 실행될 때만 코드가 실행됩니다. 이렇게 하면 모듈이 import될 때는 실행되지 않습니다.

def add(a, b):
    return a + b

def sub(a, b):
    return a - b

if __name__ == "__main__":
    print(add(1, 4))
    print(sub(4, 2))

클래스나 변수 등을 포함한 모듈
#

모듈에는 함수 외에도 클래스와 변수를 포함할 수 있습니다.

PI = 3.141592

class Math:
    def solv(self, r):
        return PI * (r ** 2)

def add(a, b):
    return a + b

모듈 불러오기
#

import mod2

result = mod2.add(3, 4)
print(result)

다른 디렉터리에 있는 모듈 불러오기
#

sys.path.append 사용하기
#

import sys
sys.path.append("C:/ruff/modules")
import mymod
print(mymod.add(3, 4))

PYTHONPATH 환경 변수 사용하기
#

C:\\ruff>set PYTHONPATH=C:\\ruff\\modules
C:\\ruff>python
>>> import mymod
>>> print(mymod.add(3, 4))
7

패키지 만들기
#

파이썬에서 패키지(packages)란 관련 있는 모듈의 집합을 말합니다. 패키지는 파이썬 모듈을 계층적(디렉터리 구조)으로 관리할 수 있게 해줍니다.

패키지 구조
#

game/
    __init__.py
    sound/
        __init__.py
        echo.py
    graphic/
        __init__.py
        render.py

패키지 만들기
#

  1. 디렉터리와 파일을 생성합니다.

  2. echo.py 파일에 함수를 작성합니다.

    # echo.py
    def echo_test():
        print("echo")
    
  3. render.py 파일에 함수를 작성합니다.

    # render.py
    def render_test():
        print("render")
    
  4. PYTHONPATH를 설정하고 패키지를 사용합니다.

    C:\\> set PYTHONPATH=C:/ruff
    C:\\> python
    >>> from game.sound import echo
    >>> echo.echo_test()
    echo
    

패키지 사용
#

이와 같이 패키지를 사용하면 모듈을 체계적으로 관리하고 재사용성을 높일 수 있습니다.

C:\\> set PYTHONPATH=C:/ruff
C:\\> python

>>> import game
Initializing game ...

>>> game.print_version_info()
The version of this game is 3.5.

>>> from game.sound import echo
>>> echo.echo_test()
echo

>>> from game.graphic.render import render_test
>>> render_test()
render
echo

__init__.py의 용도
#

패키지 변수 및 함수 정의

# C:/ruff/game/__init__.py

# 패키지 내 모듈을 미리 Import
from .graphic.render import render_test

# 패키지 변수 및 함수 정의
VERSION = 3.5

def print_version_info():
    print(f"The version of this game is {VERSION}.")

# C:/ruff/game/__init__.py
print("Initializing game ...")
# 여기에 초기화 코드 작성

__all__
#

여기에서 __all__이 의미하는 것은 sound 디렉터리에서 *를 사용하여 import할 경우, 이곳에 정의된 echo 모듈만 import된다는 의미입니다.

 __all__과 상관없이 무조건 import되는 경우는 from a.b.c import *에서 from의 마지막 항목인 c가 모듈인 때이다.

# C:/ruff/game/sound/__init__.py
__all__ = ['echo']

relative 패키지
#

다른 디렉터리의 모듈을 relative하게 Import 할 수 있습니다.

# render.py
from ..sound.echo import echo_test

def render_test():
    print("render")
    echo_test()

📗 유니코드
#

파이썬에서 사용하는 문자열은 모두 유니코드 문자열입니다. 파이썬 3부터 모든 문자열을 유니코드로 처리합니다.

인코딩 (Encoding)
#

유니코드 문자열을 바이트 문자열로 변환하는 과정을 인코딩이라고 합니다.

>>> a = "Hello Python"
>>> b = a.encode('utf-8')
>>> b
b'Hello Python'

>>> type(b)
<class 'bytes'>

인코딩 방식을 생략하면 기본값인 utf-8로 동작합니다.

한글 문자열을 아스키 방식으로 인코딩하려고 하면 오류가 발생합니다.

>>> a = "한글"
>>> a.encode("ascii")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

다른 인코딩 방식(e.g., euc-kr)을 사용하면 인코딩할 수 있습니다.

>>> a = '한글'
>>> a.encode('euc-kr')
b'\\xc7\\xd1\\xb1\\xdb'

>>> a.encode('utf-8')
b'\\xed\\x95\\x9c\\xea\\xb8\\x80'

디코딩 (Decoding)
#

인코딩한 바이트 문자열을 유니코드 문자열로 변환하는 과정을 디코딩이라고 합니다.

>>> a = '안녕'
>>> b = a.encode('euc-kr')
>>> b.decode('euc-kr')
'안녕'

잘못된 인코딩 방식으로 디코딩하려고 하면 오류가 발생합니다.

>>> b.decode('utf-8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc7 in position 0: invalid continuation byte

입출력과 인코딩
#

인코딩과 관련된 입출력 작업을 할 때 다음 규칙을 지키면 좋습니다.

  • 입력으로 받은 바이트 문자열은 되도록 빨리 유니코드 문자열로 디코딩합니다.
  • 함수나 클래스 등에서는 유니코드 문자열만 사용합니다.
  • 입력에 대한 결과를 전송하는 마지막 부분에서만 유니코드 문자열을 바이트 문자열로 인코딩하여 반환합니다.

다음은 euc-kr 방식으로 작성한 파일을 읽고 수정하여 저장하는 예제입니다.

# 1. euc-kr로 작성된 파일 읽기
with open('euc_kr.txt', encoding='euc-kr') as f:
    data = f.read()  # 유니코드 문자열

# 2. unicode 문자열로 프로그램 수행하기
data = data + "\\n" + "추가 문자열"

# 3. euc-kr로 수정된 문자열 저장하기
with open('euc_kr.txt', encoding='euc-kr', mode='w') as f:
    f.write(data)

소스 코드의 인코딩
#

소스 코드의 인코딩이란 소스 코드 파일이 어떤 방식으로 인코딩되었는지를 뜻합니다. 소스 코드 가장 위에 다음과 같은 문장을 넣어 인코딩 방식을 명시할 수 있습니다.

# -*- coding: utf-8 -*-

파이썬 3.0부터는 기본값이 utf-8이므로 이 문장은 생략할 수 있습니다. 만약 소스 코드를 euc-kr로 인코딩했다면 다음과 같이 작성해야 합니다.

# -*- coding: euc-kr -*-

잘못된 인코딩으로 명시하면 문자열 처리 부분에서 인코딩 관련 오류가 발생할 수 있습니다.


📗 클로저
#

클로저는 함수 안에 내부 함수를 구현하고, 그 내부 함수를 리턴하는 함수입니다.

외부 함수는 자신의 변숫값 등을 내부 함수에 전달할 수 있습니다.

클로저 사용
#

def multiply(m):
    def wrapper(n):
        return m * n
    return wrapper

if __name__ == "__main__":
    multiply3 = multiply(3)
    multiply5 = multiply(5)

    print(multiply3(10))  # 30 출력
    print(multiply5(10))  # 50 출력

위 로직을 클래스로 구현

class Multiply:
    def __init__(self, m):
        self.m = m

    def multiply(self, n):
        return self.m * n

if __name__ == "__main__":
    multiply3 = Multiply(3)
    multiply5 = Multiply(5)

    print(multiply3.multiply(10))  # 30 출력
    print(multiply5.multiply(10))  # 50 출력
class Multiply:
    def __init__(self, m):
        self.m = m

    def __call__(self, n):
        return self.m * n

if __name__ == "__main__":
    multiply3 = Multiply(3)
    multiply5 = Multiply(5)

    print(multiply3.multiply(10))  # 30 출력
    print(multiply5.multiply(10))  # 50 출력

📗 데코레이터
#

기존 함수에 기능을 추가하는 클로저입니다.

실행 시간 측정
#

import time

def execfunc():
    start = time.time()
    print("EXECUTE A FUNCTION")
    end = time.time()
    print(f"RUN TIME: {end-start}초")

execfunc()

클로저를 이용한 데코레이터
#

import time

def elapsed(original_func):
    def wrapper():
        start = time.time()
        result = original_func()
        end = time.time()
        print("함수 수행시간: %f 초" % (end - start))
        return result
    return wrapper

def myfunc():
    print("함수가 실행됩니다.")

decorated_myfunc = elapsed(myfunc)
decorated_myfunc()

데코레이터 적용
#

# decorator.py
import time

def elapsed(original_func):
    def wrapper():
        start = time.time()
        result = original_func()
        end = time.time()
        print("함수 수행시간: %f 초" % (end - start))
        return result
    return wrapper

@elapsed
def myfunc():
    print("함수가 실행됩니다.")

myfunc()

매개변수가 있는 함수에 데코레이터 적용
#

import time

def elapsed(original_func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = original_func(*args, **kwargs)
        end = time.time()
        print("함수 수행시간: %f 초" % (end - start))
        return result
    return wrapper

@elapsed
def myfunc(msg):
    """ 데코레이터 확인 함수 """
    print(f"{msg}을 출력합니다.")

myfunc("I need python")

📗 타입 어노테이션
#

변수 타입을 명시하여 코드의 가독성과 유지보수성을 높입니다.

def add(a: int, b: int) -> int:
    return a + b

📗 이터레이터
#

이터레이터는 next 함수 호출 시 계속 그다음 값을 리턴하는 객체입니다.

더 리턴할 값이 없다면 StopIteration 예외가 발생하게 됩니다.

>>> a = ['a', 'b', 'c']
>>> ia = iter(a)
>>> type(ia)
<class 'list_iterator'>

>>> next(ia)
'a'

>>> next(ia)
'b'

>>> next(ia)
'c'

>>> next(ia)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

이터레이터는 for문이나 next로 그 값을 한 번 읽으면 그 값을 다시는 읽을 수 없습니다.

>>> a = [1, 2, 3]
>>> ia = iter(a)
>>> for i in ia:
...     print(i)
... 
1
2
3

>>> for i in ia:
...     print(i)

__iter__ & __next__
#

__iter__ 메소드를 구현하면 해당 클래스로 생성한 객체는 반복 가능한 객체가 됩니다.

__iter__ 메소드는 반복 가능한 객체를 리턴해야 하며 보통 클래스의 객체를 의미하는 self를 리턴합니다.

__iter__ 메서드를 구현할 경우 반드시 __next__ 함수를 구현해야 합니다.

__next__ 메서드는 반복 가능한 객체의 값을 차례대로 반환하는 역할을 하며 for 문을 수행하거나 next 함수 호출 시 수행됩니다.

아래는 MyIterator 객체를 생성할 때 전달한 data를 하나씩 리턴하고 더는 리턴할 값이 없으면 StopIteration 예외를 발생시킵니다.

class MyIter:
    def __init__(self, data):
        self.data = data
        self.position = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.position >= len(self.data):
            raise StopIteration
        result = self.data[self.position]
        self.position += 1
        return result

if __name__ == "__main__":
    i = MyIter([6,7,8])
    for num in i:
        print(num)

📗 제너레이터
#

이터레이터를 생성해 주는 함수입니다.

next 함수 호출 시 그 값을 차례대로 얻을 수 있으며, 이러한 결과를 반환하고자 return 대신 yield 키워드를 사용합니다.

차례를 기억하고 있습니다.

>>> def my_generator():
...     yield 4
...     yield 5
...     yield 6
... 
>>> a = my_generator()
>>> type(a)
<class 'generator'>

>>> next(a)
4

>>> next(a)
5

>>> next(a)
6

>>> next(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

제너레이터 표현식
#

def my_square():
    for i in range(1, 1000):
        result = i * i
        yield result

sq = my_square()

print(next(sq))
print(next(sq))
print(next(sq))

위 함수는 아래와 같이 간단히 표현 가능합니다.

리스트 컴프리헨션과 비슷하지만, 튜플을 이용한 점이 다릅니다.

sq = (i * i for i in range(1, 1000))

간단한 경우라면 이터레이터 클래스보다는 제너레이터 표현식을 사용하는 것이 훨씬 간편하고 이해하기 쉽습니다.

제너레이터 활용
#

lazy evaluation 되어 list_job에는 이터레이터가 담겨지고, 아래 next()로 호출됩니다.

import time

def my_job():
    print("job start")
    time.sleep(1)
    return "done"

list_job = (my_job() for i in range(5))
print(next(list_job))

출력

job start
done

아래 이터레이터는 5번 실행되어 결과가 변수에 담기게 되고, print에서 출력만 하게됩니다.

import time

def longtime_job():
    print("job start")
    time.sleep(1)  # 1초 지연
    return "done"

list_job = [longtime_job() for i in range(5)]
print(list_job[0])

출력

job start
job start
job start
job start
job start
done

📗 정규식
#

컴파일된 패턴 객체를 사용하여 문자열 검색을 수행할 수 있습니다. 컴파일된 패턴 객체는 다음과 같은 4가지 메서드를 제공합니다.

match() : 문자열의 처음부터 정규식과 매치되는지 조사합니다.

search() : 문자열 전체를 검색하여 정규식과 매치되는지 조사합니다.

findall() : 정규식과 매치되는 모든 문자열(substring)을 리스트로 리턴합니다.

finditer() : 정규식과 매치되는 모든 문자열(substring)을 반복 가능한 객체로 리턴합니다.

matchsearch는 정규식과 매치될 때 match 객체를 리턴하고 매치되지 않을 때는 None을 리턴합니다.

match
#

match 메서드는 문자열의 처음부터 정규식과 매치되는지 조사합니다.

>>> import re
>>> pattern = re.compile("[a-z]+")
>>> m = pattern .match("python")
>>> print(m)
<re.Match object; span=(0, 6), match='python'>

>>> m = pattern.match("3 python")
>>> print(m)
None

match의 결과로 match 객체 또는 None을 리턴하기 때문에 파이썬 정규식 프로그램은 보통 다음과 같은 흐름으로 작성합니다.

pattern = re.compile('정규표현식')
m = p.match('조사할 문자열')
if m:
    print('Match found:', m.group())
else:
    print('No match')

search#

search 메서드는 문자열 전체를 검색하여 정규식과 매치되는지 조사합니다.

>>> m = pattern.search("python")
>>> print(m)
<re.Match object; span=(0, 6), match='python'>
>>> m = pattern.search("3 python")
>>> print(m)
<re.Match object; span=(2, 8), match='python'>

findall
#

findall은 정규식과 매치되는 모든 값을 찾아 리스트로 리턴합니다.

>>> import re
>>> pattern = re.compile("[a-z]+")
>>> result = pattern.findall("Hello python hi")
>>> print(result)
['ello', 'python', 'hi']

finditer
#

finditerfindall과 동일하지만, 그 결과로 반복 가능한 객체(iterator object)를 리턴합니다.

>>> reresult = pattern.finditer("Hello python hi")
>>> for r in result:
...     print(r)
... 
<re.Match object; span=(1, 5), match='ello'>
<re.Match object; span=(6, 12), match='python'>
<re.Match object; span=(13, 15), match='hi'>

match 객체의 메서드
#

group : 매치된 문자열을 리턴합니다.

start : 매치된 문자열의 시작 위치를 리턴합니다.

end : 매치된 문자열의 끝 위치를 리턴합니다.

span : 매치된 문자열의 (시작, 끝)에 해당하는 튜플을 리턴합니다.

>>> m = pattern.match("aaabbbccc")
>>> m.group()
'aaabbbccc'

>>> m.start()
0

>>> m.end()
6

>>> m.span()
(0, 6)

모듈 직접 실행
#

>>> m = re.match('[a-z]+', "python")

컴파일 옵션
#

  • DOTALL(S) : .(dot)이 줄바꿈 문자를 포함해 모든 문자와 매치될 수 있게 합니다.
  • IGNORECASE(I) : 대소문자에 관계없이 매치될 수 있게 합니다.
  • MULTILINE(M) : 여러 줄과 매치될 수 있게 합니다. ^, $ 메타 문자 사용과 관계 있는 옵션입니다.
  • VERBOSE(X) : verbose 모드를 사용할 수 있게 합니다. 정규식을 보기 편하게 만들 수 있고 주석 등을 사용할 수 있게 됩니다.
>>> p1 = re.compile('a.b', re.DOTALL)
>>> m = p1.match('a\\nb')
>>> print(m)
<re.Match object; span=(0, 3), match='a\\nb'>

>>> p = re.compile('[a-z]+', re.I)

IGNORECASE(I)
#

>>> p = re.compile('[a-z]+', re.I)
>>> m = p.match('aBCd')
>>> print(m)
<re.Match object; span=(0, 4), match='aBCd'>

MULTILINE, M
#

각 라인의 처음으로 인식시키고 싶은 경우:

import re
p = re.compile("^python\\s\\w+", re.MULTILINE)

data = """python 12
python 34
I need python
python 56"""

print(p.findall(data))

# Output
# ['python 12', 'python 34', 'python 56']

VERBOSE, X
#

문자열에 사용된 화이트스페이스는 컴파일할 때 제거됩니다 (단, [ ] 안에 사용한 화이트스페이스는 제외).

charref = re.compile(r"""
 &[#]                # Start of a numeric entity reference
 (
     0[0-7]+         # Octal form
   | [0-9]+          # Decimal form
   | x[0-9a-fA-F]+   # Hexadecimal form
 )
 ;                   # Trailing semicolon
""", re.VERBOSE)

역슬래시 문제
#

\\\\section

정규식을 컴파일하려면 다음과 같이 작성해야 합니다.

>>> p = re.compile('\\\\abc')

결국 정규식 엔진에 \\\\ 문자를 전달하려면 파이썬은 \\\\\\\\처럼 역슬래시를 4개나 사용해야 합니다.

>>> p = re.compile('\\\\\\\\abc')

raw string 표현법을 사용하여 해결할 수 있습니다.

>>> p = re.compile(r'\\\\abc')

문자열 소비가 없는 메타 문자
#

  • | 메타 문자는 or 과 동일한 의미로 사용됩니다.
>>> import re
>>> p = re.compile('Python|Hello')
>>> m = p.match('PythonHi')
>>> print(m)
<re.Match object; span=(0, 6), match='Python'>

\A는 문자열의 처음과 매치된다는 것을 의미합니다. ^ 메타 문자와 동일한 의미지만, re.MULTILINE 옵션을 사용할 경우에는 다르게 해석됩니다.

\Z는 문자열의 끝과 매치된다는 것을 의미합니다.

\b는 단어 구분자(word boundary)입니다.

>>> p = re.compile(r'\bvery\b')
>>> print(p.search('she is very cute'))
<re.Match object; span=(7, 11), match='very'>

\B 메타 문자는 \b 메타 문자와 반대의 경우입니다.

>>> p = re.compile(r'\Bvery\B')
>>> print(p.search('she is very cute'))
None

>>> print(p.search('sheisverycute'))
<re.Match object; span=(5, 9), match='very'>

그루핑
#

그룹을 만들어 주는 메타 문자는 ()입니다.

(ABC)+
>>> p = re.compile('(ABC)+')
>>> m = p.search('ABCABCABC GGG?')
>>> print(m)
<re.Match object; span=(0, 9), match='ABCABCABC'>

>>> print(m.group())
ABCABCABC

이름과 전화번호를 찾는 정규식

>>> p = re.compile(r"\w+\s+\d+[-]\d+[-]\d+")
>>> m = p.search("park 010-1234-1234")

이름만 추출

>>> p = re.compile(r"(\w+)\s+\d+[-]\d+[-]\d+")
>>> m = p.search("kim 010-1234-1234")
>>> print(m.group(1))
kim

국번 추출

>>> p = re.compile(r"(\w+)\s+((\d+)[-]\d+[-]\d+)")
>>> m = p.search("park 010-1234-1234")
>>> print(m.group(3))
010

그룹 인덱스
#

group(0) : 매치된 전체 문자열

group(1) : 첫 번째 그룹에 해당되는 문자열

group(2) : 두 번째 그룹에 해당되는 문자열

group(n) : n 번째 그룹에 해당되는 문자열

그루핑된 문자열 재참조
#

>>> p = re.compile(r'(\b\w+)\s+\1')
>>> p.search('he is very very coool').group()
'very very'

그루핑된 문자열에 이름 붙이기
#

(?P<name>\w+)\s+((\d+)[-]\d+[-]\d+)

그룹 이름을 참조

>>> p = re.compile(r"(?P<name>\w+)\s+((\d+)[-]\d+[-]\d+)")
>>> m = p.search("kim 010-1234-1234")
>>> print(m.group("name"))
kim

재참조

>>> p = re.compile(r'(?P<word>\b\w+)\s+(?P=word)')
>>> p.search('he is very very coool').group()
'very very'

전방 탐색
#

긍정형 전방 탐색

:에 해당하는 부분에 긍정형 전방 탐색 기법을 적용하여 (?=:)으로 변경하였습니다.

:에 해당하는 문자열이 정규식 엔진에 의해 소비되지 않아(검색에는 포함되지만, 검색 결과에는 제외됨) 검색 결과에서는 :이 제거된 후 리턴됩니다.

>>> p = re.compile(".+(?=:)")
>>> m = p.search("<http://google.com>")
>>> print(m.group())
http

부정형 전방 탐색

확장자가 bat가 아닌 것 추출

.*[.](?!bat$).*$

예제

.*[.](?!bat$|exe$).*$

문자열 바꾸기
#

>>> p = re.compile('(red|black|green)')
>>> p.sub('colour', 'black hat and red shoes')
'colour hat and colour shoes'

바꾸기 횟수 제어

>>> p.sub('colour', 'black hat and red shoes', count=1)
'colour hat and red shoes'

📗 라이브러리
#

tempfile
#

mkstemp()
#

중복되지 않는 임시 파일의 이름을 무작위로 생성하여 리턴합니다.

import tempfile
filename = tempfile.mkstemp()
print(filename)

# Output
# ('C:\\\\WINDOWS\\\\TEMP\\\\tmp1a2b3c', 3)

TemporaryFile()
#

임시 저장 공간으로 사용할 파일 객체를 리턴합니다. 기본적으로 바이너리 쓰기 모드(wb)로 열리며, f.close()가 호출되면 자동으로 삭제됩니다.

import tempfile
f = tempfile.TemporaryFile()
f.close()

traceback
#

오류 추적 결과를 문자열로 리턴하는 traceback.format_exc()를 사용하여 어디에서 오류가 발생했는지 확인할 수 있습니다.

# traceback_test.py
def a():
    return 1 / 0

def b():
    a()

def main():
    try:
        b()
    except:
        print("오류가 발생했습니다.")
        print(traceback.format_exc())

main()

json
#

파일을 딕셔너리로 읽기

import json

with open('myinfo.json') as f:
    data = json.load(f)

print(type(data))
print(data)

# Output
# <class 'dict'>
# {'name': '홍길동', 'birth': '0525', 'age': 30}

딕셔너리를 파일로 저장

import json

data = {'name': '김철수', 'birth': '0203', 'age': 23}
with open('myinfo.json', 'w') as f:
    json.dump(data, f)

딕셔너리를 JSON 문자열로 변환

import json

d = {'name': '김철수', 'birth': '0203', 'age': 23}
json_data = json.dumps(d, ensure_ascii=False)
print(json_data)

# Output
# {'name': '김철수', 'birth': '0203', 'age': 23}

JSON 문자열을 딕셔너리로 변환

import json

json_data = '{"name": "김철수", "birth": "0203", "age": 23}'
data = json.loads(json_data)
print(data) 

# Output
# {'name': '김철수', 'birth': '0203', 'age': 23}

JSON 문자열 보기 좋게 출력

import json

d = {'name': '김철수', 'birth': '0203', 'age': 23}
print(json.dumps(d, indent=2, ensure_ascii=False))

# Output
# {
#   "name": "김철수",
#   "birth": "0203",
#   "age": 23
# }

urllib
#

URL을 읽고 분석하는 모듈입니다.

특정 페이지를 파일로 저장
#

import urllib.request

def get_pages(page):
    resource = f'<https://test-site.com/{page}>'
    with urllib.request.urlopen(resource) as s:
        with open(f'skitttles_{page}.html', 'wb') as f:
            f.write(s.read())

get_pages("posts/ssrf_curl_url_globbing/")

SSL 인증서 검증 비활성화
#

SSL 오류가 발생할 경우 다음과 같이 SSL 인증서 검증을 비활성화할 수 있습니다.

import urllib.request
import ssl

context = ssl._create_unverified_context()

def get_pages(page):
    resource = f'<https://test-site.com/{page}>'
    with urllib.request.urlopen(resource, context=context) as s:
        with open(f'skitttles_{page}.html', 'wb') as f:
            f.write(s.read())

get_pages("posts/ssrf_curl_url_globbing/")

threading
#

스레드 프로그래밍을 통해 한 프로세스 안에서 여러 작업을 동시에 수행할 수 있습니다.

스레드의 join() 함수를 사용하면 해당 스레드가 종료될 때까지 기다립니다.

import time
import threading

def long_task():
    for i in range(5):
        time.sleep(1)
        print(f"working: {i}")

print("Start")

threads = []
for i in range(5):
    t = threading.Thread(target=long_task)
    threads.append(t)

for t in threads:
    t.start()

for t in threads:
    t.join()  # 모든 스레드가 종료될 때까지 기다림

print("End")