[Python - Modules & Packages] 기초 정리

jaylight·2020년 11월 21일
0

Modules & import

  • Modules: 파이썬 코드 파일, 개발자는 특별한 작업 없이 코드를 모듈로 사용 가능함
  • import문: 다른 모듈의 코드를 참조하기 위한 코드

모듈 import하기

import문을 사용하여 간단하게 모듈을 임포트할 수 있으며, import뒤에 확장자(.py)를 제외한 파이썬 파일명을 입력하여 임포트한다.

import fast

place = fast.pick()
print("Let's go to", place)
  • 조건: fast.py 파일이 실행 파일과 같은 디렉터리에 저장되어 있어야한다.
  • 실행 절차
    1. 실행 프로그램을 실행하면, 실행 프로그램은 fast모듈에 접근하여 pick()함수를 실행한다.
    2. 함수는 결과를 반환하고, 실행 프로그램은 반환 받은 결과를 출력한다.

import된 모듈의 객체 사용

임포트된 모듈의 객체를 사용할 때는 모듈 이름으로 객체의 위치를 한정하고 정리함으로써,
다른 모듈 간의 불필요한 이름 충돌을 피하기 위해 fast.pick()과 같이 모듈이름을 앞에 붙여서 사용하는 것을 권장한다.

import된 모듈의 객체가 한정된 특정 함수 내에서 사용되는 경우,

def pick():
    import random
    return random.choice(places)

와 같이 함수 내에서 임포트하여 활용할 수 있다.

import문의 위치는 개발자의 선호에 따르지만 일반적으로 코드의 모든 의존성을 서두에 명시하기 위해 파일 맨 위에 두는 방식을 활용함

다른 이름으로 모듈 import하기

import된 모듈의 이름을 임의의 다른 이름으로 지정하여 활용할 수 있다.
이는 다른 곳에 동일한 이름의 모듈이 존재하거나 짧은 이름으로 타이핑을 최소화하기 위해 사용된다.

import fast as f

place = f.pick()
print("Let's go to place", place)

필요한 모듈만 선택적으로 import하기

모듈에서 필요한 부분만 개별적으로 import할 수 있다.

from fast import pick as who_cares

place = who_cares()
print("Let's go to", place)

위의 예제는 fast모듈에서 pick 함수를 import하면서 who_cares라는 이름으로 변경하였다.


Packages

  • 패키지: 하위 디렉터리로 .py파일들을 포함한 파일과 모듈 계층 구조로,
    디렉터리 안에 디렉터리로 여러 깊이로 구성하여 파이썬 애플리케이션을 확장할 수 있다.

import하여 사용하는 Module이 다른 패키지 내에 포함되어 있다면 아래와 같이 모듈에서 필요한 객체만 선택적으로 활용하는 것처럼 from ~ import~를 활용할 수 있다.

from soucres import fast

print("Let's go to", fast.pick())

위와 같이 sources 패키지 내에 fast 모듈을 import하여 해당 모듈 내의 객체를 활용할 수 있다.

파이썬 3.3 이전 버전의 경우, 코드 하위 디렉터리에 __init__.py 파일을 넣어주어야 디렉터리로 패키지로 간주했었지만,
이후 버전의 경우 위와 같은 별도의 작업이 필요하지 않다.

모듈 탐색 경로

실행 py파일과 동일 디렉터리 혹은 하위 디렉터리에 있지 않은, 다른 위치에 있는 모듈에 접근하고 제어하는 방법

표준 라이브러리인 random과 같은 모듈 등은 현재 디렉터리에 있지 않아도 자동으로 import되는데 이는 파이썬 인터프리터가 모듈을 import하기 위해 탐색하는 경로가 지정되어있고, 해당 경로 내에 random과 같은 표준 라이브러리들이 들어있기 때문이다.

위의 경로를 보기 위해서는 sys를 import한 뒤, sys.path를 통해 볼 수 있으며,
sys.path에는 파이썬 인터프리터가 탐색하는 경로가 개별 항목으로 리스트로 저장되어 있다.

import sys
sys.path

sys.path 리스트의 세 번째 항목으로 빈 문자열('')이 들어가 있는데, 빈 문자열이 sys.path에 있다면, 해당 항목에서 임포트할 파일을 현재 실행 파일이 위치한 동일한 디렉터리에서 찾는 작업을 수행한다.

일반적으로 빈 문자열은 맨 위에 위치하지만, 위의 경우 다름 why..?

위의 경로를 탐색 중에 중복된 이름의 모듈이 있다면, 첫 번째로 검색된 모듈을 사용한다.
즉, 표준 라이브러리가 들어있는 경로 이전에 동일한 이름의 모듈이 먼저 등장한다면, 해당 모듈을 사용하고 표준 라이브러리는 활용하지 않는다.

코드 내에서 탐색 경로를 수정하기 위해서는 sys.path 리스트에 경로를 다음과 같이 추가할 수 있다.

import sys
sys.path.insert(0, "/my/modules")

위의 코드르 통해 my/modules에 있는 모듈을 다른 모듈 경로보다 우선적으로 탐색하도록 지정할 수 있다.

상대/절대 경로 import

파이썬은 절대(Absoulte), 상대(Relative) 경로 import를 지원하며, 현재까지 보았던 예제는 모두 절대경로 import이다.

상대 경로 import

  • 현재 디렉터리 기준: .을 사용
# 사용하는 모듈이 메인 프로그램과 같은 디렉터리인 경우
from . import rougarou
  • 상위 디렉터리 기준: ..을 사용
# 사용하는 모듈이 메인 프로그램 상위 디렉터리인 경우
from .. import rougarou

# 사용하는 모듈이 메인 프로그램 상위 디렉터리의 다른 하위 디렉터리인 경우
from ..creatures import rougarou

현재 디렉터리와 부모 디렉터리 표기법은 UNIX에서 차용됨

네임스페이스 패키지

네임스페이스 패키지가 있는 디렉터리에서 패키지 분할이 가능하며, 이를 활용해 패키지가 커질 경우, 패키지를 조금 더 세분화하여 표현할 수 있다.

기존에 아래와 같은 패키지를

critters
ㄴ rougarou.py
ㄴ wendigo.py

다음과 같이 변경했을 때,

north
ㄴ critters
    ㄴ wendigo.py
south
ㄴ critters
    ㄴ rougarou.py

위의 northsouth가 모두 모듈 탐색 경로에 있기만 하다면, critters라는 공통의 이름으로 여러 패키지를 단일 디렉터리 패키지로 활용하는 것처럼 모듈을 가져올 수 있다.

from critters import wendigo, rougarou

모듈 vs. 객체

모듈과 객체는 여러면에서 유사한 특성을 가짐

모듈 및 클래스: 내부의 데이터 값을 모듈 및 클래스 생성 시 지정하거나, 나중에 할당할 수 있음
객체: 프로퍼티나 던더(__)를 활용하여 데이터 속성에 대한 접근을 숨기거나 제어

import math
math.pi #3.141592653589793

math.pi = 3.0
math.pi #3.0

모듈은 위와같이 import하면서 사본을 만들어 가져오므로, import받은 모듈에 속한 값을 메인 프로그램에서 수정하더라도 원본 math모듈에는 영향을 미치지 않는다.

두 번 이상 동일 모듈을 import하더라도, 프로그램 내 import한 모듈 사본은 하나만 존재
→ _이를 이용하여 import한 모든 코드에 전역 값을 저장할 수 있음 (?)

파이썬 표준 라이브러리

  • Batteries incluted: 파이썬의 특징으로 작업 처리를 위한 많은 유용한 표준 라이브러리들이 포함되어 있음을 나타내는 개념

위의 라이브러리는 핵심 언어가 지나치게 늘어나는 것을 피하기 위해 분리되어 있음
코드 작성 시, 원하는 기능이 표준 라이브러리에 있는지 확인하기 위해 공식문서나 책 등을 확인

누락된 키 처리하기: setdefault(), defaultdict()

딕셔너리에 존재하지 않는 키로 접근 → 예외 발생 → 해결: 기본값을 반환하는 함수 get()을 활용하여 위 예외를 피함

  • setdefault(): get()과 유사하지만, 키가 누락된 딕셔너리에 항목을 할당
periodic_table = {'Hydrogen': 1, 'Helium': 2}

# setdefault()를 활용하여 키를 찾은 후, 없으면 해당 키에 값을 할당하고 반환
carbon = periodic_table.setdefault('Carbon', 12)
carbon # 12

# 키가 있다면, 새로운 값이 할당되지않고 기존 값이 반환
helium = periodic_table.setdefault('Helium', 947)
helium # 2
  • defaultdict(): 딕셔너리 생성 시 새 키에 대한 기본값을 먼저 지정하며, 인수로 기본값을 지정하기 위한 함수를 받음
from collections import defaultdict

# 0을 반환하는 int()함수를 defaultdict의 인수를 넣으면, 값이 할당되지 않은 새로운 키에 대해 자동으로 0을 할당함

periodic_table = defaultdict(int)

periodic_table['Hydrogen'] = 1
periodic_table['Lead']

periodic_table # defaultdict(<class 'int'>, {'Hydrogen': 1, 'Lead': 0})

int(), list(), dict()함수는 인수가 없을 경우(None) 각각 0, [], {}을 기본값으로 반환한다.

lambda를 활용해서도 기본값을 만드는 함수 정의가 가능하다.

bestiary = defaultdict(lambda: 'Huh?')
bestiary['E'] # 'Huh?'

카운터를 만들기 위해 defaultdict(int)를 아래와 같이 활용 가능하다.

food_counter = defaultdict(int)
for food in ['spam', 'spam', 'eggs', 'spam']:
    food_counter[food] += 1
for food, count in food_counter.items():
    print(food, count)

# eggs 1
# spam 3

만약에 food_counter 딕셔너리가 일반 딕셔너리였다면,
food_counter[food]를 연산으로 증가시키 이전에 해당 [food]의 이름을 가진 키가 초기화되어있지 않기 때문에 오류가 발생한다.
하지만 위의 경우 defaultdict(int)를 통해 자동으로 0으로 초기화되기 때문에 오류가 발생하지 않는다.

항목 세기: Counter()

  • Counter(): 리스트의 항목을 세서 딕셔너리 형태 (항목: 키, 빈도: 값)으로 반환
from collections import Counter

breakfast = ['spam', 'spam', 'eggs', 'spam']
breakfast_counter = Counter(breakfast)
breakfast_counter
# Counter({'spam': 3, 'eggs': 1})

+, - 연산자를 활용하여 두 카운터를 결합하거나 뺄 수 있다. 또한 교집합 연산(&), 합집합 연산(|)을 활용하여 공통된 항목 혹은 전체 항목을 얻을 수 있다. 단, 합집합을 사용할 경우 양쪽 카운터에 공통 항목이 있을 경우, +와 같이 합치지 않고 더 높은 값으로 공통 항목을 잡는다.

lunch = ['eggs', 'eggs', 'bacon']
lunch_counter = Counter(lunch)
lunch_counter
# Counter({'eggs': 2, 'bacon': 1})

# + 연산자를 활용한 breakfast_counter와 lunch_counter 결합
breakfast_counter + lunch_couter
# Counter({'spam': 3, 'eggs': 3, 'bacon': 1})

# - 연산자를 활용한 breakfast_counter에서 lunch_counter 빼기
breakfast_counter - lunch_couter
# Counter({'spam': 3})

# - 연산자를 활용한 lunch_counter에서 breakfast_counter 빼기
lunch_couter - breakfast_counter
# Counter({'bacon': 1, 'eggs': 1})

# &을 활용한 교집합 항목 얻기
breakfast_counter & lunch_counter
# Counter({'eggs': 1})

# |을 활용한 합집합 항목 얻기
breakfast_counter | lunch_counter
# Counter({'spam': 3, 'eggs': 2, 'bacon': 1})
  • most_common(): 모든 요소를 내림차순으로 반환, 인수로 숫자를 입력하는 경우 그 숫자만큼 상위 요소를 반환
breakfast_counter.most_common()
[('spam', 3), ('eggs', 1)]
breakfast_counter.most_common(1)
[('spam', 3)]

Counter()는 딕셔너리, most_common()는 리스트 내 튜플 형태로 반환한다.

키 정렬하기: OrderedDict()

파이썬 3.7 이전에 딕셔너리를 출력할 경우, 출력 순서가 기존에 입력한 순서대로 유지되지 않는 특성이 있다.

키-값이 입력된 순서대로 유지하기 위해서는 (키, 값) 튜플의 시퀀스로 OrderedDict를 만들어야 한다.

파이썬 3.7 부터 딕셔너리에서 키가 추가된 순서대로 유지되므로, 위의 방법을 안써도 됨

from collections import OrderedDict

quotes = OrderedDict([
    ('Moe', 'A wise gutyy, huh?'),
    ('Larry', 'Ow!'),
    ('Curly', 'Nyuk nyuk!'),
    ])
for stooge in quotes:
    pritn(stooge)

# Moe
# Larry
# Curly

스택 + 큐 == 데크

  • deque: 스택과 큐의 기능을 모두 가진 출입구가 양 끝에 있는 큐
    활용
    1. 시퀀스의 양 끝으로부터 항목을 추가하거나 삭제할 때
    2. 회문인지 확인할 때

회문(palindrome): 앞에서부터 읽으나, 뒤에서 부터 읽으나 같은 구문

# 회문인지 확인하는 코드
def palindrome(word):
    from collections import deque
    dq = deque(word)
    while len(dq) > 1:
        if dq.popleft() != dq.pop():
            return False
    return True

palidrome('a') # True
palidrome('racecar') # True
palidrome('') # True
palidrome('radar') # True
palidrome('halibut') # False

deque를 활용하지 않더라도 아래와 같이 간단하게 코드 작성이 가능하다.
Python에는 문자열에 대한 reverse() 메서드가 없지만, [::-1]을 통해 문자열 반전이 가능

def another_palindrome(word):
    return word == word[::-1]
another_palindrome('radar') # True
another_palindrome('halibut') # False

코드 구조 순회하기: itertools

  • itertools: 특수 목적의 이터레이터 함수들을 포함하고 있음

for ... in 반복문에서 이터레이터 함수를 호출할 때, 함수는 한 번에 한 항목을 반환하고 호출 상태를 기억한다.

  • itertools.chain(): 순회가능한 인수들을 차례로 반복
import itertools
for item in itertools.chain([1, 2], ['a', 'b']):
    print(item)
# 1
# 2
# 3
# 4
  • itertools.cycle(): 인수를 순환하는 무한 이터레이터
import itertools
for item in itertools.cycle([1, 2]):
    print(item)
# 1
# 2
# 1
# 2
# 1
.
. 
(반복)
  • itertools.accumulate(): 축적된 값을 합계로 계산하는 이터레이터
import itertools
for item in itertools.accumulate([1, 2, 3, 4]):
    print(item)

# 1
# 3
# 6
# 10

accumulate() 함수의 두 번째 인수로 함수를 전달하여, 합계 대신 이 함수를 사용한 결과를 반환할 수 있음

import itertools
def multiply(a, b):
    return a * b

for item in itertools.accumulate([1, 2, 3, 4], multiply):
    print(item)
# 1
# 2
# 6
# 24

itertools 모듈에는 많은 함수를 제공하며, 조합 및 순열을 위한 함수도 존재

깔끔하게 출력하기: pprint()

  • pprint(): 가독성을 위해 요소들을 정렬하여 출력
from pprint import pprint
quotes = OrderedDict([
    ('Moe', 'A wise gutyy, huh?'),
    ('Larry', 'Ow!'),
    ('Curly', 'Nyuk nyuk!'),
    ])

print(quotes) # OrderedDict([('Moe', 'A wise quy, huh?'), ('Larry', 'Ow!'), ('Curly', 'Nyuk nyuk!')])

pprint(quotes)
# {'Moe', 'A wise guy, huh?'
#  'Larry', 'Ow!'
#  'Curly', 'Nyuk nyuk!'
# }

임의값 얻기

  • random.choice(): 주어진 시쿼스(리스트, 튜플, 딕셔너리, 문자열) 인수에서 값을 임의로 반환
from random import choice
choice([23, 9, 46, 'bacon', 0x123abc]) # 1194684
choice('alphabet') # 'l'
  • random.sample(): 한 번에 둘 이상의 값을 얻음, 두 번째 인수로 얻을 값 갯수를 전달
from random import sample
sample([23, 9, 46, 'bacon', 0x123abc], 3)
# [1194684, 23, 9]

sample('alphabet', 7)
# ['l', 'e', 'a', 't', 'p', 'a', 'b']
  • randint(): 정수 범위에서 랜덤의 정수를 반환
from random import randint
randint(38, 74) # 71
randint(38, 74) # 60
randint(38, 74) # 61
  • randrange(): 시작, 끝, 스탭(option값)으로 인수를 전달하여 랜덤의 정수를 반환
from random import randrange
randrange(38, 74) # 65
randrange(38, 74, 10) # 68
randrange(38, 74, 10) # 48

random()을 인수 없이 호출하면, 0.0과 0.1 사이의 임의의 실수를 반환

from random import random
random() # 0.07193393312692198

0개의 댓글