기억해야할 Python 기능 정리(1)

python 을 매일 사용하는 것이 아니니 몇 개월이나 1 년 이상의 시간이 지나면 세세한 것들은 잊어버리기 일쑤다. 그럴 때마다 두터운 책을 찾아보는 것이 참으로 귀찮기도 하거니와 핵심만 짚어서 공부하기가 쉽지 않다.
그럴 때마다 한번 부담없이 쭈욱 읽어보면 기억이 새록새록 떠오를 수 있도록 Remind 용 기록을 남기는게 얼마나 유용한지 새삼 깨닫는다.
회사 업무때문에 오늘 예전에 보던 Python 책을 다시 꺼내들었는데 내가 언제 Python 을 잘했었나 싶다.
이 참에 책과 Research 를 통해 틈틈이 공부하는 것을 이 공간에 정리해 보려고 한다.
구구절절 모든걸 정리할 것은 아니고, 중요하고 유용한 기능인데 앞으로도 안쓰면 자꾸 까먹을 것 같은 것들만 잘 추려서 몇 회에 걸쳐 기록해본다.
아래 리스트는 이번 글에서 정리할 주제들이다.

  • 얕은 복사와 깊은 복사
  • 함수도 객체다. Python 은 모든 것이 객체다
  • Built-in 영역의 이름 및 함수들 확인
  • 함수의 가변 인자 리스트
  • 정의되지 않은 인자 처리하기
  • 람다 함수
  • Python 에서의 재귀 호출을 통한 하노이 탑 구현
  • 의외로 유용한 pass 구문

얕은 복사와 깊은 복사

a 라는 객체를 생성한 후 b = a 와 같이 할당하면 이는 a 객체가 통째로 b 로 복사되는 것이 아니다. 단지 b 또한 a 객체를 참조만 할 뿐. 따라서, 아래와 같이 a 리스트의 첫번째 값을 바꾸면 b 의 첫번째 값도 같은 것을 가리키는 것이므로 바뀌어 있다. 이를 얕은 복사라 한다.

>>> a = [1,2,3]
>>> b = a
>>> a[0] = 77
>>> a
[77, 2, 3]
>>> b
[77, 2, 3]
>>> id(a), id(b)
(140658273805664, 140658273805664)

a 와 b 가 정확히 같은 객체를 가리킨다는 것을 확인할 방법은 없을까?
이 때는 아래와 같이 객체의 고유한 값인 아이디를 반환하는 함수인 id() 를 사용하면 된다. id(객체) 함수의 결과가 같다면 이는 같은 객체인 것이다. C/C++ 의 개념으로 생각하면 a 와 b 가 포인터이면서 같은 주소를 가리키는 상황인 것이라고 생각하면 된다.

>>> id(a), id(b)
(140658273805664, 140658273805664)

객체를 완전하게 복사(깊은 복사)하고 싶을 때는 아래와 같이 2 가지 방법이 있다. b = a[:] 는 리스트일 때만 가능한 방법이고 일반적인 경우에는 copy 모듈을 사용하면 된다.

>>> a = [1,2,3]
>>> b = a[:]
>>> a, b
([1, 2, 3], [1, 2, 3])
>>> a[0] = 77
>>> a
[77, 2, 3]
>>> b
[1, 2, 3]
>>> import copy
>>> a = [1,2,3]
>>> b = copy.deepcopy(a)
>>> a[0] = 77
>>> a
[77, 2, 3]
>>> b
[1, 2, 3]

함수도 객체다. Python 은 모든 것이 객체다.

Python 에서는 리스트나 튜플, 기본 자료형 뿐 아니라 함수조차도 객체이다. 따라서, def 는 정확히 말하면 함수 객체를 만들겠다는 키워드이다. 함수도 객체이니 func2 = func 와 같이 다른 이름으로 참조할 수 있다.
globals() 내장 함수를 사용하면 global namespace 에 생성된 생성된 객체들 뿐 아니라 함수 객체들의 정보를 볼 수 있다. locals() 내장 함수는 local namespace 에 생성된 객체들의 정보를 보여준다.

>>> def func(a,b):
...   return a + b
...
>>> func
<function func at 0x7fb04e2af9b0>
>>> globals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None, 'func': <function func at 0x7fb04e2af9b0>, '__package__': None}
>>> func2 = func
>>> globals()
{'func2': <function func at 0x7fb04e2af9b0>, '__builtins__': <module '__builtin__' (built-in)>, '__package__': None, 'func': <function func at 0x7fb04e2af9b0>, '__name__': '__main__', '__doc__': None}

Built-In 영역의 이름 및 함수들 확인

dir(객체이름)을 통해 Python 의 모든 종류의 객체들이 가진 attribute 들을 조회해볼 수 있다. 모듈도 객체이므로 dir(os)를 수행하면 os module 내에 정의된 각종 이름 및 함수들의 리스트를 볼 수 있다.
__builtins__ 도 모듈의 이름인데 이 모듈에는 각종 에러 이름과 내장 함수들이 등록되어 있다. 따라서, Built-In 영역의 이름 및 함수들의 목록을 확인하고 싶다면 dir(__builtins__)를 수행해보면 된다.

>>> type(__builtins__)
<type 'module'>
>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs', 'all', 'any', 'apply', 'basestring', 'bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'long', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']

함수의 가변 인자 리스트

함수 인자의 갯수가 정해지지 않은 가변 인자를 전달받을 수 있는 방법이 있다. * 를 함수 인자 앞에 붙이면 정해지지 않은 수의 인자를 받겠다는 의미이다. 이와 같은 가변 인자는 튜플을 통해 전달된다.
따라서, 함수 내에서 가변 인자를 받으면 인자 이름을 인자들이 담긴 튜플이라고 생각하고 사용하면 된다.

>>> def func(*args):
...   print(type(args))
...   for i in args:
...     print(i)
...
>>> func(1,'a',2,'b')
<type 'tuple'>
1
a
2
b

정의되지 않은 인자 처리하기

함수의 인자 이름 앞에 ** 를 붙여서 사용할 수도 있다. 이는 인자를 사전(Dictionary) 형식으로 전달받겠다는 의미이다. 즉, 인자로 {number:1, name:’cloudrain21′} 와 같은 사전 형태로 전달하고 싶을 때 **args 와 같이 사용하면 된다.
따라서, 함수 내에서는 args 를 사전이라고 생각하고 아래와 같이 사용하면 된다.
인자의 이름과 값을 필요에 따라 그때 그때 유동적으로 한번에 전달할 수 있으니 무척 편리한 기능이다.
다만, 사용 시 주의할 점은 아래와 같이 사전 객체를 만들고 그 객체를 인자로 넘기면 안된다. (key1=val1, key2=val2)와 같이 넘겨야만 에러 없이 동작한다.

>>> def func(**user):
...   for key, val in user.items():
...     print(key, val)
...
>>> u = {1:'dplee', 2:'cloudrain21'}
>>> func(u)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() takes exactly 0 arguments (1 given)
>>> func(id='userid', passwd='1234')
('passwd', '1234')
('id', 'userid')

람다 함수

람다 함수가 익명 함수라는 것을 모르는 사람은 없을 것 같다. 이름이 없이 객체만 있을 뿐이다. 이름도 없으니 사용하고 나면 쥐도 새도 모르게 사라진다.
한 줄의 간단한 함수가 필요한 경우나 소스의 가독성을 위해, 또는 함수의 인자로 넘겨줄 때 람다 함수를 쓸 수 있다. 그렇다고 람다 함수가 반드시 한줄에 작성해야하는 것은 아니다. 역슬래쉬(\)를 줄 끝에 붙여서 여러 줄에 걸쳐 작성할 수도 있다.
함수의 원형은 lambda 인자 : <구문> 의 형태이다.
아래와 같이 f 라는 이름이 람다 함수 객체를 참조하도록 하여 f(3,4) 와 같이 사용할 수도 있고, (lambda…)(3,4)와 같이 이름 없이 직접 lambda 함수를 호출하여 사용할 수도 있다.

>>> f = lambda x, y : x * y
>>> f(3,4)
12
>>> (lambda x, y : x * y)(3,4)
12
>>> globals()
{'a': [1, 2, 3], 'func2': <function func at 0x7fb04e2af9b0>, 'aa': <function aa at 0x7fb04e2afaa0>, 'key': 2, 'val': 'cloudrain21', 'f': <function <lambda> at 0x7fb04003e848>, '__builtins__': <module '__builtin__' (built-in)>, '__package__': None, 'u': {1: 'dplee', 2: 'cloudrain21'}, 'func': <function func at 0x7fb040039ed8>, 'MyClass': <class __main__.MyClass at 0x7fb04037e0b8>, '__name__': '__main__', 'os': <module 'os' from '/usr/lib64/python2.7/os.pyc'>, '__doc__': None}

Python 에서의 재귀호출을 통한 하노이탑 구현

알고리즘을 공부하다보면 기초적인 문제로 하노이탑을 만나는 경우가 있는데 이는 Python 의 재귀 함수 호출을 이용해서도 쉽게 풀 수 있다. C/C++ 이나 Java 를 이용하여 해결하는 것과 다를 것이 없다.

>>> def hanoi(numdisks, startpos, endpos):
...   if numdisks:
...     hanoi(numdisks - 1, startpos, 6-startpos-endpos)
...     print("move %d th disk from %d to %d" %(numdisks, startpos, endpos))
...     hanoi(numdisks - 1, 6-startpos-endpos, endpos)
...
>>> hanoi(3,1,3)
move 1 th disk from 1 to 3
move 2 th disk from 1 to 2
move 1 th disk from 3 to 2
move 3 th disk from 1 to 3
move 1 th disk from 2 to 1
move 2 th disk from 2 to 3
move 1 th disk from 1 to 3

의외로 유용한 pass 구문

pass 구문은 말 그대로 패스… ‘아무 것도 수행하지 않고 지나간다’는 말이다.
아무 필요가 없을 것 같지만 while 문을 아무 일도 하지 않고 돌아야할 경우에도 유용할 뿐더러, 함수나 클래스, 모듈 등을 일단 만들어두고 차츰차츰 변수나 메소드를 추가하려고 할 때도 사용할 수 있다.

>>> while True:
...   pass
>>> Class tempClass:
      pass

References
Python 3.2 Programming (신호철 등)
https://docs.python.org/2/library
https://docs.python.org/3/library

You may also like...