파이써닉한 파이썬을 배워보자 - 1일차 기본 문법

0

pythonic

목록 보기
1/10

해당 시리즈는 python이나 프로그래밍에 익숙하지 않으신 분들을 대상으로 하지 않습니다. 보다 더 파이써닉하게 코드를 짜고, advanced한 문법을 배우는 시리즈입니다.

쉬운 문법 설명은 https://velog.io/@chappi/%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%9D%84-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90-1%EC%9D%BC%EC%B0%A8 을 참고해주세요.

기본 설명

formatting

f-문자열(f-string)을 사용하여 print()함수를 변경할 수 있다. 작은 따옴표인 '을 사용하여 묶어 넣은다음 변수를 넣을 수 있다.

year: int = 10
principal: float = 1215.50625

print(f'{year:>3d} {principal:0.2f}') #  10 1215.51

:>3은 세 자리 십진수를 오른쪽으로 정렬하라는 것이다. :0.2f은 소수점 이하 두 자리의 정밀도를 가지는 부동 소수점 수를 의미한다.

f-string 대안으로 format 메서드와 %연산자가 문자열 포맷 지정을 위해 사용된다.

a = 2
b = 3.2
print('{0:>3d} {1:0.2f}'.format(a, b))
print('%3d %0.2f' % (a, b))

특히 %은 c언어의 printf와 사용 방법이 아주 유사하다.

일반 수학 함수

함수설명
abs(x)절댓값
divmod(x,y)(x // y, x % y) 반환
pow(x, y, [, modulo])(x ** y) % modulo 반환
round(x, [n])10의 -n승의 가장 가까운 수로 반올림

round은 은행원식 반올림을 수행한다. 은행원 식 반올림은 정수를 2로 나누어 소수점이 생기면, 가장 가까운 짝수로 반올림하는 방법이다. 가령 0.5이면 0.0이고, 1.5이면 2.0이다.

논리 연산자

python은 다른 언어와 다르게 ||, &&, !을 사용하지 않고 or, and, not을 사용한다.

False, None, 0, 빈 문자열은 False 즉, 거짓으로 간주한다.

조건식

if-elif-else문법을 사용한다.

문자열

문자열은 '', "", """ """, ''' '''을 사용한다. 삼중 따옴표는 그 안의 내용을 literal하게 처리한다는 장점이 있다.

문자열은 유니코드 문자의 sequence로 저장되고, 0부터 시작하는 정수로 index를 가진다. 음수 index는 끝부터 index를 센다.

문자열에서 사용되는 메서드들은 다음과 같다.

메서드설명
s.endswith(prefix, [,start [,end]])문자열이 prefix로 끝나는 지 검사
s.find(sub, [, start [, end]])부분 문자열 sub가 처음으로 나타나는 위치를 찾으며 찾지 못하면 -1을 반환
s.lower()소문자로 변경
s.replace(old, new, [,maxreplace])부분 문자열 대체
s.split([sep [, maxsplit]])sep를 분리 기호로 사용하여 문자열을 분할, maxsplit는 최대 분할 횟수를 지정
s.startswith(prefix, [,start [,end]])문자열이 prefix로 시작하는 지 검사
s.strip([chrs])앞이나 뒤에 나오는 공백문자나 chrs로 지정된 문자를 제거
s.upper()대문자로 변경

+로 문자열 간의 연결이 가능하다.

문자를 숫자로 변경할 때는 str(), repr(), format()이 있는데, str()print()함수를 사용할 때와 동일한 결과를 생성한다. repr()은 객체의 값을 정확히 표현하기 위해 사용자가 프로그램에 입력한 문자 그대로 즉, literal한 문자열을 그대로 생성한다.

s = 'hello\tworld'
print(str(s)) # hello   world
print(repr(s)) # 'hello\tworld'

다음과 같이 repr을 사용하면 문자를 literal하게 그대로 가져온다.

format을 사용하면 원하는 포맷으로 변경한다.

x = 12.34567
ret = format(x, '0.2f')
print(ret) # 12.35

문자열은 인덱스로 접근이 가능하지만, 변경은 불가능하다. 즉, immutable이다. 때문에 인덱스로 접근하여 특정 문자만 변경하는 것은 불가능하다.

이를 위해서는 문자열을 리스트로 변경하여 수정하는 것은 가능하다.

파일 입출력

open을 통해 파일을 열 수 있는데, 파일을 열면 닫는 코드도 함께 있어야한다. 이를 위해 닫기를 자동으로 해줄 with를 사용하여 file을 사용하는 context를 한정하는 것이 좋다. 제어가 블록을 벗어나면 이 파일은 자동으로 닫히게 되는 것이다.

with open('data.txt') as file:
    for line in file:
        print(line, end='')

위 코드에서 with을 사용하지 않는다면 다음과 같다.

file = open('data.txt')
for line in file:
    print(line, end='')
file.close()

close는 잊어버리기 쉬우므로 with을 사용하는 것이 좋다.

file의 정보를 읽고 쓰는 메서드로 read, write를 제공한다. 또는 printfile인수를 사용하여 다음과 같이 사용이 가능하다.

a = 2
b = 12.3456
with open('out.txt','wt') as out:
    print(f'{a:>3} {b:0.2f}', file=out)

out.txt 파일이 만들어지고,

  2 12.35

다음의 msg가 적히게 된다.

리스트

list()를 통해서 문자열을 list화 할 수 있다.

letters = list('Dave')
print(letters) # ['D', 'a', 'v', 'e']

list로 문자열을 리스트로 만들 수 있기 때문에 이를 변경하여 새로운 문자열을 만들 수 있다.

s = 'Dave'
letters = list('Dave')
print(letters) # ['D', 'a', 'v', 'e']

letters[1] = '2'
print(''.join(letters)) # D2ve

튜플

튜플은 변경이 불가능한 객체로 다음과 같이 괄호로 감싼다. ()을 사용하기 때문에 우선순위를 높여주는 ()과 혼동된다. 때문에 1개의 요소를 묶어낼 때는 반드시 ,을 써주어야 한다.

a = () # 0개의 요소
b = (2, ) # 1개의 요소
hold = ('ggoc', 100, 200)

리스트처럼 튜플의 값도 숫자 인덱스로 추출할 수 있지만, 튜플을 변수로 풀어서 가져오는 언패킹 방식이 더 흔하다.

name, price, count = hold
name, _, count = hold # 안쓰는 값은 _으로 무시할 수 있다.

리스트가 지원하는 대부분의 연산을 튜플도 지원하지만, 한번 생성된 튜플의 내용은 변경할 수 없다. 즉, 이미 생성된 튜플에 요소를 대체하거나 삭제, 새로운 요소 추가는 불가능하다.

집합

집합은 고유한 객체의 순서없는 모음이다. 고유 값을 찾거나, 포함 관계 같은 문제를 다룰 때 사용된다. {}을 사용하거나 set()을 사용한다.

name1 = {'IBM', 'MSFT', 'AA'}
name2 = set(['IBM', 'MSFT', 'AA'])

집합의 요소들은 일반적으로 변경이 불가능한 객체들로 제한된다. 가령, 숫자, 문자열 ,튜플은 집하의 요소가 될 수 있지만, 리스트가 요소라면 안된다. 위의 예제는 리스트를 받았지만 리스트를 unpack해서 그 안의 요소들을 집합에 넣은 것이지, 집합의 요소가 리스트인 것은 아니다.

| 합집합, & 교집합, - 차집합, ^ 대칭 차집합을 지원한다.

add, update로 집합에 새로운 항목을 추가할 수 있고, remove, discard로 항목을 삭제할 수 있다.

dict

dict의 key로는 그 내용이 바뀔 수 있는 자료구조가 들어갈 수 없다. 가령, list, set, dict는 들어가지 못하고 tuple은 가능하다.

prices = {}
prices[('IBM', '2015-02-03')] = 91.23 # tuple
prices['IBM', '2015-02-03'] = 91.23 # 괄호 생략 tuple

dict를 초기화 할 떄 key-value tuple을 만들어 list에 넣어주면 된다.

pairs = [('IBM', 125), ('ACME', 50), ('PHP', 40)]
d = dict(pairs)
print(d) # {'IBM': 125, 'ACME': 50, 'PHP': 40}

dict는 python3.6이전에는 순서가 보장되지 않았지만 3.6이후는 순서가 보장된다.

함수

함수의 첫 번째 문장으로 문서화 문자열(documentation string)을 작성하면 help()명령에 제공되어 IDE또는 기타 개발 도구에서 개발자를 돕기 위해 사용되낟.

def remainer(a,b):
    '''
    a를 b로 나눈 나머지 연산
    '''
    q = a // b
    r = a - q * b
    return r

print(remainer(10,2)) # 0

vscode에서 remainer함수에 커서를 갖다대면 우리가 작성한 help내용들이 나오게 된다.

함수의 입력과 출력이 이름만으로 명확하지 않다면 다음과 같이 type hint를 추가할 수 있다.

def remainer(a: int,b: int) -> int:
    '''
    a를 b로 나눈 나머지 연산
    '''
    q = a // b
    r = a - q * b
    return r

print(remainer(10,2)) # 0

type hint는 정보를 제공할 뿐, 강제성이 없다. 즉, 정수가 아니라 float를 넣어도 문제가 없다.

전역변수는 모든 함수에서 접근이 가능하다.

예외

try-except 문으로 예외 상황을 찾아 관리할 수 있다.

try:
    ...
except ValueError as err:
    ...

ValueError가 발생하면 에러의 원인에 대한 세부 원인이 err에 담기고 제어가 except로 빠진다. 다른 종류의 예외가 발생하면 프로그램은 평소와 같이 멈춘다. 예외가 발생하지 않으면 except 블록에 있는 코드는 무시된다. 예외가 해결되면 최종 except 블록 바로 뒤이어 나오는 문에서 프로그램이 재개된다.

raise문은 예외 발생을 알릴 때 사용된다. 이를 위해 예외 이름이 필요하다. 다음은 내장 예외의 하나인 RuntimeError를 일으키는 방법이다.

raise RuntimeError('Computer says no')

예외가 발생하고 try-except로 깜싸지지 않으면 프로그램이 종료된다.

예외 처리를 하면서 lock, 파일, 네트워크 연결과 같은 시스템 자원을 관리하는 작업이 까다롭다. 즉, 어떤 일이 발생하여 프로그램이 죽는다하더라도 반드시 해야할 일은 해야한다. 이를 위해 try-finally를 사용한다.

다음은 lock과 관련된 예로 교착 상태(deadlock)을 회피하기 위해서는 반드시 lock을 해제해야한다.

import threading
lock = threading.Lock()
...
lock.acquire()
try:
    ...
finally:
    lock.release() # 항상 실행된다.

try안에서 어떠한 에러가 발생하여 예외처리된다해도 finally는 실행된다.

이 코드를 수정한 것이 바로 with이다. with은 어떠한 경우에도 자원을 해제하는 로직을 실행하기 때문이다.

with lock:
    ...

이 예에서 lock객체는 with문을 사용할 때 자동으로 확보된다. 실행이 with 블록을 벗어나는 순간 lock은 자동으로 해제된다. 이것은 with블록 안에서 발생하는 일과 독립적으로 수행되는 일이다.

프로그램 종료

프로그램에서 더 이상 실행할 문장이 없거나 잡히지 않는 SystemExit 예외가 발생하면 프로그램은 종료된다.

raise SystemExit() # 에러 메시지 없이 종료
raise SystemExit() # 에러 메시지를 포함하여 종료

프로그램이 끝날 때 해야할 동작이 있다면 atexit모듈에 등록하여 실행할 수 있다.

import atexit
# connection = open_connection()

def cleanup():
    print("hello world")
    # close_connection(connection)

atexit.register(cleanup)
raise SystemExit() 
# hello world

객체와 클래스

프로그램 안에서 쓰이는 값은 모두 객체이다. 객체는 내부 데이터와 데이터와 관련된 다양한 연산을 수행하는 메서드로 구성된다.

클래스에서 시작과 끝이 __으로 만들어진 메서드를 스페셜 메서드(special method)라고 한다. __init__는 객체를 초기화하기 위해서 사용된다.

python에서는 맴버 변수에 대한 접근 제한자가 딱히 없다. 그래서 하나의 규칙으로 변수 앞에 _를 붙여 private인지 public인지를 표현 할 수 있다. _item은 클래스 외부에서 사용되지 않아야 한다. 단, 실제로 강제되는 것은 아니므로 code convention으로 지켜야 한다.

__repr__()은 클래스가 출력되는 방식을 변경해준다. __len__()len()과 함께 동작하도록 만든다.

모듈

모듈을 생성하려면 관련 문장과 정의를 모듈고 ㅏ동일한 이름을 갖는 파일(확장자는 .py)에 넣으면 된다.

  • hello.py
# hello.py
#
# flag에 따른 hello 내용이 다름

def hello_msg(flag: bool):
    hello = []
    if flag:
        hello.append("hello")
        hello.append("world")
    else:
        hello.append("no")
    return hello

다음과 같이 hello.py라는 모듈 이름이 정해지게 된다. 이제 이를 사용하여 main.py에서 가져와 함수를 사용하면 된다.

  • main.py
import hello

msg = hello.hello_msg(True)
print(msg) # ['hello', 'world']

msg = hello.hello_msg(False)
print(msg) # ['no']

import문은 새로운 namepsace 또는 환경을 생성하고 .py 안에 있는 문장을 이 namespace안에서 모두 실행한다. 해당 namespace안에 있는 내용에 접근하려면 hello.hello_msg와 같이 namespace에서 참조를 해서 가져와야 한다.

ImportError 예외와 함께 import문이 실패하면 환경에서 몇 가지 사항을 확인해야한다. 먼저 hello.py이 있는 지 확인하고, 다음으로 sys.path에 나열된 디렉터리를 확인한다. 파일이 해당 디렉터리에 없는 경우 파이썬은 파일을 찾을 수 없다.

모듈을 다른 이름으로 불러오고 싶다면 asimport문에 추가해주면 된다.

  • main.py
import hello as he

msg = he.hello_msg(True)
print(msg) # ['hello', 'world']

msg = he.hello_msg(False)
print(msg) # ['no']

특정 정의만 현재 namespace에 불러오고 싶다면 from문을 사용하면 된다. 그러면 from은 파일 이름인 namespace가 되고, import은 가져오고 싶은 정의가 된다.

  • main.py
from hello import hello_msg

msg = hello_msg(True)
print(msg) # ['hello', 'world']

msg = hello_msg(False)
print(msg) # ['no']

스크립트 작성

파이썬의 프로그램은 import문으로 다른 file에서 불려 참조되어 사용될 때도 있고, 그 자체로 메인으로 사용될 때도 있다. 이를 위해서 현재 모듈의 이름을 담고 있는 __name__을 사용하여 필요한 로직을 추가할 수 있다.

__name__은 메인으로 해당 파이썬 프로그램이 실행되면 __main__이 되고, 참조되어 import되어 사용되면 from이나 import문에 있는 모듈이름이 된다. 즉, 파일이름인 hello가 될 것이다.

  • hello.py
# hello.py
#
# flag에 따른 hello 내용이 다름

def hello_msg(flag: bool):
    hello = []
    if flag:
        hello.append("hello")
        hello.append("world")
    else:
        hello.append("no")
    return hello
    
if __name__ == '__main__':
    print("This program exectued in main")
  • hello.py를 main으로 실행할 때
python3 hello.py
This program exectued in main
  • hellp.py를 import하여 모듈로 참조할 때
ython3 main.py 
['hello', 'world']
['no']

모듈로 참조되면 __name__부분이 모듈이름인 hello이므로 실행되지 않는 것을 확인할 수 있다.

패키지

패키지는 계층적인 모듈의 모임이다.

tutorial/
    __init__.py
    readport.py
    pcost.py
    stack.py
    ...

디렉토리는 내용이 비어 있을 수도 있는 __init__.py파일이 있어야 한다. 일단 이 작업이 완료되면 중첩 import 문을 만들 수 있다.

import tutorial.readport
port = turorial.readport.read_portfolio('portfolio.dat')

이름이 길어 마음에 들지 않는다면 import문을 다음과 같이 사용해 짧게 줄여 쓸 수 있다.

from tutorial.readport import read_portfolio
port = read_portfolio('portfolio.dat')

패키지에서 하나 까다로운 점은 같은 패키지 내에 있는 파일을 불러올 때이다.

tutorial 패키지 안에 pcost.pyreadport.py 파일이 같이 있다고 하자. pcost.py에서 readport.pyhello_msg를 import한다면 다음가 같이 하면 안된다.

  • pcost.py
import readport

def pcost_func():
    print(readport.hello_msg(True))

tutorial 패키지 내에 pcost.pyreadport.py 모듈이 같이 있다고해도, readport를 찾지못한다. 왜냐하면 tutorial 패키지의 readport.py를 찾아야만 찾을 수 있기 때문이다. 즉, namespace가 패키지도 추가된 것이다.

  • pcost.py
from tutorial import readport

def pcost_func():
    print(readport.hello_msg(True))

다음과 같이 바꾸었을 때는 쉽게 실행이 된다.

만약, 패키지 이름이 자주 변경될 것 같다면 상대 경로를 이용할 수도 이싿.

  • pcost.py
from . import readport

def pcost_func():
    print(readport.hello_msg(True))

이렇게하면 패키지의 이름이 변경되어도 패키지 안의 모듈들의 경로를 변경할 필요가 없다.

응용 프로그램의 구조화

많은 양의 파이썬 코드를 작성하다보면 스스로 작성한 코드와 서드파티에 의존하는 코드가 서로 뒤섞이며 덩치가 커진 응용프로그램을 다루게된다. 이를 해결하기 위한 몇 가지 다양한 의견들이 이싿.

먼저 대규모 코드 베이스를 패키지( __init__.py 파일을 포함하는 .py의 디렉터리)로 구성하는 것이 관행이다. 이 때 최상위 디렉터리 이름을 고유한 패키지 이름으로 선택하도록 한다. 패키지 디렉터리를 구성하는 주요 목적은 프로그래밍을 하는 동안 사용되는 모듈의 namepsace와 import문을 관리하기 위해서이다.

소스 코드 이외에 테스트, 예제, 스크립트, 문서가 추가로 있을 것이다. 이러한 자료는 소스코드가 포함된 패키지와 별도로 다른 디렉터리에 두도록 한다. 따라서 프로젝트를 위한 최상위 디렉터리를 만들고 작업은 모두 그 하위에 두는 것이 일반적이다.

tutorial-project/
    tutorial/
        __init__.py
        readport.py
        pcost.py
        ...
    tests/
        test_stack.py
        test_pcost.py
        ...
    examples/
        ...
    doc/
        tutorial.txt
        ...

패키지를 구성하는 방법은 굉장히 다양하므로 필요에 따라 적절하게 사용하도록 하자.

서드 파티 관리

파이썬은 pypi 라는 라이브러리 인덱스 사이트(라이브러리 설명)이 있다. 이를 참고하여 라이브러리를 사용할 수 있다.

pip를 이용하여 서드파티를 설치할 수 있는데, 다음과 같다.

python3 -m pip install somepackage

설치된 패키지들은 sys.path의 값을 살펴보면 찾을 수 있는데, site-packages라는 특별한 디렉터리에 위치한다. 가령 유닉스 시스템에서 패키지들은 /usr/local/lib/python3.8/site-packages에 위치한다. 패키지가 어디에 있는 지 궁금하다면 인터프리터에서 패키지를 불러온 다음 패키지의 __file__속성을 살펴보면 된다.

import pandas
pandas.__file__

패키지를 설치할 때 가장 큰 문제는 버전에 대한 이슈이다. 그래서 무언가를 망칠 염려 없이 패키지를 설치하고 작업 할 수 있는 환경을 만들기 위해서 다음과 같은 명령으로 가상 환경을 만들면 된다.

python3 -m venv myproject

이렇게 입력하면 myproject 디렉터리에 파이썬 전용으로 사용할 수 있는 환경이 만들어진다. 해당 디렉터리 내에서 패키지를 안전하게 설치할 수 있는 인터프리터 실행 파일과 라이브러리를 찾을 수 있다.

가령, myproject/bin/python을 실행하면 개인용으로 구성된 인터프리터가 실행된다. 기본으로 설치된 파이썬을 깨뜨릴 염려없이, 이 인터프리터로 패키지를 설치할 수 있다. 캐피지를 설치하기 위해 pip를 사용하는 것은 동일하지만 정확히 인터프리터를 지정해주어야 한다.

./myproject/bin/python3 -m pip install somepackage

pipvenv를 쉽게 사용할 수 있게 단순화하는 것을 목적으로 하는 다양한 도구들이 있다.

0개의 댓글