Week01 Day03

 

stack

나중에 들어온 데이터가 먼저 나간다.
Last in First Out

queue

먼저 들어온 데이터가 먼저 나간다.
First in First Out

tuple

변경할 수 없는 list이며 tuple 내 요소를 제거하거나 추가할 수 없다.
바뀌면 안되는 데이터를 저장하여 개발자가 변경하는 실수를 피할 수 있다.

set

입력하는 순서를 고려하지 않고 중복을 허용하지 않는다.
수학에서의 set과 매우 유사하다.

dict

데이터를 저장할 때 구분할 수 있는 값을 함께 저장하며 이 값을 key라 하고 이때 데이터를 key라고 한다.
즉, 구분하기 위해 같은 key값의 중복을 허용하지 않는다.

collections module

list, tuple 등의 자료구조를 확장했다.
dequelist처럼 element를 추가 및 삭제할 수 있다.
추가된 점은 appendleft인데 element를 맨 앞에 추가할 수 있다.
OrderedDictdict에서 입력하는 순서를 기억한다.
defaultdictdict에서 만약 없는 key값을 요청할 때 error가 발생하는데 이를 방지해준다.
없는 key값을 요청하면 기본값으로 설정한 값을 반환해준다.
Counterlist내 element들의 갯수를 세어준다.

from collections import Counter
l = ["a", "b", "b", "c", "c", "c", "d", "d", "d", "d"]
counter = Counter(l)
print(counter) # {"a": 1, "b": 2, "c": 3, "d": 4}

pythonic code

python에서만 사용하는 code style이 아니지만 주로 python에서 쓰이는 style을 말한다.
코드의 효율성과 가독성이 증가한다.

list comprehension

한줄로 list를 만드는 방법이다.
보통 list를 만든다면 이렇게 한다.

# 0 ~ 9까지 element가 담긴 list를 생성해보자.
l = []
for i in range(10):
  l.append(i)
print(l) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

list를 생성하고 안에 element를 넣기 위해 append를 사용했다.
하지만 list comprehensionappend를 사용하지 않는다.

l = [i for i in range(10)] # list comprehension
print(l) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

이와 유사하게 set comprehension, dict comprehension이 있다.

s = {i for i in range(10)} # set comprehension
d = {i: i*3 for i in range(10)} dict comprehension

list comprehensionfor + loop보다 빠르다.

익명함수 lambda

lambdadef처럼 함수를 만들때 사용된다.

f = lambda x: x*x
print(f(10)) # 100

lambda의 목적은 한번 사용하고 다시는 안쓰는, 일회용 함수, 쓰고나서 버리는 함수이다.
lambda의 사용을 줄이자는 얘기가 나오고 있다.
디버깅이 힘들고 일회용으로 쓰고 버리다 보니 코드 해석의 어려움이 있다.
사실 lambda를 안쓰고 def로 정의된 함수를 사용해도 전혀 문제없다.

iterable

An object capable of returning its members one at a time.
Examples of iterables include all sequence types (such as list, str, and tuple) and some non-sequence types like dict, file objects, and objects of any classes you define with an __iter__() method or with a __getitem__() method that implements Sequence semantics.
Iterables can be used in a for loop and in many other places where a sequence is needed (zip(), map(), …).
When an iterable object is passed as an argument to the built-in function iter(), it returns an iterator for the object.
This iterator is good for one pass over the set of values.
When using iterables, it is usually not necessary to call iter() or deal with iterator objects yourself.
The for statement does that automatically for you, creating a temporary unnamed variable to hold the iterator for the duration of the loop.1

정의를 살펴보면 iterable은 이 object에 속하는 요소를 하나씩 반환할 수 있다.
iterable의 예시로는 모든 sequence 타입과 non-sequencedict과 임의로 정의한 class 중 __iter__혹은 __getitem__을 정의한 class라고 되어 있다.
iter함수를 실행하면 class 내 __iter__가 실행되어 iterator를 반환한다.
혹은 __getitem__를 0부터 시작하는 인덱스를 받도록 구현해야 된다.
iterator를 반환하는 이유는 iterable인 동일한 object에서 여러 독립적인 iterator를 가질 수 있도록 하기 위함이다.
iterator는 재사용이 불가능하다.
즉, 이전에 생산한 값을 다시 생산하기 불가능하며 다시 반복하고 싶으면 iterableiter함수를 사용하여 새로운 iterator를 만들어야 한다.

ex_iterable = range(5)
for i in ex_iterable:
  print(i) # 0, 1, 2, 3, 4
# ex_iterable을 여러번 for loop에 사용할 수 있다
for i in ex_iterable:
  print(i) # 0, 1, 2, 3, 4

# ex_iterator는 0, 1, 2, 3, 4를 순차대로 생산한다.
# 즉, 2를 생산하려면 0, 1이 먼저 생산되어야 한다.
# 또한 2를 2번 생산할 수 없다. iterator는 오직 모든 값들을 한번만 생산할 수 있다.
ex_iterator = iter(ex_iterable)
for i in ex_iterator:
  print(i) # 0, 1, 2, 3, 4
for i in ex_iterator:
  print(i) # nothing...

iterablefor loop에서 사용하면 자동으로 iter함수를 사용하여 iterator로 만들어 준다.

iterator

An object representing a stream of data.
Repeated calls to the iterator’s __next__() method (or passing it to the built-in function next()) return successive items in the stream.
When no more data are available a StopIteration exception is raised instead.
At this point, the iterator object is exhausted and any further calls to its __next__() method just raise StopIteration again.
Iterators are required to have an __iter__() method that returns the iterator object itself so every iterator is also iterable and may be used in most places where other iterables are accepted.
One notable exception is code which attempts multiple iteration passes.
A container object (such as a list) produces a fresh new iterator each time you pass it to the iter() function or use it in a for loop.
Attempting this with an iterator will just return the same exhausted iterator object used in the previous iteration pass, making it appear like an empty container.2

iterator__next__를 구현한 class로써 next함수를 호출하여 값을 순차적으로 반환하며 상태를 가지고 있다.
또한 __iter__를 구현하며 self를 반환한다.
여기서 상태는 지금 생산할 값이 무엇인지이다.
즉, 다음 next함수 호출시 어떤 값을 생산할지에 대한 상태를 저장하고 있다.
만약 더이상 생산할 값이 없다면 StopIteration 에러를 발생시킨다.
iterable object에서 여러 개의 iterator를 만들 수 있다.
iterator는 독립적이다.

ex_iterable = range(5)
ex_iterator1 = iter(ex_iterable)
ex_iterator2 = iter(ex_iterable)
print(next(ex_iterator1)) # 0
print(next(ex_iterator1)) # 1
print(next(ex_iterator1)) # 2

# ex_iterator1과 2는 같은 iterable object에 의해 만들어졌다.
# ex_iterator1이 0, 1, 2를 출력했을때 ex_iterator2는 1과 상관없이 0, 1, 2, 3, 4를 출력한다.
# 이는 iterator간 독립적이기 때문이다.
for i in ex_iterator2:
  print(i) # 0, 1, 2, 3, 4

generator

A function which returns a generator iterator.
It looks like a normal function except that it contains yield expressions for producing a series of values usable in a for-loop or that can be retrieved one at a time with the next() function.
Usually refers to a generator function, but may refer to a generator iterator in some contexts.
In cases where the intended meaning isn’t clear, using the full terms avoids ambiguity.3

generator는 iterator를 반환하는 함수이다.
yield가 있는 것 말고는 평범한 함수처럼 보인다.

# 함수에 yield가 있으면 generator라 부른다.
# iterator처럼 내부에 상태를 저장하고 있으며 메모리상에 계속 유지하고 있다.
# gen가 생성될떄 generator 함수는 실행되지 않다가 next(gen)에 의해 비로소 값을 생성한다.
# 즉, 요청이 들어왔을 때 실행하며 lazy evaluation이라 한다.
# 이러한 특징은 메모리를 효율적으로 사용할 수 있다.
def generator(n):
  val = 0
  while val < n:
    yield val
    val += 1
gen = generator(5)
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 2
for i in gen:
  print(i) # 3, 4

# generator를 pythonic code로 바꿀 수 있다.
# list1과 list2의 차이는 list1이 생성될때 gen_func이 실행되었고 list2는 for loop에서 gen_func이 실행되었다.
# list2와 같이 괄호()를 사용하는 것을 generator expression이라 한다.
# list1과 같은 실행을 eager evaluation, list2를 lazy evaluation이라 한다.
def gen_func(n):
  val = 0
  while val < n:
    print("hi", end=" ")
    yield val
    val += 1
gen1 = gen_func(5)
list1 = [x*x for x in gen1] # hi hi hi hi hi
for i in list1:
  print(i) # 0, 1, 4, 9, 16
gen2 = gen_func(5)
list2 = (x*x for x in gen2)
for i in list2:
  print(i)
  # hi 0
  # hi 1
  # hi 4
  # hi 9
  # hi 16