행복한 아빠

Python의 숨은? 특징 (어머 이건 사야 돼) 본문

Python

Python의 숨은? 특징 (어머 이건 사야 돼)

행복한아빠 2016. 8. 25. 11:50

어머 이건 사야 돼


스택오버플로우에 덜 알려진 python의 유용한 특징을 여러 사람이 소개한 글이 있어 기록합니다.

Python 이라는 백화점에서 쇼핑하다가 특이하고 멋진 물건들만 모아 놓은 느낌입니다.

몇가지는 별 필요 없지만 대부분 상당히 유용하다는 생각이 들어 맘에 드는 것만 정리해 놓습니다.

(http://stackoverflow.com/questions/101268/hidden-features-of-python)


Argument Unpacking

리스트나 딕션너리 값을 * 또는 **을 이용하여 함수 아규먼트에 자동으로 풀어주는 기능입니다.


1
2
3
4
5
6
7
8
def draw_point(x, y):
    # do some magic
 
point_foo = (34)
point_bar = {'y'3'x'2}
 
draw_point(*point_foo)
draw_point(**point_bar)
cs

매우 유용한 shortcut 이라 자주 사용할 거라는 예감



Chaining Comparison Operators

많이 아는 기능일 겁니다. if x > 0 and x < 10:  이런 식의 장황한 식을 if 0 < x < 10: 이런 식으로 간결하게 해 줍니다.

1
2
3
4
5
6
7
8
9
10
11
>>> x = 5
>>> 1 < x < 10
True
>>> 10 < x < 20 
False
>>> x < 10 < x*10 < 100
True
>>> 10 > x <= 9
True
>>> 5 == x > 4
True
cs


Decorators

함수나 메소드를 감싸 원본 함수나 메소드에 추가적인 기능을 제공하는 기능입니다. Django 같은 프레임워크를 사용하다보면 자주 사용합니다. 데코레이터가 작동하는 방식은 단순하며 자신의 데코레이터도 쉽게 만들 수 있습니다.
AOP(Aspect Oriented Programming)에 많이 적용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> def print_args(function):
>>>     def wrapper(*args, **kwargs):
>>>         print 'Arguments:', args, kwargs
>>>         return function(*args, **kwargs)
>>>     return wrapper
 
>>> @print_args
>>> def write(text):
>>>     print text
 
>>> write('foo')
Arguments: ('foo',) {}
foo
cs

@print_agrs로 시작하는 7,8 라인은 사실 다음과 같습니다.


1
2
3
def write(text):
    print text
write = print_args(write)
cs

보통 데코레이터는 함수보다 클래스로 많이 구현합니다. 데코레이터에 대해 간략히 잘 소개된 페이지는 다음을 참조하세요. (http://agiliq.com/blog/2009/06/understanding-decorators/)



Be careful with mutable default arguments

아주 중요한 부분인데 아래 예제와 같이 기본 아규먼트 값 사용에 주의하라는 내용입니다.


1
2
3
4
5
6
7
8
9
10
>>> def foo(x=[]):
...     x.append(1)
...     print x
... 
>>> foo()
[1]
>>> foo()
[11]
>>> foo()
[111]
cs


의도치 않게 함수 호출할 때마다 기본값이 변경됩니다. 기본적으로 아규먼트로 전달되는 값은 변경하지 않는 것이 좋은 API 작성방법입니다.



Descriptors

점(.)으로 객체의 멤버를 x.y와 같이 접근할 때, 파이썬은 먼저 인스턴스 딕션너리(__dict__)에서 멤버를 찾습니다. 없을 경우 클래스 딕션너리에서 찾습니다. 클래스에서 멤버를 찾고 객체가 descriptor 프로토콜을 구현했다면 이런 경우 멤버를 바로 리턴하지 않고 descriptor 메소드(__get__, __set__, __delete__)를 호출합니다.

아래는 descriptor를 이용하여 read-only 프로퍼티를 구현한 예입니다.


1
2
3
4
5
6
7
8
class Property(object):
    def __init__(self, fget):
        self.fget = fget
 
    def __get__(self, obj, type):
        if obj is None:
            return self
        return self.fget(obj)
cs


Descriptor에 대한 상세한 내용은 http://users.rcn.com/python/download/Descriptor.htm 에서 찾아보면 됩니다.

파이썬 언어에서 Java의 private 같은 접근제어가 없다는 점에서 이 기능은 유용합니다. 또한 점(.) 접근을 제어함으로써 고급기능들을 간단하게 사용할 수 있는 유용한 방법으로 다소 이해가 어렵더라도 알아두면 유용합니다. python에서 기본으로 제공하는 property descriptor를 알아보세요.



Dictionary default .get value

딕션너리는 보통 d['k']와 같은 형식으로 다루는데 이 경우 딕션너리에 'k' 키가 없을 경우 에러가 발생합니다. 'k'키가 없을 경우 None을 받고 싶을 경우 예외처리하거나 다음과 같이 접근합니다.

1
= d['k'if 'k' in d.keys() else None
cs

딕션너리에 get() 메소드를 사용하면 일이 쉬워집니다.

1
= d.get('k', None)
cs



Docstring Tests

Python의 documentation을 읽어 테스트로 활용하는 방법입니다. 예제 보는게 이해가 빠를 겁니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def factorial(n):
    """Return the factorial of n, an exact integer >= 0.
    If the result is small enough to fit in an int, return an int.
    Else return a long.
    >>> [factorial(n) for n in range(6)]
    [1, 1, 2, 6, 24, 120]
    >>> factorial(-1)
    Traceback (most recent call last):
        ...
    ValueError: n must be >= 0
    Factorials of floats are OK, but the float must be an exact integer:
    """
 
    import math
    if not n >= 0:
        raise ValueError("n must be >= 0")
    if math.floor(n) != n:
        raise ValueError("n must be exact integer")
    if n+1 == n:  # catch a value like 1e300
        raise OverflowError("n too large")
    result = 1
    factor = 2
    while factor <= n:
        result *= factor
        factor += 1
    return result
 
def _test():
    import doctest
    doctest.testmod()    
 
if __name__ == "__main__":
    _test()
cs

문서 주석에서 >>> 부분(7행)을 읽고 실행하여 그 아래내용과 실행한 출력과 비교하는 테스트를 수행합니다. 출력과 문자열 비교를 함으로 띄어쓰기가 하나라도 다르면 테스트 실패 합니다.


재미있는 기능이긴 한데 주로 단위테스트 프레임워크를 사용하므로 패쓰...



Ellipsis Slicing Syntax

파이썬은 잘 알려져 있지 않는 ellipsis 라는 slicing 연산자를 제공합니다.

1
2
3
4
5
6
>>> class C(object):
...  def __getitem__(self, item):
...   return item
... 
>>> C()[1:2, ..., 3]
(slice(12, None), Ellipsis, 3)
cs

위와 같이 slice할 때 쩜쩜쩜(...) 연산자를 제공하며 Ellipsis라는 타입으로 나타납니다. 대괄호 [ ] 연산을 통해 직접 slice를 하는 클래스 만들때 쩜쩜쩜 문법도 쓸 수 있다는... 얼마나 쓸 지는 모르겠네요.

더 자세히 알고 싶을 경우 -> http://stackoverflow.com/questions/118370/how-do-you-use-the-ellipsis-slicing-syntax-in-python



Enumeration

for 문에 index가 필요할 때 iterable 객체를 enumerate로 감싼(wrap)다. 그러면 루프 돌면서 index를 돌려줍니다.

1
2
3
4
5
6
7
8
>>> a = ['a''b''c''d''e']
>>> for index, item in enumerate(a): print index, item
...
0 a
1 b
2 c
3 d
4 e
cs


많이 쓰는 것이라 숨은(?) feature라 하긴 뭐합니다.



For/else

1
2
3
4
5
for i in foo:
    if i == 0:
        break
else:
    print("i was never 0")
cs

else 블럭은 break가 호출되지 않을 경우 루프의 맨 나중에 실행됩니다.

위 코드는 아래와 같이 작동합니다.


1
2
3
4
5
6
7
found = False
for i in foo:
    if i == 0:
        found = True
        break
if not found: 
    print("i was never 0")
cs


for else 구문은 마치 for가 돌지 않았을 때 else 구문을 실행하라는 뜻 같아서 (오히려 반대인데) 쓰지 말아야 할 구문으로 이야기하기도 합니다. 되도록 사용하지 마세요.


Function as iter() argument

iter()에 함수나 lambda같은 callable 아규먼트를 넘길 수 있습니다.

1
2
3
def seek_next_line(f):
    for c in iter(lambda: f.read(1),'\n'):
        pass
cs

iter(callable, until_value) 함수는 until_value를 만날때 까지 반복적으로 callable을 호출하여 루프를 돌립니다.

즉 위 코드는 '\n'를 만날 때까지 file 포인터를 옮깁니다. 즉 다음 라인으로 이동



Generator expressions

다음과 같은 코드를 작성했다면


1
x=(n for n in foo if bar(n))

cs


x에 generator를 할당한 것을 얻을 수 있고, 다음과 같이 루프를 돌릴 수 있습니다.


1
for n in x:
cs


이것의 장점은 다음과 같이 했을 경우 발생되는 중간 저장소가 필요 없다는 것입니다. 다음과 같이 하면 x에 list 값이 메모리에 할당 되겠지요.


1
= [n for n in foo if bar(n)]
cs


경우에 따라 generator를 사용하면 눈에 띄게 성능이 올라갑니다.

보통 중첩되는 for 루프가 들어가는 generator 끝에 if 문을 여러 개 넣을 수 있습니다. 


1
2
3
4
5
6
7
8
 >>> n = ((a,b) for a in range(0,2for b in range(4,6))
>>> for i in n:
...   print i 
 
(04)
(05)
(14)
(15)
cs



import this

그냥 import this 해 보세요. 뭔가 하고 싶은 말이 있나봅니다.



In Place Value Swapping

튜플을 이용하여 다음과 같이 값을 swap하는 코드를 작성할 수 있습니다.

1
2
3
4
5
6
7
8
>>> a = 10
>>> b = 5
>>> a, b
(105)
 
>>> a, b = b, a
>>> a, b
(510)

cs


할당문 오른쪽에서 새로운 튜플(b, a)를 생성하여 왼쪽 튜플을 unpack해서 각 변수에 할당합니다. 직관적이네요.



List stepping

slice 연산자에서 step 아규먼트를 넣을 수 있습니다.

1
2
3
= [1,2,3,4,5]
>>> a[::2]  # iterate over the whole list in 2-increments
[1,3,5]
cs

거꾸로 돌리기 위해 다음과 같이 특별한 표현도 사용합니다.


1
2
 >>> a[::-1]
[5,4,3,2,1]

cs



__missing__ items

파이썬 2.5 부터 디션너리에 __missing__ 메소드를 제공하는데 이것은 아이템이 없을 경우 호출됩니다. 아래 경우 키에 대한 아이템이 없을 경우 빈 list를 주네요.

1
2
3
4
5
6
7
8
9
10
>>> class MyDict(dict):
...  def __missing__(self, key):
...   self[key] = rv = []
...   return rv
... 
>>> m = MyDict()
>>> m["foo"].append(1)
>>> m["foo"].append(2)
>>> dict(m)
{'foo': [12]}
cs

collections 패키지에 dict을 상속하는 defaultdict 클래스가 있는데 위와 동일한 일을 할 수 있습니다.

1
2
3
4
5
6
>>> from collections import defaultdict
>>> m = defaultdict(list)
>>> m["foo"].append(1)
>>> m["foo"].append(2)
>>> dict(m)
{'foo': [12]}
cs


특별한 경우가 아니면 dict을 쓰시기 바랍니다. 대부분 함수들은 dict에 키가 없을 경우 KeyErrors를 잡고 key가 없을 경우에 대한 특별한 처리를 하는 것이 보통입니다.



Multi-line Regex

파이썬에서 정규식을 여러 줄로 나눌 수 있으며 주석도 넣을 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
>>> pattern = """
... ^                   # beginning of string
... M{0,4}              # thousands - 0 to 4 M's
... (CM|CD|D?C{0,3})    # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's),
...                     #            or 500-800 (D, followed by 0 to 3 C's)
... (XC|XL|L?X{0,3})    # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's),
...                     #        or 50-80 (L, followed by 0 to 3 X's)
... (IX|IV|V?I{0,3})    # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's),
...                     #        or 5-8 (V, followed by 0 to 3 I's)
... $                   # end of string
... """
>>> re.search(pattern, 'M', re.VERBOSE)
cs


다음과 같은 문자열 연결 방법으로 re.VERVOBSE 옵션 없이 정규식을 여러 줄로 쓸 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> pattern = (
...     "^"                 # beginning of string
...     "M{0,4}"            # thousands - 0 to 4 M's
...     "(CM|CD|D?C{0,3})"  # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's),
...                         #            or 500-800 (D, followed by 0 to 3 C's)
...     "(XC|XL|L?X{0,3})"  # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's),
...                         #        or 50-80 (L, followed by 0 to 3 X's)
...     "(IX|IV|V?I{0,3})"  # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's),
...                         #        or 5-8 (V, followed by 0 to 3 I's)
...     "$"                 # end of string
... )
>>> print pattern
"^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$"
>>> re.search(pattern, 'M', re.VERBOSE)
cs



Named string formatting

% 포맷팅을 디션너리를 받습니다.

1
2
3
4
5
6
7
>>> print "The %(foo)s is %(bar)i." % {'foo''answer''bar':42}
The answer is 42.
 
>>> foo, bar = 'question'123
 
>>> print "The %(foo)s is %(bar)i." % locals()
The question is 123.
cs

locals() 역시 dict입니다. 로컬 변수를 이걸 이용해서 간단히 넘깁니다. (개인적으로 추천하고 싶지 않음)


아래 새로운 포맷팅 방법이 더 낫겠네요.


1
>>> print("The {foo} is {bar}".format(foo='answer', bar=42))
cs



Nested list/generator comprehensions

중첩된 리스트나 generator 포함된 표현식을 다음과 같이 쓸 수 있습니다.

1
2
[(i,j) for i in range(3for j in range(i) ]
((i,j) for i in range(4for j in range(i) )
cs

1행의 내용을 중첩된 for 문으로 작성하려면 다음과 같이 해야 합니다.

이 기능은 지저분한 중첩 루프 코드를 간단히 바꿀 수 있습니다.


1
2
3
4
= []
for i in range(3):
    for j in range(i):
        l.append((i, j))
cs



New types at runtime

동적으로 새로운 타입을 생성할 수 있습니다.

1
2
3
4
>>> NewType = type("NewType", (object,), {"x""hello"})
>>> n = NewType()
>>> n.x
"hello"
cs

이 코드는 아래와 완전히 같습니다.


1
2
3
4
5
>>> class NewType(object):
>>>     x = "hello"
>>> n = NewType()
>>> n.x
"hello"
cs


뭐 얼마나 유용한지는 모르겠지만 알아둬서 손해는 없겠죠.



.pth files

파이썬 모듈(특히 3rd party 것)을 추가하기 위해서 대부분 사람들은 PYTHONPATH 환경변수를 사용하거나 site-packages 디렉토리에 심볼릭링크나 디렉토리를 추가하는 것 같습니다. 다른 방법으로는 *.pth 파을을 사용하는 것입니다. 여기 공식 파이썬 문서의 설명이 있습니다.

파이썬의 검색 패쓰를 수정하기 위한 가장 편한 방법은 기존 파이썬 패쓰 디렉토리에 패쓰 설정파일을 추가하는 것입니다. 보통 ../site-packages/ 디렉토리겠지요. 패쓰 설정파일은 .pth 확장자를 가지며 각 줄은 sys.path에 추가할 단일 패쓰를 갖습니다. (새 패쓰가 sys.path에 추가되기 때문에 추가되는 디렉토리는 표준 모듈을 재정의하지 않습니다. 이는 표준 모듈 버전의 수정된(fixed) 버전을 설치하기 위해 이 메커니즘을 사용할 수 없다는 뜻입니다.)



ROT13 Encoding

ROT13 인코딩은 영문자를 13자리 시프트하는 간단한 방법입니다. 예를 들어 'a'는 'n', 'b'는 'o' 이런 식이죠.
ROT13은 유효한 소스코드 인코딩입니다. 소스코드 최상단에 코딩 인코딩을 rot13으로 선언하고 아래와 같은 식으로 소스코드를 작성해도 됩니다.

1
2
3
4
#!/usr/bin/env python
# -*- coding: rot13 -*-
 
cevag "Uryyb fgnpxbiresybj!".rapbqr("rot13")
cs

듣보잡의 위 코드가 뭔소리인지 알고 싶다면 아래와 같이 위 문자열을 rot13으로 decode해 보세요.

소스코드 읽기 어렵게 만드는 것 같은데... 도대체 왜??


1
'cevag "Uryyb fgnpxbiresybj!".rapbqr("rot13")'.decode('rot13')
cs


Regex Debugging

정규식은 파이썬의 멋진 기능 입니다만 디버깅은 고통스럽고 잘못된 정규식을 만들기 쉽습니다.
다행이도 파이썬은 re.DEBUG 플래그를 넘기는 걸로 정규식 파스 트리를 출력할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
>>> re.compile("^\[font(?:=(?P<size>[-+][0-9]{1,2}))?\](.*?)[/font]",
    re.DEBUG)
at at_beginning
literal 91
literal 102
literal 111
literal 110
literal 116
max_repeat 0 1
  subpattern None
    literal 61
    subpattern 1
      in
        literal 45
        literal 43
      max_repeat 1 2
        in
          range (4857)
literal 93
subpattern 2
  min_repeat 0 65535
    any None
in
  literal 47
  literal 102
  literal 111
  literal 110
  literal 116
cs

저 문법을 이해했다면 에러를 잡을 수 있겠죠. 여기에서는 [/font]의 []에 escape 문자가 빠진 걸 볼 수 있네요.

물론 주석붙은 정규식 같이 다른 플래그와도 같이 쓸 수 있습니다.


1
2
3
4
5
6
7
8
9
10
>>> re.compile("""
 ^              # start of a line
 \[font         # the font tag
 (?:=(?P<size>  # optional [font=+size]
 [-+][0-9]{1,2} # size specification
 ))?
 \]             # end of tag
 (.*?)          # text between the tags
 \[/font\]      # end of the tag
 """, re.DEBUG|re.VERBOSE|re.DOTALL)
cs



Sending to Generators

Generator 함수에 값을 전달할 수 있습니다. 다음 예제를 보시죠.

1
2
3
4
5
6
7
def mygen():
   """Yield 5 until something else is passed back via send()"""
   a = 5
   while True:
       f = (yield a) #yield a and possibly get f in return
       if f is not None: 
           a = f  #store the new value
cs


다음과 같이 실행됩니다. 즉 send(7) 하면 위 코드의 yield a의 반환값으로 전달됩니다.


1
2
3
4
5
6
7
8
9
 >>> g = mygen()
>>> g.next()
5
>>> g.next()
5
>>> g.send(7)  #we send this back to the generator
7
>>> g.next() #now it will yield 7 until we send something else
7
cs



Tab Completion in Interactive Interpreter

파이썬 셀(인터렉티브 인터프리터)에서 탭 자동완성을 지원합니다.

1
2
3
4
5
6
7
8
9
10
11
12
>>> import readline
>>> import rlcompleter
>>> readline.parse_and_bind("tab: complete")
>>> class myclass:
...    def function(self):
...       print "my function"
... 
>>> class_instance = myclass()
>>> class_instance.<TAB>
class_instance.__class__   class_instance.__module__
class_instance.__doc__     class_instance.function
>>> class_instance.f<TAB>unction()
cs

.pythonrc에 다음 파일을 넣고 PYTHONSTARTUP 환경변수 설정을 하면 tab completion을 사용할 수 있습니다.


1
2
3
4
5
6
7
8
9
# ~/.pythonrc
# enable syntax completion
try:
    import readline
except ImportError:
    print("Module readline not available.")
else:
    import rlcompleter
    readline.parse_and_bind("tab: complete")
cs


export PYTHONSTARTUP=~/.pythonrc


이것 보다는 ipython을 쓰거나 막강한 jupyter notebook을 사용하는 편이 훨씬 편리합니다.



Ternary Expression

조건부 할당을 할 수 있습니다.

x = 3 if (y == 1) else 2


이 식은 명확히 "y 가 1이면 x에 3을 아니면 2를 할당" 하라는 것 같습니다. 여기서 괄호가 필수는 아니지만 이것이 더 읽기 쉽습니다. 여기에 좀 더 복잡한 조건을 연결할 수 있습니다.


x = 3 if (y == 1) else 2 if (y == -1) else 1


어떤 표현에도 if ... else 를 사용할 수 있습니다. 다음 예를 보죠.


(func1 if y == 1 else func2)(arg1, arg2) 


y가 1일 경우 func1이 아니면 func2가 실행됩니다. 두 경우 모두 관련함수는 arg1과 arg2를 인자로 호출됩니다.


비슷하게 다음 코드도 가능합니다.


x = (class1 if y == 1 else class2)(arg1, arg2)


또 다른 예제. C, Java 스타일로는 return y == 1 ? 3 : 2;


return 3 if (y == 1) else 2



try/except/else

예외에서 else 구문:  try 블럭에서 예외가 발생하면 except 블럭이 발생하지 않으면 else 블럭이 실행됩니다.
예외가 발생하던 하지 않던 finally 블럭은 무조건 실행됩니다.

1
2
3
4
5
6
7
8
try:
  put_4000000000_volts_through_it(parrot)
except Voom:
  print "'E's pining!"
else:
  print "This parrot is no more!"
finally:
  end_sketch()
cs


with statement

PEP 343에 소개되었고 한 벌의 문장들에 대해 실시간 컨텍스트를 관리하는 context manager 객체를 지원합니다.

이 기능이 새로운 키워드를 사용하여 이 기능은 점진적으로 지원합니다. 파이썬 2.5에서는 __future__ 지시자를 통해 사용할 수 있습니다. 파이썬 2.6 이상에서는 기본으로 사용 가능합니다.

이 기능은 유용한 구성을 가능하게 하여 많이 사용합니다. 다음은 간단한 데모를 보시죠.

1
2
3
4
from __future__ import with_statement
 
with open('foo.txt''w') as f:
    f.write('hello!')
cs

이 뒤에서 일어나는 일을 보면 "with" 문장은 file 객체의 __enter__ 그리고 __exit__ 메소드를 호출합니다. (with 블럭 들어가기전 __enter__ 예외 또는 나올 때 __exit__)

with 문장 블럭에서 예외가 발생했을 때 예외상황의 자세한 부분은 __exit__ 메소드에 맡깁니다.

이 경우 정상적으로 수행되거나 예외가 발생하거나 상관없이 with 블럭을 벗어나면 파일을 닫는 것을 보증합니다.

이 기능은 일반적인 마무리(?) 작업을 암시적으로 수행하는 방법을 제공합니다.


with절은 쓰레드 잠금이나 데이터베이스 트랜잭션에 일반적으로 사용됩니다.



'Python' 카테고리의 다른 글

Ubuntu에 장고 애플리케이션 배포  (0) 2016.10.07
Python의 숨은? 특징 2  (0) 2016.08.30
Python의 숨은? 특징 (어머 이건 사야 돼)  (0) 2016.08.25
0 Comments
댓글쓰기 폼