본 과정은 제가 파이썬 수업을 들으면서 배운 내용을 복습하는 과정에서 적어본 것입니다.
틀린 부분이 있다면 댓글에 남겨주시면 고치도록 하겠습니다.
확실하지 않은 내용은 ‘(?)’을 함께 적었으니 그 내용을 아신다면 댓글에 남겨주시면 감사하겠습니다.

  • 참고 - 파이썬 HOWTO [https://docs.python.org/ko/3/howto/index.html]
    • 로깅 HOWTO
    • Regular Expression HOWTO
    • 함수형 프로그래밍 HOWTO: 모든 기반을 함수로 하는 것 -> bigdata 처리할 때 효율적 - java, pandas

1) 함수

  • g(f(x))
  • input과 output이 1:1 로 매칭해야한다.
  • 정의역을 한거번에 처리하는데 효율적
  • LISP (?)
  • 하스켈에서 배껴왔다.
  • 함수형 프로그래밍은 함수들의 세트로 문제를 분해. 이상적으로 말하면, 함수들은 입력을 받아서 출력을 만들어내기만 하며, 주어진 입력에 대해 생성된 출력에 영향을 끼칠만한 어떠한 내부적인 상태도 가지지 않는다. 잘 알려진 함수형 언어로는 ML 계열(Standard ML, OCaml 및 다른 변형)과 하스켈이 있다.
  • 함수형 패러다임은 mutable이 없다. immutable 형만 있다.

  • 객체지향: class를 선언하고 인스턴스화

  • side effect 가 없어야 한다.
  • 파이썬은 기본 객체지향 패러다임을 지원해준다. 따라서 ‘순수 함수 패러다임’ 언어는 아니다.
  • 형식적 증명 가능성: 이론적인 장점은 함수형 프로그램이 정확하다는 수학적 증명을 만드는 것이 더 쉽다는 것
  • 결합성: 함수형 방식의 프로그램을 만들 때, 다양한 입력과 출력으로 여러 가지 함수를 작성하게 된다. 이러한 함수 중 일부는 불가피하게 특정 응용 프로그램에 특화될 수 있지만, 대체로 다양한 프로그램에서 유용하게 사용할 수 있다.

  • iterator: 이터레이터는 데이터 스트림을 나타내는 객체
a = int(input ()) # input이 또다른 함수값에 들어감
a= int(a)

# 값에 따라 중간값이 바뀌면 안된다. 
import time
def x(a=time.time):
    return a

time.time() # 현재시간을 보여주는 것 
# 함수형 패러다임에서는 쓰면 안되는 애이다. 

2) iterator

  • iterable: iterator가 될 수 있는 순회가능한 것
  • 이터레이터 함수는 숨어있다. (하나씩 순회해서 나오는 것)
for i in [1,2,3]: # in 다음에 이터레이터가 올 수 있다. -> 이터레이터 함수는 숨어있다. (하나씩 순회해서 나오는 것) -> i 가 이터레이터로 바뀐다.
    print (i)
    
out:
1
2
3

--------------------------

a=[1,2,3]
b= iter(a)
type(b) # list_iterator -> NEXT라는 애를 사용할 수 있다.

next(b)
list(b)
# next(b) # StopIteration: 4번째 때 에러가 난다.

out:
list_iterator
1
[2, 3]

--------------------------------

a=[1,2,3]
b= iter(a)
type(b) # list_iterator -> NEXT라는 애를 사용할 수 있다.

next(b)
next(b)
list(b)

out:
list_iterator
1
2
[3]

-------------------------------------

b # <list_iterator at 0x1a6d3c96e80>: <> 꺽쇠는 확인을 못한다.

list(b) # [] : next 는 앞에서 부터 뽑아낸다. pop은 뒤에서부터 뽑아낸다. 
# 팝은 최적화가 안되어있지만 넥스트는 최적화가 되어있어서 빠르다.
  • Lazy 기법: 실행될 때 메모리상에 다시 뽑아서 올린다. -> 빅데이터 상에서 많이 쓴다.
  • next 할 때 메모리가 올라가기 때문에 메모리상 효율적이다. 하지만 속도의 문제가 있다. (속도가 좀 떨어짐)
  • 파이썬은 이를 감안해서 속도를 올릴 수 있도록 만듦.
import dis # dis: 기계어로 분해 

def iterator_exam():
    for i in [1,2,3]:
        print(i)
        
dis.dis(iterator_exam) # iterator_exam의 흐름을 보여줌        
  • set, str, tuple -> iterator로 만들 수 있다.
a={1,2,3}
b= iter(a)
type(b) # set_iterator: set 도 이터레이터로 만들 수 있다. 

a=(1,2,3)
b= iter(a)
type(b) # tuple_iterator: tuple 도 이터레이터 가능 

a= '경아'
b= iter(a)
type(b) # str_iterator: 문자열도 이터레이터 가능 

# type을 바꾸면 역할과 기능이 다르기 때문에 타입 바꾸면 그 역할을 잃어버림 

  • iter은 function
int('3') # iterator 와 비슷해보이지만 완전히 다른 기법 -> int 는 class
iter # iter 은 function 이다. 
  • [참조] 객체 값을 만드는 방법 2가지 1) 클래스 -> 인스턴스화 2) 리터럴 - 리터럴(literal)은 몇몇 내장형들의 상숫값을 위한 표기법
# 객체 값을 만드는 방법은 2가지 
# 1) 클래스 -> 인스턴스화
a= int(1) # 기호가 하나씩 붙어있다. 기호에 따라서 내부적으로 체크된다. 이 기호를 literal이라고 한다. 

# 2) 리터럴
# 리터럴(literal)은 몇몇 내장형들의 상숫값을 위한 표기법
# a=0b2  #(2진수)
a=2e1 # 2 * 10^1
a # 20.0 # 인스턴화하지 않아도 자동으로 float으로 나타나짐 
a=2e-1 
a # 0.2

##### 
a= u'ab' # 유니코드 
a= r'ab' # raw 형태의 유니코드: 그대로 나오는 것  
a= 'ab\n' # ab: \n-> 개행문자 
a= 'ab\nc' # ab <br> c 
print(a)
a= f'ab\n' 
type(a) # str: 타입 붙여주는 것을 리터럴 
a=1.2

# 내장 객체는 앞에 소문자 카멜 방식으로 쓴다. (실행시킬 때: class 만들 때 첫글자 소문자 쓰지 말라고 한다. ) 
# 객체지향은 class 를 선언하고 인스턴스화 & 리터럴 방식도 지원 
# type 쳤을 때 나오는 애가 int 

a = int() # ?설명: / 포지셔널 온리, *args 가변 포지션, **kwargs 가변 키워드 
a # 0 -> False 
# a=int(1) -> 객체:a (인스턴스) 

b= float() # ?설명: / 포지셔널 온리 
b # 0.0

b= list()
b # []-> 아무것도 없기 때문에 False (존재론적 무)

b= set()
b # set이 원래는 없었다. 기호자체가 이미 할당 되어 있었기 때문에 이렇게 나온다. 
iter # <function iter>: 객체가 아니고 function이다. 
# type을 바꿔주는 function은 거의 없다. 
# iter 예외적인 function ** 중요 
# type을 바꿔주려면 인스터스화해서 새로 만들어야한다. 

3) generator

  • generator 만드는 2가지 방법 1) comprehension -> tuple 2) yield 넣은다.
# iter와 비슷한 generator -> next 밖에 쓸 수 없다. 만드는 방식이 다르다. 
# generator 은 함수를 만들어서 만든다. 

def generator_exam():
    yield 1
    yield 2
    yield 3
    
x = generator_exam() # iterator 과 똑같다. 따라서 next 쓸 수 있다. 

next(x) # StopIteration: 4번 실행시키면 에러가 나온다. 

def generator_exam():
    yield from [1,2,3,4] # from 도 쓸 수 있다. 
     
x = generator_exam()   

next(x) # StopIteration: 5번 실행시키면 에러가 나온다. 
  • iterator: iterable 인 애를 (객체를) 바꿀 수 있는 것 - 속도 최적화 했다.
  • generator 은 내 마음대로 next를 쓸 수 있는 애
    • 장점:
      • 메모리 효율성 좋다.
      • 속도 최적화 했다. (python)
    • 함수로 만드는 방식이 있고 …
    • 식으로 만들 수 있다.
    • 제너레이터는 이터레이터를 작성하는 작업을 단순화하는 특별한 클래스의 함수입니다.

4) 반복식 (comprehension)

  • comprehension -> 하스켈에서 배낀 것
  • comprehension은 식이다.
[x for x in range(10)] # for문 구조랑 같다. 

out:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  • timeit: 여려번 실행시켜서 평균값 보여줌
%timeit [x for x in range(10)] 
# 밑에거와 비교했을 때 comprehension 방식이 훨씬 빠르다. 

%%timeit
temp=[]
for i in range(10):
    temp.append(i)
    
out: 
893 ns ± 48.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

1.28 µs ± 38.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
  • 반복식 for 앞에 여러가지가 올 수 있다.
  • if 붙이는 방식 2가지
[x+1 for x in range(10)]
[str(x) for x in range(10)] # str을 집어넣어서 어떤 값도 다 나타나게 할 수 있다. 

# if 붙이는 방식 2가지 
# 1) 뒤에 붙이는 것 
[x for x in range(10) if x%2==0] # % 나누는 것 

# python은 왼쪽에서 오른쪽으로 계산 
1+2+3+4

# 2) 앞에 붙이는 것
[x if x%2==0 else 3 for x in range(10)] # 조건식: else  (if 참 or else)

out:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
[0, 2, 4, 6, 8]
10
[0, 3, 2, 3, 4, 3, 6, 3, 8, 3]
# comprehension은 식이다.
[(x,y) for x in range(1,11)for y in range(10)] # list -> 중첩시킬 때 나타낼 수 있다.

out:
[(1, 0),
 (1, 1),
 (1, 2),
 (1, 3),
 (1, 4),
 (1, 5),
 (1, 6),
 (1, 7),
 (1, 8),
 (1, 9),
 (2, 0),
 (2, 1),
 (2, 2),
 (2, 3),
...


*comprehension 나타낼 수 있는 3가지

  • list, set, dictionary, but tuple (immutable) 은 안된다.
  • comprehension을 tuple로 만들면 generator

  • 함수형 패러다임은 mutable이 없다. immputable 형만 있다.
  • 형식적 증명 가능성을 두기 때문에 mutable처럼 바뀌면 안된다.
# comprehension은 값을 초기화 할 때 주로 쓴다.

((x,y) for x in range(1,11)for y in range(10)) # <generator object <genexpr> at 0x000001A6D4157390>
# generator 가 나온다. 
# tuple 은 안된다. 

{x for x in range (10)} # {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}: set 

# comprehension 나타낼 수 있는 3가지 
# list, set, dictionary, but tuple (immutable) 은 안된다. -> generator나옴. (next 쓸 수 있다.)

# generator 
a= (x for x in range(10))

next(a) # 0
# 최적화시킬 때 함수형 패러다임에서 가져오는 경우가 매우 많음. 

* for  쓰지 않은 기법
1) iterator & generator <- 하스켈 언어에서 가져왔다. 
    - 실제 for는 있지만 for문을 쓰지 않고 동시에 여러개를 처리하는 기법
 
* 참고: __next__ : 파이썬 객체  메서드 

5) 성능비교

%%timeit
temp=[]
for i in range(10):
    temp.append(i)

%%timeit # iter 쓰면 최적화되지만 파이썬은 코딩을 짧게 하는 것을 지향 
temp=[]
for i in iter(range(10)):
    temp.append(i)

out:
1.63 µs ± 246 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
1.54 µs ± 45.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
  • 컴프리핸선: 데이터 만들 때 쓴다.
from collections.abc import Iterable
from collections.abc import Iterator

set(dir(Iterator)) - set(dir(Iterable)) #  {'__next__'}: iterable 써야하는데 iterator 써도 된다. 

set(dir(Iterable)) - set(dir(Iterator)) # set(): 공집합 
sum # <function sum(iterable, start=0, /)>
%timeit sum((1,2,3,4,5,6,7,8,9,10))

%timeit sum([x for x in range(1,11)]) # 밑에는 만들어서 계산했기 때문에 좀 더 오래 걸린다.

%timeit sum(x for x in range(1,11)) # generator 할 때는 예외적으로 괄호를 하나 없애도 작동한다. 

%timeit sum((x for x in range(1,11))) 
# 제너레이터 표현식은 항상 괄호 안에 작성해야 하지만 함수 호출을 알리는 괄호도 포함된다. 

out:
312 ns ± 26.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
1.29 µs ± 25.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
1.91 µs ± 441 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
1.55 µs ± 17.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

  • 중첩
a=[1,2]
b=[3,4]
[(x, y) for x in a for y in b] 
# [(1, 3), (1, 4), (2, 3), (2, 4)] 
# 식은 식끼리 중첩시킬 수 있다. 
# 식은 함수 argument로도 들어갈 수 있다.

6) 재귀함수

  • 함수형 패러다임은 재귀를 좋아한다. (많이 쓴다)
    • 피보나치 구할 때 재귀 함수로 구한다.
    • for문 을 쓰지하고 쓰는 함수가 재귀 함수
  • 재귀함수란 어떤 함수에서 자신을 다시 호출하여 작업을 수행하는 방식의 함수를 의미
  • 다른 말로는 재귀호출, 되부름이라고 부르기도 함
  • 반복문을 사용하는 코드는 항상 재귀함수를 통해 구현하는 것이 가능하며 그 반대도 가능
def fibo(n):
    if n<3:
        return 1
    return fibo(n-1) + fibo(n-2)

fibo(3) # 2
  • 깊이가 길어지면 stack이 계속 쌓이면 overflow가 될 수 있다.
  • 꼬리 재귀: 계산을 여러번하고 계속 쌓이니까 꼬리 루트를 일렬로 만드는 일을 한다.
  • 재귀를 쓰면 꼬리 재귀 기법을 만들 수 있다.
  • 파이썬은 꼬리재귀를 지원하지 않기 때문에 잘 안쓴다.

7) map

  • iterator, generator - 사실상 많이 응용되진 않는다.
  • map 많이 응용된다.
map # map(func, *iterables -> 가변 포지셔널) 

def add_one(x):
    return x+1  
    
%timeit map(add_one, [1,2,3,4,5])
# 353 ns ± 10.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
list(map(add_one, [1,2,3,4,5])) # [2, 3, 4, 5, 6]

lambda :1 # 함수식이기 때문에 인자로 집어 넣을 수 있다. 
# <function __main__.<lambda>()>

%timeit list(map(lambda x : x+1, [1,2,3,4,5]))
# 1.33 µs ± 38.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
# for문
%timeit
temp = []
for i in [1,2,3,4,5]:
    temp.append(i)  # 값이 안나온다. (?)

# comprehension 
%timeit [x+1 for x in [1,2,3,4,5]]
# 529 ns ± 6.97 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

8) filter

  • 필터 안에 함수 넣어야한다.
  • if 보다 빠르다
filter # function, iterable
  • predicate : True, False 를 반환하는 함수
  • 필터는 true만 해준다.
tuple(filter(lambda x: x>3, [1,2,3,4,5,6])) # (4, 5, 6):  filter은 if 보다 속도도 빠르다. 
  • reduce
from functools import reduce # reduce도 function, sequence[,] -> 순서가 있어야한다. 

reduce(lambda x, y :x+y, [1,2,3,4,5])  # 15

# 활용: 벡터 내적에 쓸 수 있다. 

  • [복습] for문을 사용하지 않고도 짤 수 있는 것들 1) iterator, generator 2) comprehension 3) 재귀 4) map, filter, reduce

  • Enumerate

a=[1,2,3,4,5]
for i in a:
    print(i)
    
out:
1
2
3
4
5

----------------------------

a= '어쩌다 오늘'
for i in a:
    print(i)

out:



 



-----------------------------
a= '어쩌다 오늘'
for i in enumerate(a):
    print(i) 

out:
(0, '어')
(1, '쩌')
(2, '다')
(3, ' ')
(4, '오')
(5, '늘')

----------------------------------

a= '어쩌다 오늘'
for i,j in enumerate(a):
    print(i,j) # unpacking 할 수 있다. 

out:
0 
1 
2 
3  
4 
5 

--------------------------

enumerate # iterate 인자로 받는다. 
for i,j in enumerate(a,1):
    print(i,j) 

out:
1 
2 
3 
4  
5 
6 
  • 함수형 패러다임은 for, while을 바꿀 수 있다.

무단 배포 금지

Comments