본문 바로가기

Python/Python

Python - 수준 있는 디자인 패턴 (Advanced Design Patterns in Python)

원문 : http://pypix.com/python/advanced-data-structures/

 

 

List 에 대한 이해

 

다양한 숫자가 저장된 list가 있고 이 list 에서 0보다 큰 수의 제곱값을 가지는 새로운 list를 만들려고 할때 아래와 같이 코딩하곤 한다.

num = [1, 4, -5, 10, -7, 2, 3, -1]
filtered_and_squared = []
for number in num:
	if number > 0:
		filtered_and_squared.append(number ** 2)
		print filtered_and_squared
# [1, 16, 100, 4, 9]

 

4, 5 행을 보면 for, if 문이 중첩되어 사용된다. 이 부부을 filter, lamda, map을 사용하여 아래와 같이 확 줄일 수 있다.

num = [1, 4, -5, 10, -7, 2, 3, -1]
filtered_and_squared = map(lambda x: x ** 2, filter(lambda x: x > 0, num))
print filtered_and_squared
# [1, 16, 100, 4, 9]

 

이 코드를 조금 더 줄일 수 있다.

num = [1, 4, -5, 10, -7, 2, 3, -1]
filtered_and_squared = [ x**2 for x in num if x > 0]
print filtered_and_squared
# [1, 16, 100, 4, 9]

출처 : http://pypix.com/python/advanced-data-structures/

 

위 방법은 좋지만 전체 리스트를 메모리에 한번 올려야 한다는 단점이 있다.(라고 한다. 왜 그런지는 잘 모르겠음) 리스트가 작으면 크게 상관 없는데 리스트다 크다면 문제가 될 수 있다고 함. 그래서 나온게 Generator!!!

 

Generators 는 리스트 전체를 메모리에 올리지 않고 "generator 객체(object)"를 생성함. 그래서 필요할때 원본 리스트를 메모리에 올려서 원하는 결과를 얻을 수 있다고 함

예)

num = [1, 4, -5, 10, -7, 2, 3, -1]

def square_generator(optional_parameter):
	return (x ** 2 for x in num if x > optional_parameter)

print square_generator(0)

# Option I
for k in square_generator(0):
	print k
	# 1, 16, 100, 4, 9

# Option II
g = list(square_generator(0))
	print g
    # [1, 16, 100, 4, 9]

 

 

Decorators는 함수(function)나 클래스에 유용한 기능을 추가 할 수 있게 한다. 함수가 실행되기 전이나 실행된 이후 동작하는 기능을 정의 할 수 있다. (함수를 감싸고 있는 함수라고 이해하면 된다. 보안, 추적, locking에 유용하겠다.)

예) 함수 실행 시간을 측정하는 데코레이터 함수를 만들어 보자

import time from functools
import wraps

def timethis(func):
	
    @wraps(func)    
    def wrapper(*args, **kwargs):
    	print "wrapper start!!"
        start = time.time()
        #result = func(*args, **kwargs)
        func(*args, **kwargs)
        end = time.time()
        print (func.__name__, end - start)
        #return result
        return wrapper
	
    @timethis
    def countdown(n):
    print "function start!!"
    while n > 0:
        n -= 1
        print "function end!!"
        #wrapper start!!
        #function start!!
        #function end!! #('countdown', 0.0559999942779541)

위 예제에서 보는바와 같이

timethis 함수는 func 라는 이름으로 함수를 받아서 실행시키고 실행된 시간을 측정해서 출력하는 함수로 구성했고 

이를 decorator를 이용해 countdown이라는 함수를 감쌌다

 

이번엔 decorator 클래스를 만들어보자

class decorator(object): def __init__(self, f): print("inside decorator.__init__()") f() # Prove that function definition has completed def __call__(self): print("inside decorator.__call__()") print "start!!!" @decorator def function(): print("inside function()") print("Finished decorating function()") function() # start!!! # inside decorator.__init__() # inside function() # Finished decorating function() # inside decorator.__call__()

예제에서 알 수 있듯이

1. @decorator 를 만나면 decorator 클래스의 "__init__" 을 실행한다.

2. 함수를 실행시키면 정의된 decorator 클래스의 "__call__" 함수를 실행한다.

 

 

마지막 예제 하나를 더 보자

def decorator(func):
	
    def modify(*args, **kwargs):
    	variable = kwargs.pop('variable', None)
        print variable
        func(*args, **kwargs)
        return modify
	
    @decorator
    def func(a,b):
    	print a**2,b**2
        
        func(a=4, b=5, variable="hi") func(a=4, b=5)

# hi
# 16 25
# None
# 16 25

함수에는 정의되지 않은 변수라도 이런식으로 다룰 수 있다.

 

 

ContextLib는 context manager와 with문과 관련된 유용한 기능들을 제공한다. context manager를 작성하기 위해서는 __enter__()함수와 __exit__()함수가 포함된 클래스를 정의 해야 한다.

일단 예를 살펴 보자

import time   class demo:

def __init__(self, label):
	self.label = label
    
    def __enter__(self):
    	self.start = time.time()
        
        def __exit__(self, exc_ty, exc_val, exc_tb):
        	
            end = time.time()
            print('{}: {}'.format(self.label, end - self.start))
            
            with demo('counting'):
            	n = 10000000
                while n > 0:
                	n -= 1

# counting: 1.36000013351

context manager는 with 문으로 사용할 수 있다. __enter__() 함수는 with 문 안의 코드가 실행될 때 실행되는 함수이며, __exit__()함수는 with 문 안의 모든 코드가 실행되고 관련된 모든 자원과 메모리가 해제 되는 시점에 실행 된다.

 

위 예제를 contextlib 모듈에 정의된 "@contextmanager" decorator 를 이용해서 다시 작성해 보자.

from contextlib
import contextmanager
import time

@contextmanager
def demo(label):
	
    start = time.time()
    try:
        yield
	finally:
    	end = time.time()
        print('{}: {}'.format(label, end - start))
        
        with demo('counting'):
        	
            n = 10000000
            
            while n > 0:
            	n -= 1
                
# counting: 1.32399988174

이전 코드와 비교해보면 쉽게 알 수 있겠지만

"yield" 이전에는 __enter__() 함수에 들어갈 내용

"yield" 이후에는 __exit__() 함수에 들어갈 내용 을 작성하면 된다. 

 

 

Descriptors는 어떤 변수나 함수에 접근할때 동작한다. __get__(), __set__(), __delete__() 함수로 각 이벤트별 동작을 정의 할 수 있다.

__get__(self, instance, owner) - 변수나 함수에 접근할때

__set__(self, instance, value) - 변수나 함수를 변경 할 때

__delete__(self, instance) - 변수나 함수를 삭제 할때

instance 는 descriptor 가 걸려있는 변수나 함수가 포함된 인스턴스를 말한다.

owner는 변수나 함수가 포함된 클래스를 말한다.

value는 변경되는 값을 말한다.

 

예)

import types

class descriptor(object):
	def __init__(self, attr):
    	self.attr = attr
        
        def __get__(self, obj, cls):
        	print "==== __get__() ===="
            
            if isinstance(self.attr, types.FunctionType):
            	return self.attr(obj)
			else:
            	return self.attr
		
        def __set__(self, obj, value):
        	print "==== __set__() ===="
            
            if not isinstance(self.attr, types.FunctionType):
            	print "new Value is %s but not changed!!" % value

class Foo(object):
	
    bar = descriptor(100)
    
    @descriptor
    def bar2(self):
    	return 42
	
    f = Foo()
    print f.bar
    
    #==== __get__() ==== 
    #100  f.bar = 300 
    #==== __set__() ==== 
    #new Value is 300 but not changed!!  
	
    print f.bar
    #==== __get__() ==== 
    #100
    
    print f.bar2
    #==== __get__() ==== 
    #42

어떤 변수나 함수에 접근하고, 변경하는것을 제어하거나 이벤트를 걸고 싶을때 사용하면 좋을것 같다.

 

 

 

 

 

 

'Python > Python' 카테고리의 다른 글

Python - lambda()  (0) 2013.12.20
Python - zip()  (0) 2013.12.20
Python 어렵게 배우기  (0) 2013.08.23
Python - @staticmethod, @classmethod의 사용  (0) 2013.08.12
Python - UTF-8 인코딩  (0) 2013.05.02