기억해야할 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