lang/py

Book 파이썬답게 코딩하기 - 기본 문법

C/H 2020. 1. 9. 08:30

파이썬답게 코딩하기
파이썬 입문 강좌 | TEAMLAB X Inflearn | 4-1 Condition

흐름제어

flow_control

Exception

try: 
  ...
except: # except 코드
  ...
else: # except 가 되지 않는 코드
  ...
finally: # 반드시 처리해야 할 코드
  ...

반복문

import sys
# range: 지정한 크리의 리스트타입 반환
type(range(10))
# python2: <type 'list'>
# python3: <class 'range'>
sys.getsizeof(range(10))
# python2: 152
# python3: 48
sys.getsizeof(range(100))
# python2: 872
# python3: 48

type(xrange(10)) # Generator를 사용한다.
# python2: <type 'xrange'>
sys.getsize(xrange(10))
# python2: 40
sys.getsize(xrange(100))
# python2: 40

xrange() 는 python3에서 range()로 Generator를 이용하도록 개선 되었다.

list(enumerate(["a", "b", "c"]))
# [(0, 'a'), (1, 'b'), (2, 'c')]
for key, val in enumerate(["a", "b", "c"]):
  print("%s : %s " % (key, val))
"""
0 : a 
1 : b 
2 : c 
"""

Decorator

데코레이터 패턴은 로직을 설계할 때 객체가 특정 작업을 추가할 수 있도록 코드를 작성하는 방법을 패턴화 시킨 것이다.
Python2.6 부터 데코레이터 함수만 사용할 수 있던 것을 함수, 클래스 모두 사용할 수 있다.
decorator

Decorator

def deco(func):
  def wrapper():
    print("before")
    rs = func()
    print("after")
    return rs

  return wrapper

@deco
def main():
  print("main function")

if __name__ == "__main__":
  main()

"""
before
main function
after
"""

다중 Decorator

@lastRun
@firstRun
def main():
  print("main function")

클래스 Decorator

functools update_wrapper

import time
from functools import update_wrapper

class someClass:
  def __init__(self, func):
    self.func = func
    update_wrapper(self, self.func)

  def __call__(self, *args, **kwargs): # 클래스를 함수처럼 사용할 때 호출된다.
    st = time.time()
    rs = self.func(*args, **kwargs)
    print("%s run time: %s" % (self.func.__name__, time.time()-st))
    return rs

@someClass
def main(delay_time):
  time.sleep(delay_time)

if __name__ == "__main__":
  main(2)

# main run time: 2.0047287940979004

functools @wraps

import time
from functools import wraps

class someClass:
  def __init__(self, bool):
    self.bool = bool

  def __call__(self, func): # 클래스를 함수처럼 사용할 때 호출된다.

    @wraps(func)
    def wrapper(*args, **kwargs):
      if  self.bool is False:
        return func(*args, **kwargs)

      st = time.time()
      rs = func(*args, **kwargs)
      print("%s run time: %s" % (func.__name__, time.time()-st))
      return rs

    return wrapper

@someClass(True)
def active(delay_time):
  time.sleep(delay_time)

@someClass(False)
def nonActive(delay_time):
  time.sleep(delay_time)

if __name__ == "__main__":
  active(2)
  nonActive(2)

# active run time: 2.0047287940979004
# nonActive 는 return func(*args, **kwargs) 실행으로 딜레이, 출력없이 끝낸다.

Iterator

이터레이터는 가지고 있는 값을 순차적으로 반복해서 하나씩 반환할 수 있는 개체이다.
비슷한게 iterable이 있는데 보통 이 둘간의 관계를 많이 혼동한다.
이 둘은 같은 것으로 보이기도 하지만 다른 개념이다.

iterable

iterable 정의는 가지고 있는 값을 한번에 하나씩 반환할 수 있는 개체를 말한다.
주의할 점은 한번에 하나씩 반환할 수 있다는 것이지, 한번에 하나씩 반환해야 한다는 것은 아니다.
한번에 모든 값을, 혹은 하나씩만 반환할 수 있다.
iterable 예는 container(리스트, 스트링, 튜플 같은 sequence타입 자료형, 사전같은 non-sequence타입 자료형)이나 open files, open sockes 같은 것이 있다.
그리고, 클래스에서 iter, getitem 메서드를 구현한 경우 iterable이라고 말할 수 있다.

iterator는 한번에 하나씩 값은 반환하는 것만 iterator라고 부를 수 있다.
리스트, 사전형 자료는 iterable 이다.

파이썬은 구조적으로 거의 모든 개체를 이터레이터로 사용할 수 있도록 지원한다.
iterable, iterator

x = [1, 2, 3]
y = {"red":1, "blue":2, "green":3}

x_iterator = iter(x)
y_iterator = iter(y)

print(next(x_iterator))
print(next(y_iterator))
"""
list iterator next : 1
dictionary iterator next : red
"""

for문에서 iter()를 사용하지 않는다.
for문에서는 자동으로 iterable 개체를 임시로 이ㅓㅌ레이터로 변환하기 때문이다.

iterator

iterator도 한번에 하나씩 값을 반환하려면 연재 어디까지 반환했는지 상태를 알고 있어야 한다.
iterator는 가지고 있는 값 중에서 이미 반환한 상태를 갖고 있다.
파이썬 next()를 통해서 값을 순차적으로 반환한다.
파이썬은 더 이상 값을 반환할 수 없는 경우 StopIteration 예외를 발생시킨다.
for문에서는 내부적으로 StopIteration 예외 처리가 되어 있다.

Generator

사전적 정의는 루프의 반복 동작을 제어하는 특수한 함수이다.
Python2 제너레이터는 이터레이터를 반환하고 yield구문을 포함하고 있는 함수다.
Python3 제너레이터는 제터레이터 이터레이터를 반환하는 함수이다.
제너레이터 이터레이터를 반환하는 제너레이트 함수가 있고, 일반적으로 제터레이터는 제너레이트 함수를 표현한다.

Generator iterator

이터레이터 !== 제너레이터 이터레이터

이터레이터 next: Container에 있는 다음 항목을 반환한다.

  • 이터레이터는 값을 모두 연산한 뒤 메모리에 있는 결과를 하나씩 반환한다.

제너레이터 next: 제너레이터 함수를 실행하거나 마지막으로 실행된 yield 구문에서 다시 시작한다.

  • 제너레이너터는 연산을 수행하기 직전 상태로 멈춰있고 값을 반환할 때 연산을 수행한다.
  • 영문표현 '얼어 있다'라고 표현된다.
  • 제너레이터가 yield르 ㄹ만나면 그 상태를 보존하고 있다가 제너레이터가 호출되면 상태를 이어서 로직을 수행한다.
  • 제너레이트를 호출하는 주체는 next함수다

기술적으로 yield는 값을 반환하고 값을 입력 받는 2가지 기능을 가진다.
yield로 제너레이터 이터레이터를 반환하고, send 함수를 통해 yield 에 값을 할당한다.
Generator

Stateful Generator

제너레이터 이터레이터를 실행중 return을 만나면 StopIteration 예외가 발생하고 멈추게 된다.

Generator 성능

제너레이터는 이터레이터를 사용하는 모든 환경을 대체할 수 있다.
그리고 효율성 때문에 대체하는 것이 좋다.
rang, xrange에서 메모리 사용률 차이를 본다면 제너레이터를 사용하는 xrange가 효율성이 좋다.

Lazy Evaluation

제너레이터 lazy evaluation 실행지연 프로그래밍 기법.
필요할 때까지 실행하지 않고 지연시켰다가 필요할 대 실행하는 기법.
장점으로 잠재적 무한 데이터 구조를 정의할 수 있다.
지금 당장 실행하지 않으니 필요할 때마다 이어서 실행하도록 하면 데이터 길이 제약을 받지 않는다.
그래서 스트림 처리에 유용하다.
불 필요한 계산을 회피함으로써 성능 향상을 꾀할 수 있다.

Comprehension & Expression

comprehension, expression은 코드를 더 간결하게 작성하기 위한 문법이다.(~== lambda)

Comprehension

Comprehension은 iterable한 개체를 대상으로 동작한다.
[], {}를 이용한다.

arrs = [1, 2, 3]
keys = ["korea", "japen", "china"]
values = [82, 81, 86]

print(arrs)
# [1, 2, 3]
print([x*x for x in arrs])
# [1, 4, 9]
print({k:v for k, v in zip(keys, values)})
# {'korea': 82, 'japen': 81, 'china': 86}

Generator Expression

Generator Expressin은 Comprehension과 동일한 역할을 한다.
사용방법은 동일하지만 문법이 약간 다르다.
()를 사용한다.

arrs = [1, 2, 3]
print(x*x for x in arrs)
print(list(x*x for x in arrs))
# <generator object <genexpr> at 0x1063ea570>
# [1, 4, 9]
print(i for i in range(10))
print(list(i for i in range(10)))
# <generator object <genexpr> at 0x1063ea570>
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Lazy Evaluation

import time

def wait_return(num):
    print ("sleep")
    time.sleep(0.5)
    return num

def print_items(items):
    for i in items:
        print (i)

# Comprehension
for i in [wait_return(x) for x in range(10)]:
  print(i)
""" 
sleep
sleep
sleep
sleep
sleep
sleep
sleep
sleep
sleep
sleep
0
1
2
3
4
5
6
7
8
9
"""

 # Generator Expression
for i in (wait_return(x) for x in range(10)):
  print(i)
"""
sleep
0
sleep
1
sleep
2
sleep
3
sleep
4
sleep
5
sleep
6
sleep
7
sleep
8
sleep
9
"""

print(i)로 실제로 사용 될 때 실행된다.
이렇게 사용함으로써 꼭 무한대의 값이 아니더라도 많은 데이터를 메모리의 영향 없이 처리 할 수 있다.

Equality vs Identity

파이썬에서 값을 비교할 때 어떤 형식으로 사용하라고 권고문에 있다. PEP8 프로그래밍 권고

내용은 Singleton을 비교할 때 is, is not을 사용하고 ==, !== 연산자를 사용하지 말라고 적혀 있다.
if x is not None if x is True 형식으로 사용한다.
이유는 동등성(equality)과 동일성(identity) 때문으로 동등성 검증은 ==이나 != 연산자로, 동일성 검증은 isis not같은 연산자를 사용한다.
Boolean 권고문은 동등성, 동일성을 떠나서 이 값이 True, False 값을 명확하게 알려주기 때문에 아무런 연산자 없이 그냥 사용하라.

  1. True/False를 비교는 if문에 변수만 사용한다.
  2. 동등성 검증은 =!=같은 연산자를 사용한다.
  3. 동이성 검증은 isis not을 사용한다.

Identity 검증 허점

equality_vs_identit

# compare_identity.py
print (999 is 999)
# True
x = 999; y = 999
print (x is y)
# True
z = 999
print (x is z)
# True

위 내용은 파일에서 x,y,z 변수는 한번에 처리되어서 동일한 메모리 공간을 사용한다.

$ python2
>>> print (999 is 999)
True
>>> x = 999; y = 999
>>> print (x is y)
True
>>> z = 999
>>> print (x is z)
False

$ python3
>>> print (999 is 999)
True
>>> x = 999; y = 999
>>> print (x is y)
True
>>> z = 999
>>> print (x is z)
False

위 내용은 x,y,z 변수는 인터프린터로 실행하는 시점에 최적화 되어 다른 메모리 공간을 사용한다.

# compare_identity_analysis.py
print (999 is 999)
# True
x = 999; y = 999;
print (x is y)
# True
z = 999;
print (x is z)
# True

print (id(x))
# 4308976528
print (id(y))
# 4308976528
print (id(z))
# 4308976528
$ python3
>>> print(999 is 999)
True
>>> x=999; y=999
>>> print(x is y)
True
>>> z=999
>>> print(x is z)
False
>>> print(id(x))
4400043184
>>> print(id(y))
4400043184
>>> print(id(z))
4400042704

성능(Performance)

None는 파이썬에서 사용하기 위해 만든 NoneType 클래스다.
그래서 다른변수에 값을 할당하거나 None값을 확인하더라도 같은 identity 값을 가진다.
그러므로 is==로 비교하나 결과 차이는 없다.
None은 동등성, 동일성을 모두 보장하지만 실제로는 동일성을 보장한다고 하는 것이 맞는 표현이다.
그래서 동일성 비교 is가 성능이 더 좋다.

import timeit

def average(items):
    sum = 0
    for item in items:
        sum += float(item)

    return sum / len(items)

def check_performance(compare_expression, condition):
    results = timeit.Timer(compare_expression, setup=condition).repeat(100, 10000)
    return average(results)

def main():
    print ("=== compare x is not None ===")
    print ("identity : %s" % check_performance("x is None", "x = 1")) # is 동일성
    # identity : 0.00021696792999999985
    print ("equality : %s" % check_performance("x == None", "x = 1")) # = 동등성
    # equality : 0.0003247947200000002

    print ("=== compare x is None ===")
    print ("identity : %s" % check_performance("x is None", "x = None"))
    # identity : 0.0001889131700000006
    print ("equality : %s" % check_performance("x == None", "x = None"))
    # equality : 0.00023917626999999997


if __name__ == "__main__":
    main()
반응형