프로그램의 출력 표현 방법에는 여러 가지가 있습니다. 사람이 보기에 적합한 형태로 데이터를 인쇄할 수도 있고, 나중에 사용하기 위해 파일에 쓸 수도 있습니다.

7.1. 출력 포매팅

지금까지 우리는 값을 쓰는 두 가지 방법을 만났습니다: 표현식 문장 과 print() 함수입니다. (세 번째 방법은 파일 객체의 write() 메서드를 사용하는 것입니다; 표준 출력 파일은 sys.stdout 로 참조할 수 있습니다. 이것에 대한 자세한 정보는 라이브러리 레퍼런스를 보세요.)

종종 단순히 스페이스로 구분된 값을 인쇄하는 것보다 출력 형식을 더 많이 제어해야 하는 경우가 있습니다. 출력을 포맷하는 데는 여러 가지 방법이 있습니다.

  • 포맷 문자열 리터럴을 사용하려면, 시작 인용 부호 또는 삼중 인용 부호 앞에 f 또는 F 를 붙여 문자열을 시작하십시오. 이 문자열 안에서, { 및 } 문자 사이에, 변수 또는 리터럴 값을 참조할 수 있는 파이썬 표현식을 작성할 수 있습니다.
  • >>>
    year = 2016
    event = 'Referendum'
    
    print(f'Results of the {year} {event}')
    print('Results of the {year} {event}')
    
    ## 실행결과
    Results of the 2016 Referendum
    Results of the {year} {event}
  • 문자열의 str.format() 메서드는 더 많은 수작업을 요구합니다. 변수가 대체 될 위치를 표시하기 위해 { 및 }를 사용하고, 자세한 포매팅 디렉티브를 제공할 수 있지만, 포맷할 정보도 제공해야 합니다.
  • >>>
    yes_votes = 42_572_654
    no_votes = 43_132_495
    percentage = yes_votes / (yes_votes + no_votes)
    
    print('{:-9} YES votes  {:2.2%}'.format(yes_votes, percentage))
    
    ## 실행결과
     42572654 YES votes  49.67%
  • 마지막으로, 문자열 슬라이싱 및 이어붙이기 연산을 사용하여 상상할 수 있는 모든 배치를 만듦으로써, 모든 문자열 처리를 수행할 수 있습니다. 문자열형에는 주어진 열 너비로 문자열을 채우는 데 유용한 연산을 수행하는 몇 가지 메서드가 있습니다.

보기좋은 출력이 필요하지 않고 단지 디버깅을 위해 일부 변수를 빠르게 표시하려면, repr() 또는 str() 함수를 사용하여 모든 값을 문자열로 변환할 수 있습니다.

str() 함수는 어느 정도 사람이 읽기에 적합한 형태로 값의 표현을 돌려주게 되어있습니다. 반면에 repr() 은 인터프리터에 의해 읽힐 수 있는 형태를 만들게 되어있습니다 (또는 그렇게 표현할 수 있는 문법이 없으면 SyntaxError 를 일으키도록 구성됩니다). 사람이 소비하기 위한 특별한 표현이 없는 객체의 경우, str() 는 repr() 과 같은 값을 돌려줍니다. 많은 값, 숫자들이나 리스트와 딕셔너리와 같은 구조들, 은 두 함수를 쓸 때 같은 표현을 합니다. 특별히, 문자열은 두 가지 표현을 합니다.

몇 가지 예를 듭니다:

>>>
>>> s = 'Hello, world.'
>>> str(s)
'Hello, world.'
>>> repr(s)
"'Hello, world.'"
>>> str(1/7)
'0.14285714285714285'
>>> x = 10 * 3.25
>>> y = 200 * 200
>>> s = 'The value of x is ' + repr(x) + ', and y is ' + repr(y) + '...'
>>> print(s)
The value of x is 32.5, and y is 40000...
>>> # The repr() of a string adds string quotes and backslashes:
... hello = 'hello, world\n'
>>> hellos = repr(hello)
>>> print(hellos)
'hello, world\n'
>>> # The argument to repr() may be any Python object:
... repr((x, y, ('spam', 'eggs')))
"(32.5, 40000, ('spam', 'eggs'))"

string 모듈에는 문자열에 값을 치환하는 또 다른 방법을 제공하는 Template 클래스가 포함되어 있습니다. $x와 같은 자리 표시자를 사용하고 이것들을 딕셔너리에서 오는 값으로 치환하지만, 포매팅에 기능이 적습니다.

7.1.1. 포맷 문자열 리터럴

포맷 문자열 리터럴(간단히 f-문자열이라고도 합니다)은 문자열에 f 또는 F 접두어를 붙이고 표현식을 {expression}로 작성하여 문자열에 파이썬 표현식의 값을 삽입할 수 있게 합니다.

선택적인 포맷 지정자가 표현식 뒤에 올 수 있습니다. 이것으로 값이 포맷되는 방식을 더 정교하게 제어할 수 있습니다. 다음 예는 원주율을 소수점 이하 세 자리로 반올림합니다.

>>>
import math
print(f'The value of pi is approximately {math.pi:.3f}.')

## 실행결과
The value of pi is approximately 3.142.

':' 뒤에 정수는 해당 필드의 최소 문자 폭이 됩니다. 줄 맞춤할 때 편리합니다.

>>>
table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
for name, phone in table.items():
	print(f'{name:10} ==> {phone:10d}')

## 실행결과
Sjoerd     ==>       4127
Jack       ==>       4098
Dcab       ==>       7678

다른 수정자를 사용하면 포맷되기 전에 값을 변환할 수 있습니다. '!a'는 ascii()를, '!s'는 str()을, '!r'는 repr()을 적용합니다.:

>>>
animals = 'eels'
print(f'My hovercraft is full of {animals}.')
My hovercraft is full of eels.

print(f'My hovercraft is full of {animals!r}.')
My hovercraft is full of 'eels'.

이러한 포맷 사양에 대한 레퍼런스는 포맷 명세 미니 언어에 대한 레퍼런스 지침서를 참조하십시오.

7.1.2. 문자열 format() 메서드

str.format() 메서드의 기본적인 사용법은 이런 식입니다:

>>>
print('We are the {} who say "{}!"'.format('knights', 'Ni'))

## 실행결과
We are the knights who say "Ni!"

중괄호와 그 안에 있는 문자들 (포맷 필드라고 부른다) 은 str.format() 메서드로 전달된 객체들로 치환됩니다. 중괄호 안의 숫자는 str.format() 메서드로 전달된 객체들의 위치를 가리키는데 사용될 수 있습니다.

>>>
>>> print('{0} and {1}'.format('spam', 'eggs'))
spam and eggs
>>> print('{1} and {0}'.format('spam', 'eggs'))
eggs and spam

str.format() 메서드에 키워드 인자가 사용되면, 그 값들은 인자의 이름을 사용해서 지정할 수 있습니다.

>>>
print('This {food} is {adjective}.'.format(food='spam', adjective='absolutely horrible'))

This spam is absolutely horrible.

위치와 키워드 인자를 자유롭게 조합할 수 있습니다:

>>>
print('The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred',other='Georg'))

The story of Bill, Manfred, and Georg.

나누고 싶지 않은 정말 긴 포맷 문자열이 있을 때, 포맷할 변수들을 위치 대신에 이름으로 지정할 수 있다면 좋을 것입니다. 간단히 딕셔너리를 넘기고 키를 액세스하는데 대괄호 '[]' 를 사용하면 됩니다.

>>>
table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
print('Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '	'Dcab: {0[Dcab]:d}'.format(table))

Jack: 4098; Sjoerd: 4127; Dcab: 8637678

〈**〉 표기법을 사용해서 table을 키워드 인자로 전달해도 같은 결과를 얻을 수 있습니다.

>>>
table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
print('Jack: {Jack:d}; Sjoerd: {Sjoerd:d}; Dcab: {Dcab:d}'.format(**table))

Jack: 4098; Sjoerd: 4127; Dcab: 8637678

이 방법은 모든 지역 변수들을 담은 딕셔너리를 돌려주는 내장 함수 vars() 와 함께 사용할 때 특히 쓸모가 있습니다.

예를 들어, 다음 줄은 정수와 그 제곱과 세제곱을 제공하는 빽빽하게 정렬된 열 집합을 생성합니다:

>>>
for x in range(1, 11):
	print('{0:2d} {1:3d} {2:4d}'.format(x, x*x, x*x*x))

 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

str.format() 를 사용한 문자열 포매팅의 완전한 개요는 포맷 문자열 문법 을 보세요.

7.1.3. 수동 문자열 포매팅

여기 같은 제곱수와 세제곱수 표를 수동으로 포매팅했습니다:

>>>
for x in range(1, 11):
	print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')
	# Note use of 'end' on previous line
	print(repr(x*x*x).rjust(4))

 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

(print() 의 동작 방식으로 인해 각 칼럼 사이에 스페이스 하나가 추가되었음에 유의하세요: 항상 인자들 사이에 스페이스를 추가합니다.)

문자열 객체의 str.rjust() 메서드는 왼쪽에 스페이스를 채워서 주어진 폭으로 문자열을 우측 줄 맞춤합니다. 비슷한 메서드 str.ljust() 와 str.center() 도 있습니다. 이 메서드들은 어떤 것도 출력하지 않습니다, 단지 새 문자열을 돌려줍니다. 입력 문자열이 너무 길면, 자르지 않고, 변경 없이 그냥 돌려줍니다; 이것이 열 배치를 엉망으로 만들겠지만, 보통 값에 대해 거짓말을 하게 될 대안보다는 낫습니다. (정말로 잘라내기를 원한다면, 항상 슬라이스 연산을 추가할 수 있습니다, x.ljust(n)[:n] 처럼.)

다른 메서드도 있습니다, str.zfill(). 숫자 문자열의 왼쪽에 0을 채웁니다. 플러스와 마이너스 부호도 이해합니다:

>>>
print('12'.zfill(5))
'00012'

print('-3.14'.zfill(7))
'-003.14'

print('3.14159265359'.zfill(5))
'3.14159265359'

 

7.1.4. 예전의 문자열 포매팅

% 연산자(모듈로)는 문자열 포매팅에도 사용할 수 있습니다. 'string' % values가 주어지면, string에 있는 % 인스턴스는 0개 이상의 values 요소로 대체됩니다. 이 연산을 흔히 문자열 보간(interpolation)이라고 합니다. 예를 들면:

>>>
import math
print('The value of pi is approximately %5.3f.' % math.pi)

The value of pi is approximately 3.142.

더 자세한 내용은 printf 스타일 문자열 포매팅 섹션에 나옵니다.

 

7.2. 파일을 읽고 쓰기

open() 은 파일 객체 를 리턴합니다.

Syntax : open(filename, mode)

텍스트 파일을 열 때에는 open 명령어를 사용한다. 기본적으로 '변수(개체이름) = open("파일명")' 형태로 코딩한다. 파일의 특정 위치(폴더)를 지정하지 않으면 python을 실행한 폴더에서 찾습니다.

mode는 쓰기모드 'w', 읽기모든 'r', 읽기쓰기모드 'r+', binary 모드는 'b' 로 표기합니다. 값을 주지않으면 디폴트는 'r'입니다.

>>>
>>> f = open('workfile', 'w')

첫 번째 인자는 파일 이름의 문자열입니다. 두 번째 인자는 파일이 사용될 방식의 문자열입니다. mode 는 파일을 읽기만 하면 'r', 쓰기만 하면 'w' (같은 이름의 이미 존재하는 파일은 삭제됩니다) 가 되고, 'a' 는 파일을 덧붙이기 위해 엽니다; 파일에 기록되는 모든 데이터는 자동으로 끝에 붙습니다. 'r+' 는 파일을 읽고 쓰기 위해 엽니다. mode 인자는 선택적인데, 생략하면 'r' 이 가정됩니다.

보통, 파일은 텍스트 모드 (text mode) 로 열리는데, 이 뜻은, 파일에 문자열을 읽고 쓰고, 파일에는 특정한 인코딩으로 저장된다는 것입니다. 인코딩이 지정되지 않으면 기본값은 플랫폼 의존적입니다. mode 에 'b' 는 파일을 바이너리 모드 (binary mode)로 엽니다. 데이터는 바이트열 객체의 형태로 읽고 쓰입니다. 텍스트를 포함하지 않는 모든 파일에는 이 모드를 사용해야 합니다.

텍스트 모드에서, 읽을 때의 기본 동작은 플랫폼 의존적인 줄 종료 (유닉스에서 \n, 윈도우에서 \r\n) 를 \n 로 변경하는 것입니다. 텍스트 모드로 쓸 때, 기본 동작은 \n 를 다시 플랫폼 의존적인 줄 종료로 변환합니다. 텍스트 파일의 경우는 문제가 안 되지만, JPEG 이나 EXE 파일과 같은 바이너리 데이터가 깨지게 됩니다. 그런 파일을 읽고 쓸 때 바이너리 모드를 사용합니다.

파일 객체를 다룰 때 with 키워드를 사용하는 것이 좋은 습관입니다. with를 쓰면 도중 예외가 발생하더라도 파일이 올바르게 닫힙니다. with 를 사용하는 것은 try-finally 블록을 쓰는 것에 비교해 훨씬 간결합니다.

>>>
with open('workfile') as f:
	read_data = f.read()

# We can check that the file has been automatically closed.
f.closed
True

with 키워드를 사용하지 않으면, f.close() 를 호출해서 파일을 닫고 사용된 시스템 자원을 즉시 반납해야 합니다.

with 키워드를 사용하거나 f.close()를 호출하지 않고 f.write()를 호출하면 프로그램이 성공적으로 종료되더라도 f.write()의 인자가 디스크에 완전히 기록되지 않을  있습니다.

파일 객체가 닫힌 후에 with 문이나 f.close() 를 호출하는 경우에 파일 객체를 사용하려면 실패합니다.

>>>
f.close()
f.read()

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.

7.2.1. 파일 객체의 매소드

이 섹션의 나머지 예들은 f 라는 파일 객체가 이미 만들어졌다고 가정합니다.

파일의 내용을 읽으려면, f.read(size) 를 호출하는데, 일정량의 데이터를 읽고 문자열 (텍스트 모드 에서) 이나 바이트열 (바이너리 모드에서) 로 돌려줍니다. size 는 선택적인 숫자 인자입니다. size 가 생략되거나 음수면 파일의 내용 전체를 읽어서 돌려줍니다; 파일의 크기가 기계의 메모리보다 크다면 시스템에 문제가 발생할 수 있습니다. 그렇지 않으면 최대 size 문자(텍스트 모드에서)나 size 바이트(바이너리 모드에서)를 읽고 돌려줍니다. 파일의 끝에 도달하면, f.read() 는 빈 문자열 ('') 을 돌려줍니다.

>>>
>>> f.read()
'This is the entire file.\n'
>>> f.read()
''

f.readline() 은 파일에서 한 줄을 읽습니다; 개행 문자 (\n) 는 문자열의 끝에 보존되고, 파일이 개행문자로 끝나지 않는 때에만 파일의 마지막 줄에서만 생략됩니다. 이렇게 반환 값을 모호하지 않게 만듭니다; f.readline() 가 빈 문자열을 돌려주면, 파일의 끝에 도달한 것이지만, 빈 줄은 '\n', 즉 하나의 개행문자만을 포함하는 문자열로 표현됩니다.

>>>
f.readline()
'This is the first line of the file.\n'

f.readline()
'Second line of the file\n'

f.readline()

파일에서 줄들을 읽으려면, 파일 객체에 대해 루핑할 수 있습니다. 이것은 메모리 효율적이고, 빠르며 간단한 코드로 이어집니다:

>>>
for line in f:
	print(line, end='')

This is the first line of the file.
Second line of the file

파일의 모든 줄을 리스트로 읽어 들이려면 list(f) 나 f.readlines() 를 쓸 수 있습니다.

f.write(string) 은 string 의 내용을 파일에 쓰고, 쓴 문자들의 개수를 돌려줍니다.

>>>
f.write('This is a test\n')

15

다른 형의 객체들은 문자열 (텍스트 모드에서) 이나 바이트열 객체 (바이너리 모드에서) 로 쓰기 전에 변환될 필요가 있습니다

>>>
value = ('the answer', 42)
s = str(value)  # convert the tuple to string
f.write(s)

18

f.tell() 은 파일의 현재 위치를 가리키는 정수를 돌려주는데, 바이너리 모드의 경우 파일의 처음부터의 바이트 수로 표현되고 텍스트 모드의 경우는 불투명한 숫자입니다.

파일 객체의 위치를 바꾸려면, f.seek(offset, whence) 를 사용합니다. 위치는 기준점에 offset 을 더해서 계산됩니다; 기준점은 whence 인자로 선택합니다. whence 값이 0이면 파일의 처음부터 측정하고, 1이면 현재 파일 위치를 사용하고, 2 는 파일의 끝을 기준점으로 사용합니다. whence 는 생략될 수 있고, 기본값은 0이라서 파일의 처음을 기준점으로 사용합니다.

>>>
f = open('workfile', 'rb+')
f.write(b'0123456789abcdef')
16

f.seek(5)      # Go to the 6th byte in the file
5

f.read(1)
b'5'

f.seek(-3, 2)  # Go to the 3rd byte before the end
13

f.read(1)
b'd'

텍스트 파일에서는 파일 시작에 상대적인 위치 변경만 허락되고 (예외는 seek(0, 2) 를 사용해서 파일의 끝으로 위치를 변경하는 경우입니다), 올바른 offset 값은 f.tell() 이 돌려준 값과 0뿐입니다. 그 밖의 다른 offset 값은 정의되지 않은 결과를 낳습니다.

파일 객체는 isatty() 나 truncate() 같은 몇 가지 메서드가 더 있습니다. 파일 객체에 대한 완전한 안내는 라이브러리 레퍼런스를 참조하세요.

 

7.2.2. json 으로 구조적인 데이터를 저장하기

문자열은 파일에 쉽게 읽고 쓸 수 있습니다. 숫자는 약간의 변형을 해야 하는데, read() 메서드가 문자열만을 돌려주기 때문입니다. 이 문자열을 int() 같은 함수로 전달해야만 하는데, '123' 같은 문자열을 받고 숫자 값 123을 돌려줍니다. 중첩된 리스트나 딕셔너리 같은 더 복잡한 데이터를 저장하려고 할 때, 수작업으로 파싱하고 직렬화하는 것이 까다로울 수 있습니다.

사용자가 반복적으로 복잡한 데이터형을 파일에 저장하는 코드를 작성하고 디버깅하도록 하는 대신, 파이썬은 JSON (JavaScript Object Notation) 이라는 널리 쓰이는 데이터 교환 형식을 사용할 수 있게 합니다. json 이라는 표준 모듈은 파이썬 데이터 계층을 받아서 문자열 표현으로 바꿔줍니다; 이 절차를 직렬화 (serializing) 라고 부릅니다. 문자열 표현으로부터 데이터를 재구성하는 것은 역 직렬화 (deserializing) 라고 부릅니다. 직렬화와 역 직렬화 사이에서, 객체를 표현하는 문자열은 파일이나 데이터에 저장되거나 네트워크 연결을 통해 원격 기계로 전송될 수 있습니다.

참고

 

JSON 형식은 데이터 교환을 위해 응용 프로그램들이 자주 사용합니다. 많은 프로그래머가 이미 이것에 익숙하므로, 연동성을 위한 좋은 선택이 됩니다.

객체 x 가 있을 때, 간단한 한 줄의 코드로 JSON 문자열 표현을 볼 수 있습니다:

>>>
import json

x = [1, 'simple', 'list']
print(json.dumps(x))

## 실행결과
[1, "simple", "list"]

dump()함수는 dumps() 함수의 변형이고 객체를 텍스트 파일 로 직렬화합니다. 그래서 f 가 쓰기를 위해 열린 텍스트 파일 이면, 다음과 같이 할 수 있습니다.

json.dump(x, f)

객체를 다시 디코드하려면, f 가 읽기를 위해 열린 텍스트 파일 객체일 때:

x = json.load(f)

직렬화로 리스트와 딕셔너리를 다룰 수 있지만, 임의의 클래스 인스턴스를 JSON 으로 직렬화하기 위해서는 약간의 수고가 더 필요합니다. json 모듈의 레퍼런스는 이 방법에 대한 설명을 담고 있습니다.

 

pickle - 피클 모듈

JSON 에 반해, pickle 은 임의의 복잡한 파이썬 객체들을 직렬화할 수 있는 프로토콜입니다. 파이썬에 국한되고 다른 언어로 작성된 응용 프로그램들과 통신하는데 사용될 수 없습니다. 기본적으로 안전하지 않기도 합니다: 믿을 수 없는 소스에서 온 데이터를 역 직렬화할 때, 숙련된 공격자에 의해 데이터가 조작되었다면 임의의 코드가 실행될 수 있습니다.

 

3 입력

 

3.1. input 함수

Python에서는 표준 입력 함수로 input 함수를 지원합니다.
일단 도움말을 통해서 input 함수의 사용법을 알아보도록 하겠습니다.

Ex12_input0.py

# 표준 입력
# input함수의 도움말을 확인해 보자
help(input)

실행 결과

Help on built-in function input in module builtins:

input(prompt=None, /)
    Read a string from standard input.  The trailing newline is stripped.

    The prompt string, if given, is printed to standard output without a
    trailing newline before reading input.

    If the user hits EOF (*nix: Ctrl-D, Windows: Ctrl-Z+Return), raise EOFError.
    On *nix systems, readline is used if available.

위의 도움말을 보면 input함수의 사용법은 다음과 같습니다.

  • 표준 입력에서 문자열을 읽습니다. 후행 줄 바꿈이 제거됩니다.
  • 프롬프트 문자열이 주어진 경우, 입력을 읽기 전에 후행 개행 표시 없이 표준 출력으로 인쇄됩니다.
  • 사용자가 EOF(*nix: Ctrl-D, Windows: Ctrl-Z+Return)를 누르면 EOFError를 발생시킵니다.
     
  • *nix 시스템에서 사용 가능한 경우 readline이 사용됩니다.

Ex13_input1.py

# 표준 입력
print('이름을 입력하세요', end="")
name = input();
print("이름 : {0}, type : {1}".format(name,type(name)))
name = input('이름을 입력하세요 ');
print("이름 : {0}, type : {1}".format(name,type(name)))
name = input('아무것도 입력하지 말고 EOF(Ctrl+D 또는 Ctrl+Z+Enter)를 입력해보세요');

실행 결과

이름을 입력하세요 한사람
이름 : 한사람, type : <class 'str'>
이름을 입력하세요 두사람
이름 : 두사람, type : <class 'str'>
아무것도 입력하지 말고 EOF(Ctrl+D 또는 Ctrl+Z+Enter)를 입력해보세요^D
Traceback (most recent call last):
  File "C:/PyThonProjects/Ex01/basic03/Ex13_input1.py", line 7, in <module>
    name = input('아무것도 입력하지 말고 EOF(Ctrl+D 또는 Ctrl+Z+Enter)를 입력해보세요');
EOFError: EOF when reading a line

"이름을 입력하세요"라고 나오면 "한사람"을 입력하세요
"이름을 입력하세요"라고 나오면 "두사람"을 입력하세요
"아무것도 입력하지 말고 EOF(Ctrl+D 또는 Ctrl+Z+Enter)를 입력해보세요"라고 나오면 윈도우인 경우 Ctrl키와 Z키를 같이 눌러 보세요

2. 정수 입력

기본적으로 input함수는 문자열로 입력됩니다.
그래서 입력받은 값을 정수형으로 변환해서 사용해야 합니다.

  • eval() 함수 : 인수를 유효한 파이썬 표현식으로 리턴 합니다.
  • int() 클래스 : class int(x=0), class int(x,base=10)
    숫자나 문자열 x 로 부터 만들어진 정수 객체를 돌려줍니다.
    인자가 주어지지 않으면 0 을 돌려줍니다.
    base는 진법을 나타내며 주로 2, 8, 10, 16을 사용합니다. 10이 기본값입니다.

Ex14_input2.py

# 표준 입력
data = input("정수를 입력하시오 : ")
print(data, type(data))
# print(data, type(data), data + 1) 에러 문자열과 정수를 +(더하기)할 수 없습니다.

data = eval(input("정수를 입력하시오 : "))
print(data, type(data), data + 1)

data = int(input("정수를 입력하시오 : "))
print(data, type(data), data + 1)

data = int(input("2진수를 입력하시오 : "), 2)
print(data, type(data), data + 1)

data = int(input("8진수를 입력하시오 : "), 8)
print(data, type(data), data + 1)

data = int(input("10진수를 입력하시오 : "), 10)
print(data, type(data), data + 1)

data = int(input("16진수를 입력하시오 : "), 16)
print(data, type(data), data + 1)

실행 결과

정수를 입력하시오 : 1
1 <class 'str'>
정수를 입력하시오 : 2
2 <class 'int'> 3
정수를 입력하시오 : 3
3 <class 'int'> 4
2진수를 입력하시오 : 1010
10 <class 'int'> 11
8진수를 입력하시오 : 10
8 <class 'int'> 9
10진수를 입력하시오 : 10
10 <class 'int'> 11
16진수를 입력하시오 : 1a
26 <class 'int'> 27

실행할 때 차례대로 1, 2, 3, 1010, 10, 10, 1a를 입력해보세요

3. 실수 입력

기본적으로 input함수는 문자열로 입력됩니다.
그래서 입력받은 값을 실수형으로 변환해서 사용해야 합니다.

  • eval() 함수 : 인수를 유효한 파이썬 표현식으로 리턴 합니다.
  • float() 클래스 : class float(x)
    숫자나 문자열 x 로 부터 만들어진 실수 객체를 돌려줍니다.
    인자가 주어지지 않으면 0 을 돌려줍니다.

Ex15_input3.py

# 표준 입력
data = input("실수를 입력하시오 : ")
print(data, type(data))
# 에러 문자열과 실수를 +(더하기)할 수 없습니다.
# print(data, type(data), data + 1.2)

data = eval(input("실수를 입력하시오 : "))
print(data, type(data), data + 1.2)

data = float(input("정수를 입력하시오 : "))
print(data, type(data), data + 1.2)

실행 결과

실수를 입력하시오 : 3.14
3.14 <class 'str'>
실수를 입력하시오 : 3.14
3.14 <class 'float'> 4.34
정수를 입력하시오 : 3.14
3.14 <class 'float'> 4.34

4. 튜플(tuple), 리스트(list)로 입력받기

기본적으로 input함수는 문자열로 입력됩니다.

  • eval() 함수 : 인수를 유효한 파이썬 표현식으로 리턴 합니다.

Ex16_input4.py

# 표준 입력
string = input("(1,2) 처럼입력하시오 ")
print(string, type(string))

string = eval( input("(1,2) 처럼입력하시오 "))
print(string, type(string))

string = input("[1,2,3,4,5,6] 처럼입력하시오 ")
print(string, type(string))

string = eval( input("[1,2,3,4,5,6] 처럼입력하시오 "))
print(string, type(string))

실행 결과

(1,2) 처럼입력하시오 (1,2)
(1,2) <class 'str'>
(1,2) 처럼입력하시오 (1,2)
(1, 2) <class 'tuple'>
[1,2,3,4,5,6] 처럼입력하시오 [1,2,3]
[1,2,3] <class 'str'>
[1,2,3,4,5,6] 처럼입력하시오 [1,2,3]
[1, 2, 3] <class 'list'>

 

'Python' 카테고리의 다른 글

Python - 표준라이브러리 II  (0) 2022.01.06
Python - 오류 및 예외  (0) 2022.01.06
Python - module  (0) 2022.01.06
Python - class  (0) 2022.01.06
python - decorator  (0) 2022.01.05

파이썬 모듈은 전역변수, 함수등 을 모아둔 파일 입니다.

파이썬 인터프리터를 종료한 후에 다시 들어가면, 여러분이 만들었던 정의들이 사라집니다 (함수나 변수들). 그래서 파일로 따로 만들어서 재사용하면 좋습니다. 이렇게 하는 것을 스크립트를 만든다고 합니다. 프로그램이 길어짐에 따라, 유지관리를 쉽게 하려고 여러 개의 파일로 나눌 수 있습니다. 여러 프로그램에서 썼던 편리한 함수를 각 프로그램에 정의를 복사하지 않고 사용할 수 있습니다.

 

이런 것을 지원하기 위해, 파이썬은 정의들을 파일에 저장하고 스크립트나 인터프리터의 대화형 모드에서 사용할 수 있는 방법을 제공합니다. 그런 파일을 모듈 이라고 부릅니다.

 

모듈의 정의들은 다른 모듈이나 메인 모듈로 임포트 될 수 있습니다.

(메인 모듈은 최상위 수준에서 실행되는 스크립트나 계산기 모드에서 액세스하는 변수들의 컬렉션입니다).

모듈은 파이썬 정의와 문장들을 담고 있는 파일입니다. 파일의 이름은 모듈 이름에 확장자 .py 를 붙입니다. 모듈 내에서, 모듈의 이름은 전역 변수 __name__ 으로 제공됩니다.

 

예제) 편집기로 fibo.py 라는 이름의 파일을 아래 내용으로 만듭니다.

# Fibonacci numbers module

def fib(n):    # write Fibonacci series up to n
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a+b
    print()

def fib2(n):   # return Fibonacci series up to n
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)
        a, b = b, a+b
    return result

VSCode에서 이 모듈을 다음과 같은 명령으로 임포트 합니다:

import fibo

단, fibo 에 정의된 함수들은 현재 심볼 테이블에 직접 들어가지는 않습니다. 오직 모듈 이름 fibo 만 들어갑니다. 이 모듈 이름을 사용해서 함수들을 액세스할 수 있습니다:

import fibo

fibo.fib(1000)
print(fibo.fib2(100))
print(fibo.__name__)

## 실행결과
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
fibo

함수를 자주 사용할 거면 지역 이름으로 지정하여 사용할 수도 있습니다.

import fibo

fib = fibo.fib
fib(500)

## 실행결과
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

6.1. 모듈 더 보기

모듈은 함수 정의뿐만 아니라 실행 가능한 문장들도 포함할 수 있습니다. 이 문장들은 모듈을 초기화하는 데 사용됩니다. 이것들은 임포트 문에서 모듈 이름이 처음 등장할 때만 실행됩니다. (이것들은 파일이 스크립트로 실행될 때도 실행됩니다.)

각 모듈은 자신만의 심볼 테이블을 갖고 있는데, 그 모듈에서 정의된 함수들의 전역 심볼 테이블로 사용됩니다. 그래서, 모듈의 작성자는 사용자의 전역 변수와 충돌할 것을 걱정하지 않고 전역 변수를 사용할 수 있습니다. 반면에, 여러분이 무얼 하는지 안다면, 모듈의 함수를 참조하는데 사용된 것과 같은 표기법으로 모듈의 전역 변수들을 건드릴 수 있습니다, modname.itemname.

모듈은 다른 모듈들을 임포트할 수 있습니다. 모든 import 문들을 모듈의 처음에 놓는 것이 관례지만 반드시 그래야 하는 것은 아닙니다 (스크립트도 마찬가집니다). 임포트되는 모듈 이름은 임포트하는 모듈의 전역 심볼 테이블에 들어갑니다.

모듈에 들어있는 이름들을 직접 임포트하는 모듈의 심볼 테이블로 임포트하는 import 문도 있습니다. 예를 들어:

>>>
from fibo import fib, fib2
fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

fibo.fib(30)
## 실행결과 : fibo를 import 한게 아니기 때문에 오류발생
    fibo.fib(30)
NameError: name 'fibo' is not defined

이것은 지역 심볼 테이블에 임포트되는 모듈의 이름을 만들지 않습니다 (그래서 이 예에서는, fibo 가 정의되지 않습니다).

모듈이 정의하는 모든 이름을 임포트하는 것도 있습니다.

from fibo import *
fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

이것은 밑줄 (_) 로 시작하는 것들을 제외한 모든 이름을 임포트 합니다. 대부분 파이썬 프로그래머들은 이 기능을 사용하지 않는데, 인터프리터로 알려지지 않은 이름들의 집합을 도입하게 되어, 여러분이 이미 정의한 것들을 모호하게 할 수 있기 때문입니다.

일반적으로 모듈이나 패키지에서 * 를 임포트하지 않습니다. 하지만, 대화형 세션에서 입력을 줄이고자 사용하는 것은 상관없습니다.

모듈 이름 다음에 as 를  쓸 경우, as 다음의 이름을 임포트한 모듈에 직접 연결합니다.

>>>
import fibo as fb
fb.fib(500)

0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

이것은 import fibo 와 같은 방식으로 모듈을 임포트 하는데,  차이점은 그 모듈을 fb 라는 이름으로 사용할 수 있다는 것입니다.

from을 쓸 떄도 사용할 수 있습니다:

>>>
>>> from fibo import fib as fibonacci
>>> fibonacci(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

참고

 

효율성의 이유로, 각 모듈은 인터프리터 세션마다 한 번만 임포트됩니다. 그래서, 여러분이 모듈을 수정하면, 인터프리터를 다시 시작시켜야 합니다 — 또는, 대화형으로 시험하는 모듈이 하나뿐이라면, importlib.reload() 를 사용하세요.

예를 들어, import importlib; importlib.reload(modulename).

6.1.1. 모듈을 스크립트로 실행하기

여러분이 파이썬 모듈을 이렇게 실행하면

python fibo.py <arguments>

모듈에 있는 코드는, 모듈을 임포트할 때처럼 실행됩니다.  __name__ 은 "__main__" 로 설정됩니다.

이 코드를 모듈의 끝에 붙여서 사용합니다.

if __name__ == "__main__":
    import sys
    fib(int(sys.argv[1]))

파일을 임포트할 수 있는 모듈뿐만 아니라 스크립트로도 사용할 수 있도록 만들 수 있음을 의미하는데, 오직 모듈이 《메인》 파일로 실행될 때만 명령행을 파싱하는 코드가 실행되기 때문입니다:

$ python fibo.py 50
0 1 1 2 3 5 8 13 21 34

모듈이 다른 모듈로 임포트될 때에 코드는 실행되지 않습니다:

>>>
import fibo

이것은 종종 모듈에 대한 편리한 사용자 인터페이스를 제공하거나 테스트 목적으로 사용됩니다

6.1.2. 모듈 검색 경로

spam 이라는 이름의 모듈이 임포트될 때, 인터프리터는 먼저 그 이름의 내장 모듈을 찾습니다. 발견되지 않으면, 변수 sys.path 로 주어지는 디렉터리들에서 spam.py 라는 이름의 파일을 찾습니다. sys.path 는 이 위치들로 초기화됩니다:

  • 입력 스크립트에 포함된 디렉터리 (또는 파일이 지정되지 않았을 때는 현재 디렉터리).
  • PYTHONPATH (디렉터리 이름들의 목록, 셸 변수 PATH 와 같은 문법).
  • The installation-dependent default (by convention including a site-packages directory, handled by the site module).

참고

 

심볼릭 링크를 지원하는 파일 시스템에서, 입력 스크립트에 포함된 디렉터리는 심볼릭 링크를 변환한 후에 계산됩니다. 다른 말로, 심볼릭 링크를 포함하는 디렉터리는 모듈 검색 경로에 포함되지 않습니다.

초기화 후에, 파이썬 프로그램은 sys.path 를 수정할 수 있습니다. 스크립트를 포함하는 디렉터리는 검색 경로의 처음에, 표준 라이브러리 경로의 앞에 놓입니다. 이것은 같은 이름일 경우 라이브러리 디렉터리에 있는 것 대신 스크립트를 포함하는 디렉터리의 것이 로드된다는 뜻입니다. 이 치환이 의도된 것이 아니라면 에러입니다. 더 자세한 정보는 표준 모듈들 을 보세요.

6.1.3. 《컴파일된》 파이썬 파일

모듈 로딩을 빠르게 하려고, 파이썬은 __pycache__ 디렉터리에 각 모듈의 컴파일된 버전을 module.version.pyc 라는 이름으로 캐싱합니다. version 은 컴파일된 파일의 형식을 지정합니다; 일반적으로 파이썬의 버전 번호를 포함합니다. 예를 들어, CPython 배포 3.8 에서 spam.py 의 컴파일된 버전은 __pycache__/spam.cpython-38.pyc 로 캐싱 됩니다. 이 명명법은 서로 다른 파이썬 배포와 버전의 컴파일된 모듈들이 공존할 수 있도록 합니다.

파이썬은 소스의 수정 시간을 컴파일된 버전과 비교해서 시효가 지나 다시 컴파일해야 하는지 검사합니다. 이것은 완전히 자동화된 과정입니다. 또한, 컴파일된 모듈은 플랫폼 독립적이기 때문에, 같은 라이브러리를 서로 다른 아키텍처 시스템들에서 공유할 수 있습니다.

파이썬은 두 가지 상황에서 캐시를 검사하지 않습니다. 첫째로, 명령행에서 직접 로드되는 모듈들은 항상 재컴파일하고 그 결과를 저장하지 않습니다. 둘째로, 소스 모듈이 없으면 캐시를 검사하지 않습니다. 소스 없는 (컴파일된 파일만 있는) 배포를 지원하려면, 컴파일된 모듈이 소스 디렉터리에 있어야 하고, 소스 모듈이 없어야 합니다.

전문가를 위한 몇 가지 팁

  • 컴파일된 모듈의 크기를 줄이려면 파이썬 명령에 -O 나 -OO 스위치를 사용할 수 있습니다. -O 스위치는 assert 문을 제거하고, -OO 스위치는 assert 문과 __doc__ 문자열을 모두 제거합니다. 어떤 프로그램들은 이것들에 의존하기 때문에, 무엇을 하고 있는지 아는 경우만 이 옵션을 사용해야 합니다. 《최적화된》 모듈은 opt- 태그를 갖고, 보통 더 작습니다. 미래의 배포에서는 최적화의 효과가 변경될 수 있습니다.
  • .py 파일에서 읽을 때보다 .pyc 파일에서 읽을 때 프로그램이 더 빨리 실행되지는 않습니다; .pyc 파일에서 더 빨라지는 것은 로드되는 속도뿐입니다.
  • 모듈 compileall 은 디렉터리에 있는 모든 모듈의 .pyc 파일들을 만들 수 있습니다.
  • 이 절차에 대한 더 자세한 정보, 결정들의 순서도를 포함합니다, 는 PEP 3147 에 나옵니다.

<<< HERE >>>

6.2. 표준 모듈들

파이썬은 표준 모듈들의 라이브러리가 함께 오는데, 별도의 문서 파이썬 라이브러리 레퍼런스 (이후로는 《라이브러리 레퍼런스》) 에서 설명합니다. 어떤 모듈들은 인터프리터에 내장됩니다; 이것들은 언어의 핵심적인 부분은 아니지만 그런데도 내장된 연산들에 대한 액세스를 제공하는데, 효율이나 시스템 호출과 같은 운영 체제 기본 요소들에 대한 액세스를 제공하기 위함입니다. 그런 모듈들의 집합은 설정 옵션인데 기반 플랫폼 의존적입니다. 예를 들어, winreg 모듈은 윈도우 시스템에서만 제공됩니다. 특별한 모듈 하나는 주목을 받을 필요가 있습니다: sys. 모든 파이썬 인터프리터에 내장됩니다. 변수 sys.ps1 와 sys.ps2 는 기본과 보조 프롬프트로 사용되는 문자열을 정의합니다:

>>>
>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print('Yuck!')
Yuck!
C>

이 두 개의 변수들은 인터프리터가 대화형 모드일 때만 정의됩니다.

변수 sys.path 는 인터프리터의 모듈 검색 경로를 결정하는 문자열들의 리스트입니다. 환경 변수 PYTHONPATH 에서 취한 기본 경로나, PYTHONPATH 가 설정되지 않는 경우 내장 기본값으로 초기화됩니다. 표준 리스트 연산을 사용해서 수정할 수 있습니다:

>>>
>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')

6.3. dir() 함수

내장 함수 dir() 은 모듈이 정의하는 이름들을 찾는 데 사용됩니다. 문자열들의 정렬된 리스트를 돌려줍니다:

>>>
>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)  
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__',
 '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__',
 '__stderr__', '__stdin__', '__stdout__', '__unraisablehook__',
 '_clear_type_cache', '_current_frames', '_debugmallocstats', '_framework',
 '_getframe', '_git', '_home', '_xoptions', 'abiflags', 'addaudithook',
 'api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix',
 'breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing',
 'callstats', 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_info',
 'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info',
 'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth',
 'getallocatedblocks', 'getdefaultencoding', 'getdlopenflags',
 'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile',
 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval',
 'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
 'intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value',
 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks',
 'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'pycache_prefix',
 'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'setdlopenflags',
 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr',
 'stdin', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info',
 'warnoptions']

인자가 없으면, dir() 는 현재 정의한 이름들을 나열합니다:

>>>
>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']

모든 형의 이름을 나열한다는 것에 유의해야 합니다: 변수, 모듈, 함수, 등등.

dir() 은 내장 함수와 변수들의 이름을 나열하지 않습니다. 그것들의 목록을 원한다면, 표준 모듈 builtins 에 정의되어 있습니다:

>>>
>>> import builtins
>>> dir(builtins)  
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException',
 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning',
 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError',
 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning',
 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False',
 'FileExistsError', 'FileNotFoundError', 'FloatingPointError',
 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError',
 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError',
 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError',
 'MemoryError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented',
 'NotImplementedError', 'OSError', 'OverflowError',
 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError',
 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning',
 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError',
 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError',
 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning',
 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__build_class__',
 '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs',
 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable',
 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits',
 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit',
 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr',
 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass',
 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview',
 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property',
 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice',
 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars',
 'zip']

6.4. 패키지

패키지는 《점으로 구분된 모듈 이름》 를 써서 파이썬의 모듈 이름 공간을 구조화하는 방법입니다. 예를 들어, 모듈 이름 A.B 는 A 라는 이름의 패키지에 있는 B 라는 이름의 서브 모듈을 가리킵니다. 모듈의 사용이 다른 모듈의 저자들이 서로의 전역 변수 이름들을 걱정할 필요 없게 만드는 것과 마찬가지로, 점으로 구분된 모듈의 이름들은 NumPy 나 Pillow 과 같은 다중 모듈 패키지들의 저자들이 서로의 모듈 이름들을 걱정할 필요 없게 만듭니다.

음향 파일과 과 음향 데이터의 일관된 처리를 위한 모듈들의 컬렉션 (《패키지》) 을 설계하길 원한다고 합시다. 여러 종류의 음향 파일 형식이 있으므로 (보통 확장자로 구분됩니다, 예를 들어: .wav, .aiff, .au), 다양한 파일 형식 간의 변환을 위해 계속 늘어나는 모듈들의 컬렉션을 만들고 유지할 필요가 있습니다. 또한, 음향 데이터에 적용하고자 하는 많은 종류의 연산들도 있으므로 (믹싱, 에코 넣기, 이퀄라이저 기능 적용, 인공적인 스테레오 효과 만들기와 같은), 이 연산들을 수행하기 위한 모듈들을 끊임없이 작성하게 될 것입니다. 패키지를 이렇게 구성해 볼 수 있습니다 (계층적 파일 시스템으로 표현했습니다):

sound/                          Top-level package
      __init__.py               Initialize the sound package
      formats/                  Subpackage for file format conversions
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  Subpackage for sound effects
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  Subpackage for filters
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

패키지를 임포트할 때, 파이썬은 sys.path 에 있는 디렉터리들을 검색하면서 패키지 서브 디렉터리를 찾습니다.

파이썬이 디렉터리를 패키지로 취급하게 만들기 위해서 __init__.py 파일이 필요합니다. 이렇게 하면 string 처럼 흔히 쓰는 이름의 디렉터리가, 의도하지 않게 모듈 검색 경로의 뒤에 등장하는 올바른 모듈들을 가리는 일을 방지합니다. 가장 간단한 경우, __init__.py 는 그냥 빈 파일일 수 있지만, 패키지의 초기화 코드를 실행하거나 뒤에서 설명하는 __all__ 변수를 설정할 수 있습니다.

패키지 사용자는 패키지로부터 개별 모듈을 임포트할 수 있습니다, 예를 들어:

import sound.effects.echo

이것은 서브 모듈 sound.effects.echo 를 로드합니다. 전체 이름으로 참조되어야 합니다.

sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

서브 모듈을 임포트하는 다른 방법은 이렇습니다:

from sound.effects import echo

이것도 서브 모듈 echo 를 로드하고, 패키지 접두어 없이 사용할 수 있게 합니다. 그래서 이런 식으로 사용할 수 있습니다:

echo.echofilter(input, output, delay=0.7, atten=4)

또 다른 방법은 원하는 함수나 변수를 직접 임포트하는 것입니다:

from sound.effects.echo import echofilter

또다시, 이것은 서브 모듈 echo 를 로드하지만, 함수 echofilter() 를 직접 사용할 수 있게 만듭니다:

echofilter(input, output, delay=0.7, atten=4)

from package import item 를 사용할 때, item은 패키지의 서브 모듈 (또는 서브 패키지)일 수도 있고 함수, 클래스, 변수 등 패키지에 정의된 다른 이름들일 수도 있음에 유의하세요. import 문은 먼저 item이 패키지에 정의되어 있는지 검사하고, 그렇지 않으면 모듈이라고 가정하고 로드를 시도합니다. 찾지 못한다면, ImportError 예외를 일으킵니다.

이에 반하여, import item.subitem.subsubitem 와 같은 문법을 사용할 때, 마지막 것을 제외한 각 항목은 반드시 패키지여야 합니다; 마지막 항목은 모듈이나 패키지가 될 수 있지만, 앞의 항목에서 정의된 클래스, 함수, 변수 등이 될 수는 없습니다.

6.4.1. 패키지에서 * 임포트 하기

이제 from sound.effects import * 라고 쓰면 어떻게 될까? 이상적으로는, 어떻게든 파일 시스템에서 패키지에 어떤 모듈들이 들어있는지 찾은 다음, 그것들 모두를 임포트 하기를 원할 것입니다. 이렇게 하는 데는 시간이 오래 걸리고 서브 모듈을 임포트 함에 따라 어떤 서브 모듈을 명시적으로 임포트할 경우만 일어나야만 하는 원하지 않는 부수적 효과가 발생할 수 있습니다.

유일한 해결책은 패키지 저자가 패키지의 색인을 명시적으로 제공하는 것입니다. import 문은 다음과 같은 관례가 있습니다: 패키지의 __init__.py 코드가 __all__ 이라는 이름의 목록을 제공하면, 이것을 from package import * 를 만날 때 임포트 해야만 하는 모듈 이름들의 목록으로 받아들입니다. 새 버전의 패키지를 출시할 때 이 목록을 최신 상태로 유지하는 것은 패키지 저자의 책임입니다. 패키지 저자가 패키지에서 * 를 임포트하는 용도가 없다고 판단한다면, 이것을 지원하지 않기로 할 수도 있습니다. 예를 들어, 파일 sound/effects/__init__.py 는 다음과 같은 코드를 포함할 수 있습니다:

__all__ = ["echo", "surround", "reverse"]

이것은 from sound.effects import * 이 sound.effects 패키지의 세 서브 모듈들을 임포트하게 됨을 의미합니다.

__all__ 이 정의되지 않으면, 문장 from sound.effects import * 은 패키지 sound.effects 의 모든 서브 모듈들을 현재 이름 공간으로 임포트 하지 않습니다; 이것은 오직 패키지 sound.effects 가 임포트 되도록 만들고 (__init__.py 에 있는 초기화 코드들이 수행될 수 있습니다), 그 패키지가 정의하는 이름들을 임포트 합니다. 이 이름들은 __init__.py 가 정의하는 모든 이름 (그리고 명시적으로 로드된 서브 모듈들)을 포함합니다. 이 이름들에는 사전에 import 문으로 명시적으로 로드된 패키지의 서브 모듈들 역시 포함됩니다. 이 코드를 생각해봅시다:

import sound.effects.echo
import sound.effects.surround
from sound.effects import *

이 예에서, echo 와 surround 모듈이 현재 이름 공간으로 임포트 되는데, from...import 문이 실행될 때 sound.effects 패키지에 정의되기 때문입니다. (__all__ 이 정의될 때도 마찬가집니다.)

설사 어떤 모듈이 import * 를 사용할 때 특정 패턴을 따르는 이름들만 익스포트 하도록 설계되었다 하더라도, 프로덕션 코드에서는 여전히 좋지 않은 사례로 여겨집니다.

from package import specific_submodule 을 사용하는데 잘못된 것은 없다는 것을 기억하세요! 사실, 임포트하는 모듈이 다른 패키지에서 같은 이름의 서브 모듈을 사용할 필요가 없는 한 권장되는 표기법입니다.

6.4.2. 패키지 내부 간의 참조

패키지가 서브 패키지들로 구조화될 때 (예에서 나온 sound 패키지처럼), 이웃 패키지의 서브 모듈을 가리키는데 절대 임포트를 사용할 수 있습니다. 예를 들어, 모듈 sound.filters.vocoder 이 sound.effects 패키지의 echo 모듈이 필요하면, from sound.effects import echo 를 사용할 수 있습니다.

상대 임포트를 쓸 수도 있는데, from module import name 형태의 임포트 문을 사용합니다. 이 임포트는 상대 임포트에 수반되는 현재와 부모 패키지를 가리키기 위해 앞에 붙는 점을 사용합니다. 예를 들어, surround 모듈에서, 이렇게 사용할 수 있습니다:

from . import echo
from .. import formats
from ..filters import equalizer

상대 임포트가 현재 모듈의 이름에 기반을 둔다는 것에 주의하세요. 메인 모듈의 이름은 항상 "__main__" 이기 때문에, 파이썬 응용 프로그램의 메인 모듈로 사용될 목적의 모듈들은 반드시 절대 임포트를 사용해야 합니다.

6.4.3. 여러 디렉터리에 있는 패키지

패키지는 특별한 어트리뷰트 하나를 더 지원합니다, __path__. 이것은 패키지의 __init__.py 파일을 실행하기 전에, 이 파일이 들어있는 디렉터리의 이름을 포함하는 리스트로 초기화됩니다. 이 변수는 수정할 수 있습니다; 그렇게 하면 그 이후로 패키지에 포함된 모듈과 서브 패키지를 검색하는 데 영향을 주게 됩니다.

이 기능이 자주 필요하지는 않지만, 패키지에서 발견되는 모듈의 집합을 확장하는 데 사용됩니다.

각주

1

사실 함수 정의도 〈실행〉 되는 〈문장〉 입니다; 모듈 수준의 함수 정의를 실행하면 함수의 이름이 전역 심볼 테이블에 들어갑니다.

'Python' 카테고리의 다른 글

Python - 오류 및 예외  (0) 2022.01.06
Python - 입출력  (0) 2022.01.06
Python - class  (0) 2022.01.06
python - decorator  (0) 2022.01.05
파이썬 - list  (0) 2022.01.05

클래스는 객체를 표현하기 위한 문법으로 사람, 자동차 같은 특정한 개념이나 모양을 나타냅니다.

이를 실체로 구현한 것을 객체(object)라고 부릅니다. 이렇게 객체를 사용한 프로그래밍 언어를 객체 지향(object oriented) 언어라고 부르며 파이썬에서 사용되는 list, dict 등도 클래스 입니다.

클래스에는 크게 속성(데이터, 변수)과 메소드(함수)로 구성됩니다.객체는 클래스의 인스턴스라고도 하며 이 객체를 생성하는 프로세스를 인스턴스화라고 합니다.

 

클래스 정의

파이썬에서 함수 정의가 def 키워드로 시작하는 것처럼 클래스 정의는 class 키워드로 시작합니다.
클래스 내부의 첫 번째 문자열은 docstring이라고 하며 클래스에 대한 간략한 설명을 적습니다. 필수 사항은 아니지만 적극 권장합니다.

다음은 간단한 클래스 정의입니다.

class MyNewClass:
    '''This is a docstring. I have created a new class'''
    pass

 

클래스는 모든 속성이 정의된 새 로컬 네임스페이스를 만듭니다. 속성은 데이터 또는 함수일 수 있습니다.
이중 밑줄 __로 시작하는 특수 속성도 있습니다. 예를 들어 __doc__은 해당 클래스의 독스트링을 제공합니다.
클래스를 정의하면 동일한 이름으로 새 클래스 객체가 생성됩니다. 이 클래스 개체를 사용하면 다른 속성에 액세스할 수 있을 뿐만 아니라 해당 클래스의 새 개체를 인스턴스화할 수 있습니다.

class Car:
    "첫번째 문장은 docstring입니다."
    year = 2020

    def run(self):
        print("주행을 시작합니다.")

print(Car.year)
print(Car.run)
Car.run(Car)
print(Car.__doc__)

## 실행결과
2020
<function Car.run at 0x000002240D806AF0>
주행을 시작합니다.
첫번째 문장은 docstring입니다.

 

 

오브젝트 생성

해당 클래스의 새로운 객체 인스턴스(인스턴스화)를 만드는 데 사용할 수도 있습니다. 객체를 생성하는 절차는 함수 호출과 유사합니다.

bmw = Car()

 

이렇게 하면 bmw라는 새 인스턴스가 생성됩니다. 객체명으로 객체의 속성에 액세스할 수 있습니다.
속성은 데이터 또는 메소드일 수 있습니다. 개체의 메서드는 해당 클래스의 기능입니다.
이것은 Car.run 이 함수 객체(클래스의 속성)이기 때문에 Person.run가 메소드 객체가 된다는 것을 의미합니다.

class Person:
    "This is a person class"
    age = 10

    def greet(self):
        print('Hello')

harry = Person()

print(Person.greet)
print(harry.greet)
harry.greet()

## 실행경과
<function Person.greet at 0x000002227ADF6AF0>
<bound method Person.greet of <__main__.Person object at 0x000002227ADB74F0>>
Hello

 

객체가 메서드를 호출할 때마다 객체 자체가 첫 번째 인수로 전달됩니다.harry.greet()는 Person.greet(harry)로 변환됩니다.
일반적으로 n개의 인수 목록으로 메서드를 호출하는 것은 첫 번째 인수 앞에 메서드의 개체를 삽입하여 생성된 인수 목록으로 해당 함수를 호출하는 것과 같습니다.
이러한 이유로 클래스에 있는 함수의 첫 번째 인수는 객체 자체여야 합니다. 이것을 관례적으로 self라고 합니다. 다른 이름으로 지정할 수 있지만 규칙을 따르는 것이 좋습니다.

Constructors(생성자)

이중 밑줄 __로 시작하는 클래스 함수는 특별한 의미를 가지므로 특수 함수라고 합니다.
특히  __init__() 함수는 해당 클래스의 새 개체가 인스턴스화될 때마다 호출됩니다.

이러한 유형의 함수는 객체 지향 프로그래밍(OOP)에서 생성자라고도 합니다. 일반적으로 모든 변수를 초기화하는 데 사용합니다.

class ComplexNumber:
    def __init__(self, r=0, i=0):
        self.real = r
        self.imag = i

    def get_data(self):
        print(f'{self.real}+{self.imag}j')


# Create a new ComplexNumber object
num1 = ComplexNumber(2, 3)

# Call get_data() method
# Output: 2+3j
num1.get_data()

# Create another ComplexNumber object
# and create a new attribute 'attr'
num2 = ComplexNumber(5)
num2.attr = 10

# Output: (5, 0, 10)
print((num2.real, num2.imag, num2.attr))

# but num1 object doesn't have attribute 'attr'
# AttributeError: 'ComplexNumber' object has no attribute 'attr'
print(num1.attr)

Output

2+3j
(5, 0, 10)
Traceback (most recent call last):
  File "<string>", line 27, in <module>
    print(num1.attr)
AttributeError: 'ComplexNumber' object has no attribute 'attr'

In the above example, we defined a new class to represent complex numbers. It has two functions, __init__() to initialize the variables (defaults to zero) and get_data() to display the number properly.

An interesting thing to note in the above step is that attributes of an object can be created on the fly. We created a new attribute attr for object num2 and read it as well. But this does not create that attribute for object num1.

위의 예에서 우리는 복소수를 나타내는 새로운 클래스를 정의했습니다. 여기에는 변수를 초기화하는 __init__() 함수(기본값은 0)와 숫자를 올바르게 표시하는 get_data()의 두 가지 함수가 있습니다.

위 단계에서 주목해야 할 점은 객체의 속성이 즉석에서 생성될 수 있다는 것입니다. 우리는 객체 num2에 대한 새로운 속성 attr을 생성했습니다.


속성 및 개체 삭제

del 문을 사용하여 개체의 모든 속성을 언제든지 삭제할 수 있습니다. 

>>> num1 = ComplexNumber(2,3)
>>> del num1.imag
>>> num1.get_data()
Traceback (most recent call last):
...
AttributeError: 'ComplexNumber' object has no attribute 'imag'

>>> del ComplexNumber.get_data
>>> num1.get_data()
Traceback (most recent call last):
...
AttributeError: 'ComplexNumber' object has no attribute 'get_data'

 

del 문을 사용하여 객체 자체를 삭제할 수도 있습니다.

c1 = ComplexNumber(1,3)
del c1

 

실제로는 그보다 더 복잡합니다. c1 = ComplexNumber(1,3)를 수행하면 메모리에 새 인스턴스 객체가 생성되고 c1이라는 이름이 이에 바인딩됩니다.

del c1 명령에서 이 바인딩이 제거되고 이름 c1이 해당 네임스페이스에서 삭제됩니다. 그러나 객체는 메모리에 계속 존재하며 다른 이름이 바인딩되지 않은 경우 나중에 자동으로 소멸됩니다.
Python에서 이러한 참조되지 않은 객체의 자동 파괴를 가비지 콜렉션이라고 합니다.

Python에서 객체를 삭제하면 이름 바인딩이 제거됩니다.

'Python' 카테고리의 다른 글

Python - 입출력  (0) 2022.01.06
Python - module  (0) 2022.01.06
python - decorator  (0) 2022.01.05
파이썬 - list  (0) 2022.01.05
파이썬 - lambda 함수  (0) 2022.01.05

python decoreator를 이해하기 위해서는 퍼스트 클래스 함수와 클로저를 이해하고 있어야 합니다. 만약 이해하고 계시지 않으시다면 밑의 강좌를 참고하여 주십시오.

파이썬 – 퍼스트클래스 함수 (First Class Function)
파이썬 – 클로저 (Closure)

 

데코레이터란 무엇일까요? 사전적 의미로는 “장식가” 또는 “인테리어 디자이너” 등의 의미를 가지고 있습니다. 이름 그대로, 자신의 방을 예쁜 벽지나 커튼으로 장식을 하듯이, 기존의 코드에 여러가지 기능을 추가하는 파이썬 구문이라고 생각하시면 됩니다.

 

Step1. 함수


>>> def foo():
...     return 1
...
>>> foo()
1

제일 기본이다. Python에 있어서 함수는 def 키워드로 함수명과 파라미터의 리스트(임의)를 이용해 정의한다. 또한 괄호를 붙인 이름을 지정하여 함수를 지정할 수 있다



Step2. 스코프


Python에서는 함수를 만들면 새로은 스코프가 만들어진다.  다시 말하자면 각각의 함수는 각각의 이름 공간을 가지고 있다는 의미이다.

Python에서 이것을 확인하기 위한 함수도 준비되어 있다. locals()라는 함수로 자신이 가진 로컬 이름 공간의 값을 dictionary 타입으로 반환한다.

locals() 함수는 현재의 local 변수를 dict type으로 던져주는 내장 함수입니다.

def foo(arg):
    x = 10
    print(locals())

foo(20)

## 실행결과
{'x': 10, 'arg': 20}

 또한 글로벌 이름 공간에 대해서는 globals() 함수로 확인가능하다.

y = 30
print(globals())

## 실행결과
{..., 'y': 30}

 

 

함수는 Python에 있어서 퍼스트 클래스 오브젝트이다.

Python에서는 함수는 객체입니다.

 

print(issubclass(int, object)) # Python내의 모든 객체는 같은 공통 클래스를 상속하여 만들어진다.
def foo():
    pass 

print(issubclass(foo.__class__, object))

## 실행결과
True
True

위 코드의 의미는, 함수를 일반적으로 다른 변수와 동일하게 취급한다는 것입니다. 즉, 다른 함수의 인수로 전달하거나 함수의 리턴값으로써 사용할 수 있다는 것입니다. 

def add(x, y):
    return x + y

def sub(x, y):
    return x - y
def apply(func, x, y):
    return func(x, y) 
    
print(apply(add, 2, 1))
print(apply(sub, 2, 1))

위의 예에서는 함수 apply는 파라미터로써 함수 add, sub를 인수로써 ( 다른 변수와 다르지 않게 ) 전달하고 있음을 알 수 있습니다. 

 

def outer():
    def inner():
        print("Inside inner")
    return inner # 1

foo = outer() #2
print(foo)

foo()

## 실행결과
<function outer.<locals>.inner at 0x000002BAEC7DEB00>
Inside inner

 위에서 살펴 봤던 코드와 다른 점은 #1에서 리턴값으로써 실행 결과를 보여주는 것이 아닌 함수 그 자체를 지정하고 있다는 것이다(inner() 가 아닌 inner 를 전달하고 있다).

 이것은 #2 와 같이 보통 대입가능하므로 foo에 함수를 넣어 실행하는 것이 가능하다는 것을 알 수 있다.

 

Step8. 클로저


 위에서 봤던 예를 살짝 변경해서 살펴보자.

>>> def outer():
...     x = 1
...     def inner():
...         print x
...     return inner
>>> foo = outer()
>>> foo.func_closure
(<cell at 0x...: int object at 0x...>,)

 inner는 outer에 의해 반환되는 함수로, foo에 저장되어 foo()에 의해 실행된다....는 말이 사실일까? Python의 변수 해결 규칙을 완전히 따르고 있는 말이지만, 라이프 사이클은 어떻게 되어 있을까? 변수 x는 outer함수가 실행되는 동안에만 존재한다. 여기서 outer함수는의 처리가 종료된 후에 inner함수가 foo에 대입하고 있으므로 foo()는 실행할 수 없는 것이 아닌가?

 그 예상과는 반대로 foo()는 실행가능하다. Python이 Function closure(클로저)의 기능을 가지고 있기 때문이다. 클로저는 글로벌 스코프 이외에 정의된 함수(예시의 경우에는 inner)를 "정의했을 때"의 자신을 포함한 스코프 정보를 기억하고 있는 것이다.  실제로 기억하고 있는가를 확인하기 위해 위와 같이 func_closure 프로퍼티를 호출하여 확인할 수 있다.

 "정의했을 때"라고 말한 것을 기억해두길 바란다. inner함수는 outer함수가 호출 될 때마다 새롭게 정의된다.

>>> def outer(x):
...     def inner():
...         print x
...     return inner
>>> print1 = outer(1)
>>> print2 = outer(2)
>>> print1()
1
>>> print2()
2

 위의 예에서는 print1이나 print2에 직접 값을 인수로써 넣지 않아도 각각의 내부의 inner함수가 어떤 값을 출력해야하나를 기억하고 있다. 이것을 이용해서 고정 인수를 얻도록 커스터마이즈한 함수를 생성하는 것도 가능하다.

 

 

Step9. 데코레이터


 드디어 데코레이터에 관련된 이야기를 할 때가 왔다. 여기까지의 이야기를 바탕으로 결론부터 말하자면, 데코레이터란 "함수를 인수로 얻고 대가로 새로운 함수로 돌려주는 cllable(*)"이다.

>>> def outer(some_func):
...     def inner():
...         print "before some_func"
...         ret = some_func() #1
...         return ret + 1
...     return inner
>>> def foo():
...     return 1
>>> decorated = outer(foo) #2
>>> decorated()
before some_func
2

 하나 하나 이해해보도록 하자. 여기서 파라미터로써 some_func를 취득하는 outer이라는 함수를 정의하고 있다. 여기서 outer 안에 inner이라는 내부 함수가 정의되어 있다.

 inner은 문자열을 print한 후에, #1을 반환하는 값을 취득하고 있다. some_func는 outer를 호출하는 것으로 다른 값을 얻을 수 있지만, 여기서는 그것이 무엇이 되었든 아무튼 일단 실행 (call)하고 그 결과에 1을 더한 값을 반환한다. 마지막으로 outer함수는 inner함수의 것을 반환한다.

 #2에서 some_func로써 foo를 outer를 실행한 리턴값을 변수 decorated에 저장하고 있다. foo를 실행하면 1이 반환되지만 outer에을 씌운 foo는 그것에 +1을 하여 2를 반환하고 있다. 말하자면 decorated는 foo의 데코레이터판(foo + 어떠한 처리)라고 할 수 있다.

 실제로 유용한 데코레이터를 사용할 때에는 decorated와 같이 다른 변수를 준비할 필요없이 foo에 그대로 대입하는 경우가 많다. 즉 아래와 같다는 것이다.

foo = outer(foo)

 또 이전의 foo는 부르지 않고, 보통 데코레이트된 foo가 반환되는 것이 된다. 실용적인 예를 살펴보자.

 어떠한 좌표의 객체를 유지하는 라이브러리를 사용하고자한다. 이 객체는 x와 y의 좌표 쌍을 가지고 있지만, 아쉽게도 덧셈이나 나눗셈 등 수학 처리 기능은 없다. 그러나 우리가 이 라이브러리를 이용해서 대량의 계산 처리를 할 필요가 있어 라이브러리의 소스를 개편해야하는 반갑지 않은 상황이다. 

 우선 접근법으로는 아래와 같이 add, sub와 같은 함수를 만들면 좋을 것이라고 생각한다.

>>> class Coordinate(object):
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...     def __repr__(self):
...         return "Coord: " + str(self.__dict__)
>>> def add(a, b):
...     return Coordinate(a.x + b.x, a.y + b.y)
>>> def sub(a, b):
...     return Coordinate(a.x - b.x, a.y - b.y)
>>> one = Coordinate(100, 200)
>>> two = Coordinate(300, 200)
>>> add(one, two)
Coord: {'y': 400, 'x': 400}

 여기서 예를 들어 [취급할 좌표계는 0이하일 필요가 있다]라는 체크 처리가 필요하다면 어떻게 할 수 있을까? 즉 아래와 같은 상황이 발생을 막아야한다면 어떻게 코드를 작성할 수 있을까?

>>> one = Coordinate(100, 200)
>>> two = Coordinate(300, 200)
>>> three = Coordinate(-100, -100)
>>> sub(one, two)
Coord: {'y': 0, 'x': -200}
>>> add(one, three)
Coord: {'y': 100, 'x': 0}

 sub(one, two)는 (0, 0)을, add(one, three)는 (100, 200)을 반환하길 바라는 것이다. 각각의 함수에 하한을 체크하는 처리를 작성하는 것을 생각해볼 수 있겠지만, 여기서 데코레이터를 사용하여 체크 처리를 한 번에 처리하도록 만들어보자.

>>> def wrapper(func):
...     def checker(a, b):
...         if a.x < 0 or a.y < 0:
...             a = Coordinate(a.x if a.x > 0 else 0, a.y if a.y > 0 else 0)
...         if b.x < 0 or b.y < 0:
...             b = Coordinate(b.x if b.x > 0 else 0, b.y if b.y > 0 else 0)
...         ret = func(a, b)
...         if ret.x < 0 or ret.y < 0:
...             ret = Coordinate(ret.x if ret.x > 0 else 0, ret.y if ret.y > 0 else 0)
...         return ret
...     return checker
>>> add = wrapper(add)
>>> sub = wrapper(sub)
>>> sub(one, two)
Coord: {'y': 0, 'x': 0}
>>> add(one, three)
Coord: {'y': 200, 'x': 100}

 이전의 foo = outer(foo) 부분은 동일하지만 유용한 체크 구조를 파라미터와 함수의 처리 결과에 대해 적용할 수 있다. 

 

Step10. @심볼의 적용


Python에서는 데코레이터의 기재에 관해서 @기호를 사용한다. 즉 아래의 코드를

>>> add = wrapper(add)

이와 같은 형식으로 작성할 수 있다.

>>> @wrapper
... def add(a, b):
...     return Coordinate(a.x + b.x, a.y + b.y)

여기까지 읽어봤을 때, classmethod나 staticmethod와 같이 유용한 데코레이터를 자신이 만드는 것은 꽤 레벨이 높지만 적어도 데코레이터를 사용하는 것은 그렇게 어렵지 않다. 단지 @decoratorname를 기재할 뿐이다. 

 

Step11. *args와 **kwargs


 바로 위에서 설명한 데코레이터 wrapper은 유용하지만 파라미터가 2개뿐인 함수에만 적용할 수 있다. 물론 그건 그것대로 괜찮지만 더욱 유연한 함수에 적용할 수 있도록 데코레이터를 작성하고 싶은 경우는 어떻게 하는 것이 좋을까?

 Pytho에는 이것을 지원하는 기능이 준비되어 있다. 상세한 내용을 공식 사이트의 문서를 읽으면 좋겠지만, 함수를 정의할 때는 파라미터에 *(아스터리스크)를 붙이면 임의의 수의 필수 파라미터를 수용하도록 할 수 있다.

>>> def one(*args):
...     print args
>>> one()
()
>>> one(1, 2, 3)
(1, 2, 3)
>>> def two(x, y, *args):
...     print x, y, args
>>> two('a', 'b', 'c')
a b ('c',)

 임의의 파라미터 부분과 관련해서 리스트를 전달하고 있다는 것을 알 수 있다. 또한 정의할 때뿐만 아니라 호출할 때에도 인수에 *를 붙이면 기존 리스트나 튜플 형식의 인수를 언패키징해서 고종 인수에 적용해준다(아래의 코드를 참고). 

>>> def add(x, y):
...     return x + y
>>> lst = [1,2]
>>> add(lst[0], lst[1]) #1
3
>>> add(*lst) #2 <- # #1과 완전히 같은 의미이다.
3

 또한 **(애스터리스크 2개) 기법도 존재한다. 여기서는 리스트가 아닌 사전형이 된다.

>>> def foo(**kwargs):
...     print kwargs
>>> foo()
{}
>>> foo(x=1, y=2)
{'y': 2, 'x': 1}

 **kwargs를 함수 정의에 사용하는 것은 "명시적으로 지정하지 않은 파라미터는 kwargs이라는 이름의 사전으로 저장된다"는 의미이다. *args와 동일하게 함수를 호출할 때의 언패키징에도 대응한다.

>>> dct = {'x': 1, 'y': 2}
>>> def bar(x, y):
...     return x + y
>>> bar(**dct)
3

 

 

Step12. 제네릭한 데코레이터


위의 기능을 이용하여 함수의 인수를 로그에 출력해주는 데코레이터를 작성해보자. 간략화하기 위해 로그 출력은 stdout에 print하자.

>>> def logger(func):
...     def inner(*args, **kwargs): #1
...         print "Arguments were: %s, %s" % (args, kwargs)
...         return func(*args, **kwargs) #2
...     return inner

 #1에서 inner함수는 임의의 개수, 형식의 파라미터를 취득하는 것이 가능하여, #2에서 그것을 언패키징하여 인수로써 전달할 수 있다는 것을 주목하자. 이것에 의해 어떠한 함수에 대해서도 데코레이터 logger을 적용할 수 있다.

>>> @logger
... def foo1(x, y=1):
...     return x * y
>>> @logger
... def foo2():
...     return 2
>>> foo1(5, 4)
Arguments were: (5, 4), {}
20
>>> foo1(1)
Arguments were: (1,), {}
1
>>> foo2()
Arguments were: (), {}
2

 

 

더욱 자세히 알아보고 싶은사람을 위해


 마지막의 예까지 이해했다면, 데코레이터의 기초에 대해 이해했다고 할 수 있다. 잘 모르겠다면 그 전 단계로 돌아가 다시 하나 하나 차분히 살펴보길 바란다. 혹시 데코레이터에 대해서 더욱 학습하고 싶은 경우 아래의 두 사이트를 추천한다. (영어) 

Decorators I: Introduction to Python Decorators

Python Decorators II: Decorator Arguments



출처: https://engineer-mole.tistory.com/181 [매일 꾸준히, 더 깊이]

 

일반 데코레이터

  1. 이렇게 코딩하고 실행하면
    # 얘가 데코레이터
    def decorator(func):
        def decorator(*args, **kwargs):
            print("%s %s" % (func.__name__, "before"))
            result = func(*args, **kwargs)
            print("%s %s" % (func.__name__ , "after"))
            return result
        return decorator
    
    # 함수에 데코레이터를 붙여준다.
    @decorator
    def func(x, y):
        print(x + y)
        return x + y
    
    func(1,2)
  2. 이런 결과가 나온다. 아! 신통방통 하다!
    func before
    3
    func after
  3. @데코레이터는 사실 이거랑 같은 의미라고 한다
    def decorator(func):
        def decorator(*args, **kwargs):
            print("%s %s" % (func.__name__, "before"))
            result = func(*args, **kwargs)
            print("%s %s" % (func.__name__ , "after"))
            return result
        return decorator
    
    def func(x, y):
        print(x + y)
        return x + y
    
    func2 = decorator(func)
    func2(1,2)

파라메터를 가지는 데코레이터

  1. 데코레이터에 뭔가 파라메터를 전달하고 싶을데는 약간 복잡하긴 하지만 역시 다 된다! function을 감싸는 decorator를 다시 감싸주면 된다.
    # 얘가 파라메터도 붙는 데코레이터
    def decorator_with_param(param):
        def wrapper(func):
            def decorator(*args, **kwargs):
                print(param)
                print("%s %s" % (func.__name__, "before"))
                result = func(*args, **kwargs)
                print("%s %s" % (func.__name__ , "after"))
                return result
            return decorator
        return wrapper
    
    @decorator_with_param("hello, decorator!")
    def func(x, y):
        print(x + y)
        return x + y
    
    func(1,2)
  2. 결과는 이렇게 나온다!
    hello, decorator!
    func before
    3
    func after

-------

본질적으로, decorators는 wrapper로써 동작하며, 원본 함수를 수정할 필요 없이 원본 함수의 실행 전/후의 동작을 변경할 수 있습니다

 

함수에 대해 알아야 할 것

깊게 들어가기 전에, 명확히 알아야 할 전제조건이 있습니다. python에서 함수는 일급시민이며, 일급객체입니다. 이 말은 아래에 나오는 여러 유용한 작업들을 할 수 있다는 의미입니다.

함수를 변수에 대입할 수 있다.

def greet(name):
    return "hello "+name

greet_someone = greet
print greet_someone("John")

# Outputs: hello John

함수 내부에 다른 함수를 정의할 수 있다.

def greet(name):
    def get_message():
        return "Hello "

    result = get_message()+name
    return result

print(greet("John"))

## 실행결과 
Hello John

함수는 다른 함수의 인자로 전달될 수 있다.

def greet(name):
   return "Hello " + name 

def call_func(func):
    other_name = "John"
    return func(other_name)  

print(call_func(greet))

## 실행결과
Hello John

함수는 다른 함수에 의해 리턴될 수 있다.

다른 말로 함수는 다른 함수를 생성 할 수 있습니다.

def compose_greet_func():
    def get_message():
        return "Hello there!"

    return get_message

greet = compose_greet_func()
print(greet())

## 실행결과
Hello there!

내부 함수는 둘러싸인 범위(enclosing scope)에 대해 접근이 가능하다.

보통 closure로 알려져 있습니다. 내부 함수의 근접한 scope로부터 name 인자를 읽기 위해 위의 예제로부터 어떻게 변경했는지 봅시다.

def compose_greet_func(name):
    def get_message():
        return "Hello there "+name+"!"

    return get_message

greet = compose_greet_func("John")
print(greet())

## 실행결과 
Hello there John!

Decorators의 구성

함수 decorators는 존재하는 함수에 대한 간단한 wrappers입니다. 위에서 언급한 아이디어를 모아서 decorators를 만들 수 있습니다. 아래 예제에서, p 태그에 의해 문자열을 출력하는 함수를 wrap 하는 것을 고려해보겠습니다.

def get_text(name):
   return "lorem ipsum, {0} dolor sit amet".format(name)

def p_decorate(func):
   def func_wrapper(name):
       return "<p>{0}</p>".format(func(name))
   return func_wrapper

my_get_text = p_decorate(get_text)

print my_get_text("John")

# <p>Outputs lorem ipsum, John dolor sit amet</p>

이게 우리의 첫 번째 decorator 입니다. 함수는 다른 함수를 인자로 전달 받고 기존 함수의 기능에 추가적인 작업을 하는 새로운 함수를 생성하고 리턴합니다. get_text 함수를 p_decorate로 꾸미고 싶다면 우린 단지 p_decorate의 리턴 값을 get_text에 할당하면 됩니다.

get_text = p_decorate(get_text)

print get_text("John")

# Outputs lorem ipsum, John dolor sit amet

Python’s Decorator 문법

python은 문법적인 sugar(컴퓨터 용어 sugar란 기존 기능을 좀 더 쉽게 쓰기 위한 문법적 방법)를 통해 programmer들에게 좀 더 명확하고 보기 좋은 decorator 방법을 제공합니다. get_text를 decorate 하기 위해 get_text = p_decorator(get_text)와 같은 코드를 쓸 필요가 없습니다. decorated 된 함수 전에 decoration 하는 함수의 이름을 언급하는 방법이 있습니다. decorator 이름과 함께 @ 심볼이 나와야 합니다.

def p_decorate(func):
   def func_wrapper(name):
       return "<p>{0}</p>".format(func(name))
   return func_wrapper

@p_decorate
def get_text(name):
   return "lorem ipsum, {0} dolor sit amet".format(name)

print get_text("John")

# Outputs <p>lorem ipsum, John dolor sit amet</p>

이제 우리는 get_text 함수를 div와 string 태그로 감싸는 두 개의 다른 함수를 고려해봅시다.

def p_decorate(func):
   def func_wrapper(name):
       return "<p>{0}</p>".format(func(name))
   return func_wrapper

def strong_decorate(func):
    def func_wrapper(name):
        return "<strong>{0}</strong>".format(func(name))
    return func_wrapper

def div_decorate(func):
    def func_wrapper(name):
        return "<div>{0}</div>".format(func(name))
    return func_wrapper

기본적인 접근방법으론, 아래 코드와 같이 decorating 할 수 있습니다.

get_text = div_decorate(p_decorate(strong_decorate(get_text)))

Python의 decorator 문법으로 동일한 기능을 제공합니다.

@div_decorate
@p_decorate
@strong_decorate
def get_text(name):
   return "lorem ipsum, {0} dolor sit amet".format(name)

print get_text("John")

# Outputs <div><p><strong>lorem ipsum, John dolor sit amet</strong></p></div>

여기서 한가지 중요한 것은 decorators의 순서입니다. 만약 위의 예제에서 decorators의 순서가 달라지면 결과도 달라집니다.

Class의 Method Decorating

Python에서 methods는 첫 번째 인자가 객체를 참조(self)하는 함수입니다. wrapper 함수의 인자를 self를 받는 방법으로 동일하게 methods에 대해 decorators를 만들 수 있습니다.

def p_decorate(func):
   def func_wrapper(self):
       return "<p>{0}</p>".format(func(self))
   return func_wrapper

class Person(object):
    def __init__(self):
        self.name = "John"
        self.family = "Doe"

    @p_decorate
    def get_fullname(self):
        return self.name+" "+self.family

my_person = Person()
print my_person.get_fullname()

더 좋은 접근은 우리의 decorator가 함수와 methods 모두에게 유용하도록 만드는 것입니다. 이 방법을 위해 wrapper 함수의 인자로 args 와 *kwargs를 넣고 이를 통해 가변인자와 가변 키워드 인자를 전달받을 수 있습니다.

def p_decorate(func):
   def func_wrapper(*args, **kwargs):
       return "<p>{0}</p>".format(func(*args, **kwargs))
   return func_wrapper

class Person(object):
    def __init__(self):
        self.name = "John"
        self.family = "Doe"

    @p_decorate
    def get_fullname(self):
        return self.name+" "+self.family

my_person = Person()

print my_person.get_fullname()

decorators에게 인자 전달하기

위의 예제들을 돌아보면, 많은 decorators 예제들이 중복되어 있는 것을 알 수 있습니다. 3개의 decorators( div_decorate, p_decorate, strong_decorate)를 살펴보면 name 변수의 string을 서로 다른 태그로 감싸는 것만 다르고 다른 동작은 모두 동일합니다. 우리는 명백하게 더 많은 것을 할 수 있어야만 합니다. tag를 인자로 받아 string을 감싸는 일반적인 구현방법은 없을까요? 제발염!

def tags(tag_name):
    def tags_decorator(func):
        def func_wrapper(name):
            return "<{0}>{1}</{0}>".format(tag_name, func(name))
        return func_wrapper
    return tags_decorator

@tags("p")
def get_text(name):
    return "Hello "+name

print get_text("John")

# Outputs <p>Hello John</p>

위 경우 조금 더 많은 작업을 했습니다. Decorators는 인자로 함수를 받기를 기대하기 때문에, 우리는 추가적인 인자를 받아 우리의 decorator를 즉석에서 만드는 함수를 정의했습니다. 위의 예제에선 tags로 우리의 decorator를 생성합니다.

decorated 된 함수 디버깅

결국 가장 중요한 것은 decorators는 단지 우리가 만든 함수를 wrapping 한다는 것입니다. 이 파트에선, wrapper 함수가 원본 함수의 name, module, docstring을 가지지 않기 때문에 발생하는 문제를 다룹니다. 위의 예제들을 기반으로 할 경우 :

print get_text.__name__
# Outputs func_wrapper

출력은 get_text로 기대했지만, get_text의 __name__, __doc__, __module__의 속성이 이런 warpper 함수들에 의해 오버라이딩 됩니다. 분명하게 우리는 wrapper 함수 안에서 이들을 reset 해줘야 하고, python은 훨씬 좋은 방법을 제공합니다.

Functools 이 대안입니다.

운좋게도 python (version 2.5)는 functools.wraps를 포함하는 functools 모듈을 가지고 있습니다. Wraps는 wrapping 하는 함수의 속성들을 원본 함수의 속성들로 update 해주는 decorator 입니다. 아래 예제가 있습니다.

from functools import wraps

def tags(tag_name):
    def tags_decorator(func):
        @wraps(func)
        def func_wrapper(name):
            return "<{0}>{1}</{0}>".format(tag_name, func(name))
        return func_wrapper
    return tags_decorator

@tags("p")
def get_text(name):
    """returns some text"""
    return "Hello "+name

print get_text.__name__ # get_text
print get_text.__doc__ # returns some text
print get_text.__module__ # __main__

이제 get_text의 속성이 제대로 나오는 것을 알 수 있습니다.

decorators는 어디에 쓰나?

decorators 는 우리의 프로그램을 더욱 더 강력하고 우아하게 만들어줍니다. 일반적으로 decorators는 우리가 함수의 기능변경을 원치 않는 상황에서 기능을 확장하고 싶을 때 사용하는 개념입니다.

'Python' 카테고리의 다른 글

Python - module  (0) 2022.01.06
Python - class  (0) 2022.01.06
파이썬 - list  (0) 2022.01.05
파이썬 - lambda 함수  (0) 2022.01.05
Python 코드를 실행하는 Visual Studio Code에서 code runner 메시지를 숨기는 방법  (0) 2022.01.04

1. List란?

파이썬의 자료구조 형태중 하나로 리스트는 값을 나열하는것 이라고 정의할 수 있겠습니다.
또한 List는 시퀀스데이터 이기도 합니다.
 
시퀀스는 데이터의 순서를 정하는것을 의미합니다.
순서를 가지고 있기 때문에 인덱스(index)를 사용하여 참조할 수 있습니다.
인덱스란 문자열이 있으면 번호를 뜻합니다.
Ex)문자열 인덱싱
a = 'hello' 문자열로 했을때, a[0]을 확인하면 첫번째 문자열인 h가 출력됩니다. 이것을 문자열 인덱싱이라 하며, 시퀀스자료형이기 때문에 가능한 것이죠.
 
 
파이썬에서의 시퀀스 자료형은?(순서를 정함)
string, list, tuple
 
시퀀스 자료형이 아닌것은?(순서를 정하지 않음)
set, dict

 

 

1.1 List 만들어보기

1
list_name = [1,2,3,4,5,6]
cs

이런식으로 리스트이름 = [] 대괄호 안에 숫자나 문자등을 넣어서 만들 수 있습니다.

 

1
2
3
list_name_num = [1,2,3,4,5,6]
list_name_string = ['h','i']
list_name_word = ['hello']
cs

이렇게 문자도 가능합니다.

 

1.2 List 인덱싱

1.2.1 인덱싱의 기초

앞서 말했듯 List는 파이썬의 시퀀스 자료형 이기 때문에 인덱스(쉽게말해 자리 번호표)를 가지고 있습니다.
그것을 가지고 연산을 할수 있는데 예제를 통해 알아보겠습니다.
 
 
1
2
= [1,2,3]
print(a[0])
cs

결과는 어떻게 나올까요?

a라는 [리스트] 안에 0번째 인덱스를 프린트 했을때 무엇이 나올까요?

답은 1 입니다.

 

실험. 만약에 없는 인덱스를 프린트 했을경우 어떻게 될까?

a[3]은 없는 인덱스이기 때문에 당연히 index out of range라는 에러가 나오게 됩니다.

 

1.2.2 List 인덱싱의 연산

이요소들을 합치면 어떤 반응이 일어날까요?
1
2
= [1,2,3]
print(a[0]+a[2])
cs

의 답은 무엇일까요?

a[0]은 1

a[2]는 3

즉 1+3이 되어 4가 나오게 될것입니다.

 

곱하기도 될까요?

1
2
= [1,2,3]
print(a[0]*a[2])
cs

네 물론 됩니다.

a[0]은 1

a[2]는 3

즉 1*3이 되어 3가 나오게 될것입니다.

 

그럼 빼기도 되겠죠?

1
2
= [1,2,3]
print(a[0]-a[2])
cs

네 물론 됩니다.

a[0]은 1

a[2]는 3

즉 1-3이 되어 -2가 나오게 될것입니다.

 

 

문자는 더하거나 곱하거나 빼면 어떻게 될까요?

1
2
= ['h','e','ll','o']
print(a[0]+a[2])
cs
답: hll

 

그외 곱하기와 빼기는 문자열 이기 때문에 안된다는 에러메시지가 나옵니다.

 

1.3 리스트 슬라이싱

리스트의 슬라이싱은 매우 간단합니다.
리스트의 요소들이 있다면 그중 원하는 부분만 잘라내서 가져오는 것으로 생각하시면 되는데 실습을 통해서 알아보겠습니다.
 
기본 문법은 출력시에 name[:] 이며
[num:] num 이상(포함하고 부터)
[:num] num 미만(포함하지 않고 까지)
[num1:num2] num1이상 ~ num2미만
[::-1] 거꾸로
[1:10:2] 1이상 10미만중 2개씩만
 
예제를 통해 알아보겠습니다.
 
1
2
3
4
5
6
7
8
9
number = [1,2,3,4,5,6,7,8,9,10]
 
print(number[1:3])#이상~미만
print(number[3:])#이상
print(number[:3])#미만
print(number[1:3:2])#1이상 3미만 2개씩
print(number[0:9:2])#0이상 9미만 2개씩
print(number[::-1])#거꾸로
 
cs

주의하실점은 num:num일때 이상~미만입니다.

1:3일때 1번째를 포함하는애 부터 3번째는 포함하지 않습니다

이 점만 주의하시면 슬라이싱하시는데 문제 없으실겁니다.

 

 

1.4 리스트 연산

1.4.1 리스트 더하기

너무나도 간단하기 때문에 예제만 봐도 충분이 이해가 가실겁니다.

그냥 리스트끼리 합쳐져서 순서대로 출력됩니다.

 

 

1.4.2 리스트 반복

리스트의 기초는 너무 쉬워요~! 그냥 리스트*숫자를 사용하면 숫자만크 리스트를 반복시킵니다

 

 

1.4.3 리스트 수정

 

1.4.4 리스트 삭제

1.4.5 리스트 요소 추가

리스트의 요소를 추가하는 방법은 간단합니다.
리스트.append(요소) 를 사용하시면 되는데 주의하실점은 한번에 두개의 요소가 추가되지는 않지만
리스트안에 리스트를 추가할 수는 있다는 점입니다.

여러개의 리스트요소를 추가하고 싶을때 맨아래 extend를 참고하시면 됩니다.

 

1.4.6 리스트 정렬(sort)

문자와 숫자를 함께 정렬할수는 없는 것을 확인하실 수 있습니다.

 

 

1.4.7 리스트 뒤집기(reverse)

숫자와 문자가 함께있어도 잘 뒤집힙니다.

 

 

1.4.7 리스트 위치 반환(index)

a안에 있는 3의 요소 index를 확인하고 싶을때

a안에 있는 1의 요소 index를 확인하고 싶을때

없는 요소를 확인하면 list에는 요소가 없다고 에러가 납니다.

 

 

1.4.8 리스트 요소 삽입(insert)

3번째 index에 4를 넣겠다는 뜻입니다.

a = [ 1,  2,  3 ]

[0]  [1] [2] index 이기 때문에 3번째에 4가 추가되어 [1,2,3,4]가 됩니다.

 

 

1.4.9 리스트 요소 제거(remove)

여러개의 요소가 있다면 맨앞에 한개의 요소만 제거합니다.

 

 

1.4.10 리스트 요소 꺼내기(pop)

가장 뒤에 있는 요소가 삭제됩니다.

 

 

1.4.11 리스트 요소 갯수 세기(count)

요소의 갯수를 카운트합니다.

 

 

1.4.12 리스트 확장(extend)

list안에 요소를 추가하는데 추가개념이 아닌 확장 개념이기 때문에 결과값이 다음과 같이 나오게 된것입니다. 



출처: https://rednooby.tistory.com/109 [개발자의 취미생활]

'Python' 카테고리의 다른 글

Python - class  (0) 2022.01.06
python - decorator  (0) 2022.01.05
파이썬 - lambda 함수  (0) 2022.01.05
Python 코드를 실행하는 Visual Studio Code에서 code runner 메시지를 숨기는 방법  (0) 2022.01.04
파이썬 시작하기  (0) 2022.01.03

lambda는 왜 쓰는가?

  • 익명함수이기 때문에 한번 쓰이고 다음줄로 넘어가면 힙(heap) 메모리 영역에서 증발
  • (참고) 가비지 컬렉터 (참조하는 객체가 없으면 지워버린다)
    • 파이썬에서는 모든것이 객체로 관리 되고 각 객체들은 레퍼런스 카운터를 갖게 됩니다. 이 카운터가 0 즉, 그 누구도 참조하지 않게 된다면 메모리를 환원 하게 됩니다.

 

람다함수는 적절한 곳에 잘 쓰인다면 코드가 간결해 보이지만, 그렇지 않고 남용하게 된다면 코드가 코드 가독성이 떨어질 수 있습니다. 

<목차>
1. lambda 함수 설명
2. lambda 함수와 map
3. lambda 함수와 filter

4. lambda 함수와 reduce

 

1. 파이썬 람다 함수 설명


원래 함수는 복잡한 명령들을 편하게 반복해서 사용할 수 있도록 모아두는 역할을 하는데, def 를 이용해서 다른곳에 함수를 만들고 그걸 또 호출해서 부르기까지의 수고가 필요하지 않은 그런 "일회성 함수"들을 위해서 만들어진게 람다 함수 입니다.

> 람다함수 선언 방법

lambda 인자: 표현식

lambda 라는 키워드를 입력하고 뒤에는 매개변수(인자)를 입력하고 콜론(:)을 넣은다음에 그 매개변수(인자)를 이용한 동작들을 적으면 됩니다.
예를 들면 인자로 들어온 값에 2를 곱해서 반환한다고 하면 lambda x : x * 2  이런식으로 됩니다.

> 람다함수와 일반함수 비교

위에서 설명했듯이 람다함수는 일반함수를 가볍게 만든 함수이기 때문에 
짝수를 판별하는 함수를 만든다고 했을때 일반함수는
def is_even(x):
  return x % 2 == 0
이런식으로 표현을 할 수 있습니다.

이걸 람다로 표현하게 되면
is_even = lambda x : x % 2 == 0
이런식으로 표현할수 있습니다.

물론 일반함수, 람다함수 둘다 사용법은 같습니다.

 


is_even(1) # False
is_even(2) # True

 

2. 파이썬 lambda 함수와 map


map함수는 리스트나 튜플에 어떤 특별한 처리를 할때 사용하는 함수입니다.

> map 함수의 모양과 사용법

map(함수, 리스트나 튜플)

첫번째 인자인 함수는 두번째 인자로 들어온 리스트나, 튜플에 특별한 가공 처리를 하는 함수이며, 사용자가 직접 함수를 정의해서 넣습니다.
두번째 인자인 리스트나 튜플은 바꾸고자 하는 데이터들을 집어 넣습니다.

예를들어 리스트의 모든값에 2씩 더해서 새로운 리스트를 만든다고 할때  map 함수는 아래와 같이 사용할 수 있습니다.
map 의 반환값을 리스트로 다시 변경할수 있는데요 이때는 list(map(~~~)) 이런식으로 list 로 형변환해주는 함수로 감싸주면 됩니다.

# 1. 일반 함수 버전
def plus_two(x):
    return x + 2

result1 = list(map(plus_two, [1, 2, 3, 4, 5]))
print(result1)

## 실행결과
[3, 4, 5, 6, 7]

# 2.  람다 함수 버전
result2 = list(map((lambda x: x + 2), [1, 2, 3, 4, 5]))
print(result2)

## 실행결과
[3, 4, 5, 6, 7]
# 1. 일반 함수 버전
def plus_two(x):
    return x + 2

result1 = list(map(plus_two, [1, 2, 3, 4, 5]))
print(result1)


# 2.  람다 함수 버전
result2 = list(map((lambda x: x + 2), [1, 2, 3, 4, 5]))
print(result2)

 

이런식으로 def를 통해서 함수를 map의 인자로 전달해줘도 되지만,
map을 통해서 "리스트를 가공하는게 이번 한번 뿐이다" 라는 상황이라던지 lambda를 사용해서 간단하게 작업이 가능한 경우라면
람다 함수 사용을 고려해보는 것도 좋을듯 합니다.

여기서 range를 또 결합하면 
result3 = list(map((lambda x : x + 2), range(2, 6))) 이런식으로도 쓸 수 있습니다.

result3 = list(map((lambda x : x + 2), range(2, 6)))
print(result3)

-- 실행결과
[4, 5, 6, 7]

이렇게 람다 함수는 map 함수와 같이 사용자 정의 함수를 받을때 유용하게 작성할 수 있습니다.

 

3. python 람다 함수와 filter


filter 함수는 map 함수와 비슷하게  첫번쨰 인자로 함수를 받고, 두번째 인자로 리스트나 튜플을 받습니다.

> filter 함수 모양과 설명

filter(함수, 리스트나 튜플)

첫번째 인자에는 두번째 인자로 들어온 리스트나 튜플을 하나하나씩 받아서 필터링할 함수를 넣습니다.
두번쨰 인자에는 리스트나 튜플을 집어 넣습니다.

예를들어 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 까지의 리스트가 있을때 짝수들만 filter 함수를 이용해서 리스트를 다시 만든다고 했을때, filter 함수와 람다를 이용할 수 있습니다.

# 1. 일반 함수 버전
def is_even(x):
    return x % 2 == 0


result1 = list(filter(is_even, range(10)))  # [0 ~ 9]
print(result1)

## 실행결과
[0, 2, 4, 6, 8]

# 2.  람다 함수 버전
result2 = list(filter((lambda x: x % 2 == 0), range(10)))  # [0 ~ 9]
print(result2)

## 실행결과
[0, 2, 4, 6, 8]

 

결과 화면을 보앗듯 filter 함수의 첫번째 인자가 True 인 경우의 값만 가지고와서 리스트를 만들었습니다.
 filter 함수와 람다 함수를 결합하여 깔끔하게 filter가 가능합니다.

일회성 함수를 위해서 "def 함수 ~~" 이런작업을 해주지 않아도 됩니다.

4. python 람다 함수와 reduce


python3 부터 내장함수에서 빠졌습니다.  사용하려면 functools 모듈에서 reduce 함수를 가져와야 합니다

 

filter 함수는 map 함수와 비슷하게  첫번쨰 인자로 함수를 받고, 두번째 인자로 리스트나 튜플을 받습니다.

reduce() 함수는 두 개의 필수 인자와 하나의 옵션 인자를 가지는데, function 을 사용해서 iterable을 하나의 값으로 줄입니다. initializer 초기값을 의미합니다.
reduce() 함수를 사용할 때 많은 분들이 실수하는 부분이 있는데 바로 초기값 세팅 유무에 따라 이상한 결과가 나올 수 있는 것입니다.

예를 들어, 유저의 나이 합계를 구하는 예제에서 초기값으로 0을 넘겨주지 않으면 TypeError가 발생합니다.


functools 모듈의 reduce 함수는 다음과 같습니다. 

def reduce(function, iterable, initializer=None):
    it = iter(iterable)
    if initializer is None:
        value = next(it)
    else:
        value = initializer
    for element in it:
        value = function(value, element)
    return value

 

Syntax

reduce(함수, 리스트나 튜플[, initializer])

 

reduce()

reduce()은 리스트의 아이템들을 누적해서 하나의 값으로 리턴해주는 함수입니다. Lambda와 함께 사용할 수 있습니다.

reduce()의 첫번째 인자로 Lambda를 전달하고, 두번째 인자에 리스트를 전달합니다.

# 일반함수를 사용한 경우
from functools import reduce

def sum(x, y): 
    return x+y 
    
print(reduce(sum, range(10)))

print(lambda x,y : x+y, range(10)))

## 수행결과
45

# lambda를 사용한 경우
from functools import reduce

print(reduce(lambda x,y : x+y, range(10)))

## 수행결과
45

위에서 reduce의 연산 과정을 살펴보면, 처음에는 0 + 1이 수행되고, 그 결과를 다음 연산에 사용합니다. 그래서 그 다음에는 1 + 2이 수행되고, 3 + 36 + 4... 순서로 진행되어 마지막 값이 리턴됩니다. 따라서 결과는 0에서 9까지 합한 숫자가 리턴됩니다.

 

 

 

다음의 예제처럼 초기값을 10으로 주면, reduce 함수 코드에서 봤던 value 의 값에 10이 저장된 상태로 시작하기 때문에 10에 0부터 9까지의 합인 45를 더한 값이 리턴됩니다.

from functools import reduce

print(reduce(lambda x, y: x+y, range(10), 10))

## 실행결과
55

 

reduce 함수로 5 factorial 을 계산해 봅시다. 

from functools import reduce

print(reduce(lambda x, y: x*y, range(1, 5)))

## 실행결과
24


reduce 함수로 factorial 함수를 만들 수도 있습니다.

from functools import reduce

def factorial(n):
    return(reduce(lambda x, y: x*y, range(1, n+1)))

print(factorial(6))

## 실행결과
720

 

참고로, 파이썬은 다양한 수학적 연산을 지원하는 math 모듈을 내장하고 있는데 이 모듈에서 팩토리얼 함수를 제공합니다.
from math import factorial

print(factorial(6))

## 실행결과
720

'Python' 카테고리의 다른 글

python - decorator  (0) 2022.01.05
파이썬 - list  (0) 2022.01.05
Python 코드를 실행하는 Visual Studio Code에서 code runner 메시지를 숨기는 방법  (0) 2022.01.04
파이썬 시작하기  (0) 2022.01.03
[Python] VSCode argument 설정  (1) 2021.12.31

Python 코드를 실행하는 Visual Studio Code에서 code runner 메시지를 숨기는 방법

 

1. launch.json 파일에 "console": "externalTerminal"을 설정하면 외부 콘솔에 표시되고 스크립트가 표시되지 않습니다.

print('Hello, world')

2. launch.json 파일에 "console": "internalConsole"을 설정하면 결과가 DEBUG CONSOLE 표시되고 code runner 메시지가 표시되지 않습니다.

print('Hello, world')

 

3. Code Runner extension을 설치하고 settings.json에 아래와 같이 파라미터를 추가합니다.

Windows의 경우 setting.json 파일은 %AppData%/Roaming/Code/User/settings.json 위치에 있습니다.

Code Runner Extension
# settings.json 파일 위치
%AppData%/Roaming/Code/User/settings.json

# settings.json 파일에 아래 파라미터 추가
"code-runner.clearPreviousOutput": true,
"code-runner.showExecutionMessage": false,
 

위의 코드를 추가한 다음 단축키(Ctrl + Alt + N)을 누른거나, 마우스 오른쪽 버튼을 클릭한 후 [Run Code] 메뉴를 클릭합니다. 또는 VSCode 우측 상단의 실행버튼(▷)을 클릭해도 됩니다.

print('Hello, world')
 
 

추가

'Python' 카테고리의 다른 글

파이썬 - list  (0) 2022.01.05
파이썬 - lambda 함수  (0) 2022.01.05
파이썬 시작하기  (0) 2022.01.03
[Python] VSCode argument 설정  (1) 2021.12.31
Python과 SQLite DB 연동하기 - 수정  (0) 2021.12.31

+ Recent posts