https://eyeballs.tistory.com/648
python3 : print("hi eyeballs!") python2 : print "hi eyeballs!" |
python 은 동적 언어(dynamic language) 이기 때문에 변수를 생성할 때 타입을 직접 작성하지 않음 또한, 변수(데이터 값)는 객체이며 객체는 내부에 타입 정보, 실제 값, 객체 ID, 참조 횟수 등을 갖고 있음 그래서 type(123) 등으로 타입을 확인할 수 있는 것임 |
타입을 확인하기 위해 아래 메소드를 사용 가능 type(1) # <class 'int' > isinstance(1, int) # True isinstance("hi!", int) # False |
python은 강타입(strong type) 언어임 즉, 객체의 값 변경은 가능하지만, 객체의 타입은 변경할 수 없음 |
python 에서 변수는 객체를 가리키는 이름임 다른 정적 언어(static language) 들은 변수 자체에 타입이 있기 때문에, 변수에 값을 할당 할 때부터 타입을 지정해줘야 하지만 python 은 동적 언어이기 때문에, 변수 자체에 타입이 없고 변수에 값을 할당 할 때 타입 지정이 필요 없음 |
a = 1 b = a print(a) # 1 id(a) #9440320 print(b) # 1 id(b) #9440320 a = 2 print(a) # 2 id(a) # 9440355 print(b) # 1 id(b) # 9440320 여기서 id 값이 달라짐 왜냐면 a = 2 를 통해 a에 다른 불변 객체(2)를 바라보도록 할당했기 때문 b는 여전히 기존 불변 객체(1)를 바라보고 있음 |
a = [1,2,3] b = a print(a) # [1,2,3] print(b) # [1,2,3] a[0] = 99 print(a) # [99,2,3] print(b) # [99,2,3] 반대로 여기선 b 가 a와 동일하게 업데이트 되었음 왜냐면 list 는 가변 값의 배열이기 때문 하지만 list 객체 자체는 불변임 만약 a를 새로 할당했다면, b 는 바뀌지 않았을 것 a = [1,2,3] b = a a = [2,3,4] print(a) # [2,3,4] print(b) # [1,2,3] |
a = "!" b = "!" id(a) #21926656 id(b) # 21926656 이렇게 자주 사용된다 싶은 객체(여기선 "!" 를 담고 있는 객체)는 파이썬이 따로 저장해 둠 |
def func(p) : ... func(1) func([1,2,3]) func("eyeballs") 함수 파라미터에 들어가는 정보는 "변수의 참조값"임 def func(p) 에서 p 는 참조값을 넘겨받은 변수가 됨 이를 Call by Object Reference 라고 부름 |
0이나 empty 값, None 아닌 값은 True 로 간주함 bool(True) #True bool(1) #True bool(-1) #True bool(0) #False bool(0.0) #False bool("") #False bool(None) #False bool(Set()) #False bool({}) #False bool(()) #False bool([]) #False |
숫자를 표현할 때 세 자릿수를 underbar 를 이용하여 표현 가능 million = 1_000_000 print(million) # 1000000 물론 꼭 위와 같이 쓰지 않아도, 숫자 사이에 어느 곳에나 넣을 수 있음 a = 1_2_3 print(a) # 123 |
print(1/2) # 0.5 print(1//2) # 0. 소수점 이하 버려짐 print(1%2) # 1. 나머지를 반환 |
print(chr(65)) # 'A' print(ord('A')) # 65 |
int 는 굉장히 큰 값도 들어감. 심지어 10의 100제곱(googol)도 들어감 print(int(10**100)) # 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 |
a = b = c = 1 print(a) # 1 print(b) # 1 print(c) # 1 |
print(1<2<3) # True |
a = 1 print(a == 1) # True print(a != 1) # False print((not a) == 1) # False |
문자열, 배열 등 내부 값을 갖는 객체에서 어떤 값이 포함되어있는지 확인하려면 in 을 사용 print('a' in "abcde") # True print("abc" in "abcde") # True print('a' in ('a','b','c')) # True print('a' in {'a':'a', 'b':'b'}) # True |
:= 는 바다코끼리 연산자라고 불림... 이것은 코드 실행과 실행 결과 할당을 한 번에 처리할 수 있게 도와줌 이를테면 아래 두 코드는 동일한 결과를 출력함 diff = 2 - 1 if diff >= 0 : print("+") else : print("-") 출력 결과 : + if diff := 2-1 >= 0 : print("+") else : print("-") |
탈출 문자(Escape character) 를 무효화하려면 r 포맷팅을 사용 print(r"a\nb\tc\\d\e") # a\nb\tc\\d\e |
문자열을 연결할 때 + 를 사용 할 필요 없음 print("a" "b" "c") # "abc" |
print("aba".replace('a', 'x')) # xbx print("abcdefg"[::2]) # aceg. 2 개씩 건너뛰면서 슬라이싱 print("abcdefg"[::-1]) # gfedcba. -1 개씩 건너뛰며 슬라이싱. 결과적으로, 뒤에서부터 읽게 되어 문자열이 reverse 됨 |
문자열 검색에 사용되는 메소드는 두 가지 find() : 처음부터 문자열을 찾으면 오프셋(index)를 반환, 못 찾으면 -1 을 반환 index() : 처음부터 문자열을 찾으면 오프셋(index)를 반환, 못 찾으면 에러 rfind() : 끝에서부터 문자열을 찾으면 오프셋(index)를 반환, 못 찾으면 -1 을 반환 rindex() : 끝에서 부터 문자열을 찾으면 오프셋(index)를 반환, 못 찾으면 에러 "abcba".find("b") # 1 "abcba".find("x") # -1 "abcba".rfind("b") # 3 |
문자열이 알파벳이나 숫자로 이루어져있는지 확인 가능 "123".isnumeric() #True. 숫자로만 이루어져 있음 "123".isdigit() #True. 숫자로만 이루어져 있음 "abc".isalpha() # True. 문자로만 이루어져 있음 "abc123".isalnum() # True. 숫자와 문자로만 이루어져 있음 |
"..a..".strip('.') # 'a' "..a..".lstrip('.') # 'a..' "..a..".rstrip('.') # '..a' |
format 사용 예제 "a{}{}d".format("b","c") # 'abcd' "a{1}{0}d".format("b","c") # 'acbd' "a{x}{y}d".format(x="b", y="c") # 'abcd' |
f-문자열 사용 예제 a = "a" b = "b" f"x{a}{b}y" # "xaby" f"x{a=}{b=}y" # "xa='a'b='b'y" |
break 문이 포함된 loop 문에서 break 가 실행되지 않으면 실행되는 무언가를 넣어야 할 때 else 를 사용함 |
while index < len([0,1,2,3]): index += 1 if index > 5 : break else : print("no break") 출력 결과 : no break |
for n in (0,1,2) : if n < 0 : break else : print("no break") 출력 결과 : no break |
위와 같이 else 를 break checker 로 사용 가능 |
(1,2,3) ['a', 'b', 'c'] 를 각 index 별로 묶어서 [(1, 'a'), (2, 'b'), (3, 'c')] 로 만드는 기능은 zip 메소드를 통해 가능함 a = (1,2,3) b = ['a', 'b', 'c'] z = zip(a, b) type(z) # <class 'zip' > print(list(z)) # [(1, 'a'), (2, 'b'), (3, 'c')] for x, y in zip(a,b) : print(x+", "+y) 출력 결과 : 1, a 2, b 3, c |
>>> a = (1,2,3) >>> b = ['a','b','c'] >>> c = ([], (), {}) >>> z = zip(a,b,c) >>> print(list(z)) [(1, 'a', []), (2, 'b', ()), (3, 'c', {})] |
>>> a = (1,2,3) >>> b = ['a','b','c'] >>> print( dict(zip(a,b)) ) {1: 'a', 2: 'b', 3: 'c'} |
a = (1,2,3) # a 는 3개 b = ['a'] # b 는 1개. a 보다 2개 적음 z = zip(a, b) print(list(z)) # [(1, 'a')]. 가장 적은 개수를 갖는 b 에 따라 zip 결과가 정해짐 |
zip 메소드로 나온 결과를 어떤 방식으로든 한 번 사용하면 그 뒤로는 empty 값이 나와버림 z = zip(a,b) print(list(z)) # [(1, 'a'), (2, 'b'), (3, 'c')] print(list(z)) # [] 바로 윗 줄에서 사용했기 때문에 z 에 빈 값이 들어감 |
튜플 만드는 법 t = () t = "eyeballs", t = ("eyeballs",) t = 1, 2, 3 t = (1, 2, 3) t = tuple([1,2,3]) t = (1, 2) + (3, 4) t += t |
튜플로 여러 변수에 값을 넣어줄 수 있음 a, b, c = (1,2,3) a, b, c = 1, 2, 3 a, b = b, a |
named tuple 이란 것이 있음 이름과 위치로 값에 접근 가능한 자료구조임 튜플의 서브클래스이며, collections 모듈을 통해 사용 가능 이름이 있는 필드를 가진 불변(immutable) 객체를 만들어 사용한다고 생각하면 됨 튜플처럼 동작하면서도 필드에 이름을 부여할 수 있어 가독성이 뛰어나고 코드 유지보수성이 좋아짐 from collections import namedtuple >>> Person = namedtuple("Person", ["name", "age"]) >>> p1 = Person(name = "A", age = 30) # 속성은 두 가지 뿐이지만, 불변의 dict 같은 객체를 만들 수 있음 >>> p2 = Person(name = "B", age = 60) >>> p1[0], p1[1] ('A', 30) >>> p2.name, p2.age ('B', 60) >>> p3 = Person._make(['C', 90]) # _make 를 사용하여, 리스트를 namedtuple 에 바로 넣음 >>> p3[0], p3[1] ('C', 90) >>> type(p3._asdict()) # _asdict 를 사용하여, namedtuple 을 dictionary 로 변경 <class 'dict'> >>> print(p3._asdict()) {'name': 'C', 'age': 90} >>> p4 = p3._replace(age=10) # _replace 를 사용하여, 기존 namedtuple 속성을 수정한 새로운 namtedtuple 객체 생성 >>> print(p4) Person(name='C', age=10) >>> id(p3) 68810088 >>> id(p4) 68794984 # 기반이 된 p3 과는 다른, 새로운 객체 p4 이게 왜 tuple 이랑 비슷하게 동작한다는 것인지 모르겠음... dirtionary 랑 더 비슷해 보임 dictionary 보다 namedtuple 이 더 효율적으로 동작한다고 함 |
리스트 만드는 법 l = [] l = [1,2,3] l = list() l = list('eyeballs') # ['e','y','e','b','a','l','l','s'] l = list((1,2,3)) l = "a.b.c".split(".") l += l |
append 를 사용하면 메소드의 리스트 인자가 리스트의 마지막 항목에 들어감 extend 를 사용하면 메소드의 리스트 인자가 리스트에 병합됨 >>> a = [1,2,3] >>> a.append([4,5]) >>> print(a) # [1, 2, 3, [4, 5]] >>> a = [1,2,3] >>> a.extend([4,5]) >>> print(a) # [1, 2, 3, 4, 5] |
list 에서 항목 제거하기 a = [1,2,3] del a[1] # 숫자 2를 삭제 print(a) # [1,3] a = [1,2,3] a.remove(2) # 숫자 2를 삭제 print(a) # [1,3] a.del(1) 이런 문법은 아님... |
list 에서 항목을 가져옴과 동시에 제거하기 a = [1,2,3,4,5] n = a.pop() print(n) # 5 print(a) # [1,2,3,4] a = [1,2,3,4,5] n = a.pop(0) print(n) # 1 print(a) # [2,3,4,5] a = [1,2,3,4,5] n = a.pop(1) print(n) # 2 print(a) # [1,3,4,5] n = a.pop(1) print(n) # 4 print(a) # [1,4,5] |
sort() 는 list 자체 내부 정렬을 진행함 sorted() 는 list 의 정렬된 복사본을 반환 s = [2,5,4,1,3] result = s.sort() print(result) # None print(s) # [1,2,3,4,5] s = [2,5,4,1,3] result = sorted(s) print(result) # [1,2,3,4,5] print(s) # [2,5,4,1,3] 내림차순 정렬하려면 인수에 reverse = True 추가 s = [2,5,4,1,3] result = s.sort(reverse = True) print(s) # [5,4,3,2,1] |
a = [1,2,3] b = a 여기서 a 와 b 는 동일한 리스트 객체를 바라보고 있음 a 에서 리스트가 수정되면 b에서도 수정된 객체를 바라봄 a 의 복사본을 b 에 넣고 싶다면 아래와 같은 방법들을 사용 a = [1,2,3] b = a.copy() a[0]=-99 print(a) # [-99, 2, 3] print(b) # [1, 2, 3] a = [1,2,3] b = a[:] a[0]=-99 print(a) # [-99, 2, 3] print(b) # [1, 2, 3] a = [1,2,3] b = list(a) a[0]=-99 print(a) # [-99, 2, 3] print(b) # [1, 2, 3] |
copy 는 얕은 복사임 a = [1,2,[3,4,5]] b = a.copy() a[2][0]=-99 print(a) # [1, 2, [-99, 4, 5]] print(b) # [1, 2, [-99, 4, 5]] id(a[2]) # 30645800 id(b[2]) # 30645800 내부에 중첩된 list 까지 모두 제대로 복사하려면 deepcopy 를 사용하면 됨 import copy a = [1, 2, [3, 4, 5]] b = copy.deepcopy(a) a[2][0] = -99 print(a) # [1, 2, [-99, 4, 5]] print(b) # [1, 2, [3, 4, 5]] id(a[2]) # 62359848 id(b[2]) # 30659592 |
리스트 컴프리헨션을 사용하여, 한 줄로 for 문을 구현할 수 있음 mylist = [i for i in a] print(mylist) # [1, 2, 3, 4, 5] mylist = [i**2 for i in a] print(mylist) # [1, 4, 9, 16, 25] mylist = [i for i in a if i%2==0] print(mylist) # [2, 4] |
list 보다 tuple 을 사용하는 이유는 - tuple 이 공간을 더 적게 사용함 - tuple 은 한 번 생성되면 내부 값들이 변하지 않기 때문에, 값이 손상될 염려가 없음 - tuple 을 dictionary key 로 사용 가능 (list 는 안 되나보네..?) - namedtuple 이 객체의 단순한 대안으로 사용 가능함 |
dictionary 만드는 법 d = {} d = dict() d = {"a" : 1, 2 : "b"} d = dict( [ ['a',1], ['b',2], ['c',3] ] ) d = dict( ( ['a',1], ['b',2], ('c',3) ) ) |
dictionary 에서 값 확인 및 추출하는 법 d['a'] # 만약 'a' 키가 없으면 exception d.get('a') # 만약 'a' 키가 없으면 None 반환 d.get('a', 'nothing here') # 만약 'a' 키가 없으면 'nothing here' 를 반환 'a' in d # 'a' 가 d 의 key 값이라면 True if key := 'a' in d: print("key : ", key) print("value : ", d['a']) d.keys() # 모든 키 얻기 d.values() # 모든 값 얻기 d.items() # 모든 키값 쌍 얻기 |
dictionary 합치기 d1 = {1:1} d2 = {1:1, 2:2} d3 = {2:2, 3:3} d = {**d1, **d2} #{1: 1, 2: 2} d = {**d1, **d2, **d3} # {1: 1, 2: 2, 3: 3} d1 = {1:1} d2 = {1:1, 2:2} d1.update(d2) # 결과값이 나오는 메소드가 아님, 자기 자신의 dictionary 에 추가하는 것 print(d1) # {1: 1, 2: 2} d1 = {1:1} d2 = {1:'a'} d1.update(d2) print(d1) # {1:'a'}. key 가 동일한 아이템은 update 의 인자(d2) 값으로 업데이트 됨 |
dictionary 삭제 d = {1:1, 2:2} del d[1] print(d) # {2:2} del d[-99] # exception 발생 d = {1:1, 2:2} a = d.pop(1) print(a) # 1 print(d) # {2:2} a = d.pop(-99) # exception 발생 a = d.pop(-99, 'Nothing here') # key 가 없는 경우, default 값 반환 print(a) # Nothing here d.pop() # exception 발생 |
list 와 마찬가지로, dictionary 로 얕은 복사, 깊은 복사가 있음 얕은 복사 a = {1:1} b = a.copy() 깊은 복사 import copy a = {1:1} b = copy.deepcopy(a) |
== 혹은 != 를 사용하여 비교 가능함 a = {1:1, 2:2} b = {2:2, 1:1} a==b # True a!=b # False not a==b # False a = {1:[1,2]} b = {1:[2,3]} a==b # False |
list 와 마찬가지로, 딕셔너리 컴프리헨션을 사용하여 for 문을 한 줄로 사용 가능함 >>> d = {k : k for k in (1,2,3)} >>> d {1: 1, 2: 2, 3: 3} >>> word = "eyeballs" >>> letter_counter = {key: word.count(key) for key in word} >>> letter_counter {'e': 2, 'y': 1, 'b': 1, 'a': 1, 'l': 2, 's': 1} >>> word = "eyeballs" >>> letter_counter = {key: word.count(key) for key in word if key in ('b','l','s')} >>> letter_counter {'b': 1, 'l': 2, 's': 1} |
존재하지 않는 key 로 접근할 시 default 값을 반환하도록 할 수 있음 >>> d = {'a':1, 'b':2} >>> print(d.get('c')) # get 으로 접근하면 None 을 반환받음 None >>> d = {'a':1, 'b':2} >>> print(d.setdefault('a', 3)) # 'a' 는 존재하는 key 이므로 1 을 반환 1 >>> print(d.setdefault('b', 3)) # 'b' 는 존재하는 key 이므로 2 를 반환 2 >>> print(d.setdefault('c', 3)) # 'c' 는 존재하지 않는 key 이므로 default 값으로 넣은 두 번째 인수 3 을 반환 3 >>> print(d) {'a': 1, 'b': 2, 'c': 3} # 더불어 'c':3 을 추가해줌 |
defaultdict 를 사용하여, 존재하지 않는 key 로 접근할 시 default 값을 반환하도록 할 수 있음 이 때 함수를 넣어줄 수 있음 >>> from collections import defaultdict >>> dd_int = defaultdict(int) # int 를 넣었음, int 의 default 값은 0으로 제공됨 >>> print(dd_int) defaultdict(<class 'int'>, {}) # 처음에는 아무것도 없음 >>> dd_int['a'] = 1 # key 'a', value 1 을 넣음 >>> print(dd_int['a']) # key 'a' 는 존재하기 때문에 1 을 반환 1 >>> print(dd_int['b']) # key 'b' 는 존재하지 않기 때문에 int 의 default 값인 0을 반환 0 >>> print(dd_int) defaultdict(<class 'int'>, {'a': 1, 'b': 0}) # 더불어 a, b 모두 dict 에 넣어줌 >>> print(defaultdict(str)['key']) # str 의 default 값은 "" 으로 제공됨 >>> print(defaultdict(dict)['key']) # dict 의 default 값은 {} 으로 제공됨 {} >>> print(defaultdict(list)['key']) # list 의 default 값은 [] 으로 제공됨 [] >>> def default_func(): return "default_value" # default 값을 반환하는 함수를 넣어 default 값을 설정할 수 있음 >>> print(defaultdict(default_func)['key']) default_value >>> print(defaultdict(lambda: 'default_value')['key']) # 간단하게 lambda 를 사용하여 default 값을 반환하는 함수 넣기 가능 default_value |
set 생성하기 s = set() s = {1,2,3,3} s = set('aabbcc') # {'a', 'b', 'c'} s = set( [1,1,2,2,3,3] ) # {1,2,3} s = set( {1:'a', 2:'b', 2:'c'} ) # {1,2}. 키 값만 사용됨 |
s = {1,2,3} s.add(4) # {1,2,3,4} s.remove(1) # {2,3,4} |
a = {1,2,3} b = {2,3,4} >>> a & b # {2, 3}. 교집합 >>> a - b # {1}. 차집합 >>> b - a # {4}. 차집합 >>> a | b # {1, 2, 3, 4}. 합집합 >>> a.symmetric_difference(b) # {1, 4}. exclusive 아래는 부분집합 a = {2,3} b = {1,2,3,4} >>> a.issubset(b) # True >>> a <= b # True >>> a < b # True >>> b.issubset(a) # False >>> b <= a # False >>> b < a # False a = {1, 2, 3} b = {2} b < a # True. b가 a 와 같지 않으면서 b의 모든 요소가 a 안에 포함된 진부분집합 b <= a # True. b의 모든 요소가 a 안에 포함된 부분 집합 a = {1, 2, 3} b = {1, 2, 3} b < a # False. b가 a 와 같기 때문에 False. 진부분집합이 되지 못 함 b <= a # True. b의 모든 요소가 a 안에 포함된 부분 집합 |
셋 컴프리헨션 >>> s = {a for a in (1,1,2,2)} >>> s {1, 2} >>> s = {a%3 for a in (1,2,3,4,5,6)} >>> s {0, 1, 2} >>> s = {a%3 for a in (1,2,3,4,5,6) if a % 3 != 0} >>> s {1, 2} |
set 의 값을 불변(추가, 삭제, 업데이트 되지 않는 불변)으로 만들려면 frozenset 을 사용 >>> s = frozenset([1,1,2,2]) >>> s.add(3) Traceback (most recent call last): File "<pyshell#136>", line 1, in <module> s.add(3) AttributeError: 'frozenset' object has no attribute 'add' |
None 과 False 구분 할 때는, is 를 사용함 a = None if a is None : print("None") else : print("False") |
함수 호출시 인수 이름으로 값 직접 지정 가능 def func(a, b) : print(a, b) print(func(b = "BB", a = "AA")) # "AA, BB" |
함수 인수의 기본값 설정 def func(a, b="BB"): print(a, b) print(func("AA")) # "AA, BB" print(func(a = "AA")) # "AA, BB" print(func("AA", "XX")) # "AA, XX" print(func(a = "AA", b = "XX")) # "AA, XX" |
함수 인수의 기본값은, 함수 호출할 때 계산되는 게 아니라, 함수가 정의될 때 계산됨 즉, 함수가 정의될 때 인수의 기본값이 유지되는 것임 아래 예제로 이해해보자 >>> def func(a, l = []): l.append(a) print(l) >>> func(1) [1] >>> func(2) [1, 2] # [2] 가 나올 줄 알았지만, 바로 위 1 이 포함된 [1, 2] 가 나옴. 왜냐면 인수의 기본값이 유지되기 때문 함수에 리스트 같은 가변 인수가 들어가는 경우엔, 함수 파라미터에 리스트의 참조값이 복사되기 때문에 함수 내부에서 가변 작업한 것이 리스트에 그대로 적용됨 l = [1,2,3] def func(l) : l.append(99) print(l) # [1,2,3,99] |
함수에 넣을 인자 개수를 특정지을 수 없는 상황일 때 인수에 애프터리크 ( * ) 를 사용 >>> def func(*args): print(args) >>> func(1) (1,) >>> func(1,2) (1, 2) >>> func(1,2,3,4,5) (1, 2, 3, 4, 5) 아래와 같이, 함수 파라미터로 넣을 튜플에 애프터리스크를 사용하면 함수 내부에서 튜플로 인식하지 않고 각각의 값이 들어온 것으로 인식함(즉, 매개변수로 분해함) >>> def func(*args): print(args) >>> a = (1,2,3) >>> func(a) ((1, 2, 3),) # 튜플 하나가 들어온 것으로 인식 >>> func(*a) (1, 2, 3) # 1, 2, 3 이 들어온 것으로 인식 |
가변 인자(*args) 가 앞에 올 수도 있음 가변 인자 뒤에 오는 인자들이 키워드 기반 인자라면... >>> def func(*args, c, d): print(args, c, d) >>> func(1, c="CC", d="DD") (1,) CC DD >>> func(1,2,3,4,5, c="CC", d="DD") (1, 2, 3, 4, 5) CC DD >>> func(c="CC", d="DD", 1,2,3) # 가변 인자 값부터 넣어야 함 SyntaxError: positional argument follows keyword argument |
애프러리스크가 두 개 붙으면, 받은 keyword 값 쌍들을 함수 내부에서 dictionary 로 만들어 받음 >>> def func(**kwargs): print(kwargs) >>> func() {} >>> func(a="AA", b="BB") {'a': 'AA', 'b': 'BB'} >>> func(c="CC") {'c': 'CC'} # 애프터 리스크 사용시 인수의 기본값 유지되지 않음 근데 정작 dictionary 를 넣으면 에러가 발생.. >>> func({1:1, 2:2}) Traceback (most recent call last): File "<pyshell#174>", line 1, in <module> func({1:1, 2:2}) TypeError: func() takes 0 positional arguments but 1 was given |
단일 애프터리스크는, 함수의 위치 기반 인수와 키워드 전용 인수 사이에 넣어, 이 둘을 구분하는 역할을 함 예를 들어 def func(a, b, *, c="CC", d="DD") 처럼 위치 기반 인수(a, b) 와 키워드 전용 인수(c, d) 를 나누고 구분짓는 역할 애프터리스크 앞에 위치한 a, b 에는 (무조건) 위치 기반의 인수가 들어가야하고 애프터리스크 뒤에 위치한 c, d 에는 (무조건) 키워드 기반 인수가 들어가야 함 >>> def func(a, b, *, c="CC", d="DD") : print(a,b,c,d) >>> func(1,2) 1 2 CC DD >>> func(1) # 위치 기반으로 들어와야 할 b 의 인수가 들어오질 않아서 에러 Traceback (most recent call last): File "<pyshell#206>", line 1, in <module> func(1) TypeError: func() missing 1 required positional argument: 'b' >>> func(1,2) 1 2 CC DD >>> func(1,2, c="XX") 1 2 XX DD >>> func(c="XX", 1, 2) # 위치 기반으로 들어와야 할 a 자리에 c 가 들어와서 에러 SyntaxError: positional argument follows keyword argument >>> func(1,2,3,4) # 키워드 기반으로 들어와야 할 c, d 자리에 키워드가 들어오질 않아서 에러 Traceback (most recent call last): File "<pyshell#210>", line 1, in <module> func(1,2,3,4) TypeError: func() takes 2 positional arguments but 4 were given |
단일 애프터리스크를 이용하여, 모든 인자를 키워드 기반 인자로 받도록 강제할 수 있음 >>> def func(*, a, b): print(a,b) >>> func(a="AA", b="BB") # a 와 b 에 키워드 기반 인자를 넣음 AA BB >>> func(1) # 키워드 기반 인자가 아닌 값은 들어갈 수 없음 Traceback (most recent call last): File "", line 1, in func(1) TypeError: func() takes 0 positional arguments but 1 was given >>> func(1, a="AA", b="BB") # 키워드 기반 인자가 아닌 값은 들어갈 수 없음 Traceback (most recent call last): File "<pyshell#224>", line 1, in <module> func(1, a="AA", b="BB") TypeError: func() takes 0 positional arguments but 1 positional argument (and 2 keyword-only arguments) were given |
함수 바디가 시작되기 전에 문자열을 넣어 함수에 대한 간단한 문서를 작성할 수 있음 문서는 help 를 사용하거나, 함수의 .__doc__ 을 호출하여 확이 가능 이를 독스트링이라고 부름 예를 들어 >>> def func(): "this func print your name" print("eyeballs") >>> help(func) Help on function func in module __main__: func() this func print your name >>> func.__doc__ 'this func print your name' 이런 방식을 통해, 사용법(무슨 인자가 얼마나 어떻게 들어가야 하는지)을 모르는 함수 사용시 도움을 받을 수 있음 >>> import copy >>> help(copy.deepcopy) Help on function deepcopy in module copy: deepcopy(x, memo=None, _nil=[]) Deep copy operation on arbitrary Python objects. See the module's __doc__ string for more info. 이 간단한 문서를 읽어보고, 나는 "아 인수로 x, memo, _nil 이 들어갈 수 있고 memo 는 default 값이 None 이구나" 라고 알 수 있음 |
추가로 어떤 객체를 받았을 때 그 객체가 사용 가능한 메소드를 보려면 dir 를 사용하면 됨 >>> dir(copy.deepcopy) ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] >>> dir("eyeballs") ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] |
함수 내에 함수를 선언하고 사용할 수 있음 이렇게 만든 내부 함수는 함수 바깥에서 사용 불가하기 때문에 임시로 만들어 사용하기 좋음 >>> def func(): def inner_func(): print("hi eyeballs") inner_func() >>> func() hi eyeballs >>> inner_func() # 여기서 내부 함수 호출이 불가능 Traceback (most recent call last): File "<pyshell#292>", line 1, in <module> inner_func() NameError: name 'inner_func' is not defined |
내부 함수를 이용하여 '클로저'를 만들 수 있음 chatGPT 에 의하면, 클로저란 다음과 같음 "클로저(Closure)는 함수 내부에서 또 다른 함수를 정의하고 반환할 때 만들어지는 함수 객체입니다. 반환된 내부 함수는 자신이 선언된 환경(외부 함수의 변수 등)을 기억하여, 외부 함수의 실행 컨텍스트가 종료된 후에도 이 정보를 사용할 수 있습니다." 즉, 생성될 때 넣어준 정보를 계속 지니고 있는 함수가 클로저임 >>> def func(mention): def inner_func(): return f"what you said was this : {mention}" return inner_func >>> a = func("hi eyeballs!") >>> b = func("bye eyeballs!") >>> a() # a 는 클로저(함수)이기 때문에 실행이 가능하며, 실행시 클로저를 정의할 때 넣어줬던 정보를 기억함 'what you said was this : hi eyeballs!' >>> b() # b 도 클로저(함수)이기 때문에 실행이 가능하며, 실행시 클로저를 정의할 때 넣어줬던 정보를 기억함 'what you said was this : bye eyeballs!' |
클로저를 사용하면, 함수 객체 내부의 상태를 계속 유지할 수 있음. 내부 함수에서 내부 상태를 업데이트하는 기능을 넣어주면, 상태를 계속 업데이트 할 수 있음 >>> def func(): c = 0 def add(a=1): nonlocal c c += a return c return add >>> closure = func() >>> closure() 1 >>> closure() 2 >>> closure(98) 100 여기서 중요한 점, 외부 함수에 정의된 변수에 내부 함수가 접근할 수 없음 >>> def outer(): c = 0 def inner(): c+=1 # 내부함수(inner)에서 외부함수에 정의된 변수 c 에 접근 시도 print(c) inner() >>> outer() Traceback (most recent call last): File "<pyshell#375>", line 1, in <module> outer() File "<pyshell#374>", line 6, in outer inner() File "<pyshell#374>", line 4, in inner c+=1 UnboundLocalError: local variable 'c' referenced before assignment 외부 함수에 정의된 변수에 내부 함수가 수정하려면 nonlocal 을 사용 >>> def outer(): c = 0 def inner(): nonlocal c c+=1 print(c) inner() >>> outer() 1 근데 nonlocal 이 "함수 바깥의 변수"에 수정하는 것을 도와주는 명령어는 아님 >>> c = 0 >>> def func(): nonlocal c # 가장 바깥쪽 c 에 접근하면 문법 에러가 발생함... SyntaxError: no binding for nonlocal 'c' found 이렇게 가장 바깥쪽 c 변수가 위치한 곳을 "global scope" 라고 함 global scope 에 있는 변수는 nonlocal 을 이용하여 접근할 수 없음 nonlocal 은, 단지 nearest enclosing scope (outer scope) 에 정의된 변수에만 접근 및 수정 할 수 있게 도와줌 global scope 에 있는 변수에 접근 및 수정하려면 global 을 사용해야 함 >>> c = 0 >>> def func(): global c c+=1 print(c) >>> func() 1 >>> print(c) 1 >>> func() 2 >>> print(c) 2 추가로, global scope(namespace) 에 있는 변수들을 보려면 global() 을 실행하여 확인 가능 local scope(namespace) 에 있는 변수들을 locals() 을 실행하여 확인 가능 |
클로저를 사용하면, 내부 데이터를 은닉(캡슐화)할 수 있음 아래 예제에서 625 라는 숫자는, 클로저를 호출하는 바깥에서는 볼 수 없는 미지의 은닉된 숫자임 >>> def func(): def inner_func(a): if a == 625 : print("correct!") else : print("wrong") return inner_func >>> closure = func() >>> closure(1) wrong >>> closure(50) wrong >>> closure(100) wrong >>> closure(625) correct! |
클로저는 정보를 은닉하여 계속 유지하기 때문에, 메모리에 계속 남아있게 됨 따라서 사용하지 않는 클로저는 삭제하는 것이 좋음 del closure |
람다 lambda 함수는 단일 문장으로 표현되는 익명 함수임 따로 def 를 이용하여 정의내리지 않고, 그 때 그 때 필요한 때 사용하고 버림 일반적인 함수 def func(a, b): print(a, b) 동일한 역할을 하는 람다 함수 lambda a, b : print(a, b) 아래처럼 간단하게 사용 가능 a = lambda a, b : print(a,b) a(1,2) # 1 2 lambda 함수는, 콜론 ( : ) 뒤의 명령어가 실행되거나 반환됨 >>> a = lambda a, b : a+b >>> a(1,2) 3 >>> x, y = 1, 2 >>> swap = lambda a, b: (b, a) >>> x, y = swap(x, y) >>> print(x, y) 2 1 |
아래와 같은 함수를 인자로 받는 함수에서 >>> def func(mylist, myfunc): for i in mylist: print(myfunc(i)) def 로 정의된 함수를 넣어도 되지만 >>> def myfunc(i): return i.capitalize() >>> func(["hi", "eyeballs"], myfunc) Hi Eyeballs lambda 함수를 바로 넣을 수 있음 >>> func(["hi", "eyeballs"],lambda i: i.capitalize()) Hi Eyeballs |
아무것도 실행하지 않는 함수를 만들기 위해 pass 를 사용 def doNothing(): pass |
python3 에서 모든 객체는 기본적으로 "강한 참조"로 생성됨 강한 참조는 참조 카운트를 증가시키고, garbage collector 에 의해 수거되지 않음 약한 참조는 참조 카운트를 증가시키지 않고, garbage collector 에 의해 수거됨 약한 참조는 일부러 만들어야 함 강한 참조는 메모리의 객체 자체를 직접 참조하지만, 약한 참조는 객체를 간접적으로 참조함 강한 참조와 약한 참조 예제 >>> import weakref >>> def func(): pass # 강한 참조를 갖는 객체 생성 >>> weak_ref = weakref.ref(func) # 약한 참조를 갖는 객체 생성 >>> type(weak_ref()) # 약한 참조 객체 실행해보면 반환값이 function 인 것을 확인 <class 'function'> >>> del func # 강한 참조 객체가 삭제되면, 약한 참조도 따라서 삭제됨 >>> type(weak_ref()) # 약한 참조 객체 실행해보면 반환값이 None 으로 변한 것을 확인 <class 'NoneType'> - 강한 참조 (Strong Reference) - 참조 카운트 : 증가 - 객체 생존 : 강한 참조가 존재하면 삭제되지 않음 - 활용례 : 일반적인 객체 - 약한 참조 (Weak Reference) - 참조 카운트 : 증가하지 않음 - 객체 생존 : 강한 참조가 없으면 Garbage Collection 가능 (수거되어 사라짐) - 활용례 : cache 를 만들거나, 메모리 관리가 중요한 앱을 만들 때 사용됨 이거 마치 리눅스의 하드 링크와 소프트(심볼릭) 링크의 관계 같다는 느낌이 듦... 강한 참조를 갖는 객체를 바라보는 약한 참조 객체는 soft link 같아서 강한 참조 객체가 사라지면(원본 file/dir 가 사라지면) 약한 참조 객체는 참조 할 객체가 사라지게 됨(soft link 가 갈 길을 잃음) |
nested list 가 중첩된 리스트를 flatten 하게 만들기 위해 generator 를 아래와 같이 사용 가능 >>> lol = [1,[2,[3,4],5,[6],7]] >>> def flatten(l): for item in l: if isinstance(item, list): for subitem in flatten(item): yield subitem else: yield item >>> list( flatten(lol) ) [1, 2, 3, 4, 5, 6, 7] |
try-except 예제 |
try: 1/0 except: print("divided by zero") |
try: [1,2,3][4] except IndexError as ie: print("index error. message : ", ie) # 여기서 ie 는 시스템에서 작성해주는 error 메세지 except Exception as e: print("exception. message : ", e) |
def divide(by): try : result = 1/by except ZeroDivisionError as e : print("divided by zero", e) else : print(result) # else 는 예외가 발생하지 않았을 때 실행됨 finally : print("done") |
try: raise Exception("exception by developer on purpose") # 일부러 Exception 을 발생. error message 입력 할 수 있음 except Exception as e: print("exception message : ", e) riase # raise 를 다시 사용하여, 똑같은 exception 을 다시 발생시킴 exception message : exception by developer on purpose Traceback (most recent call last): File "<pyshell#619>", line 2, in <module> raise Exception("exception by developer on purpose") Exception: exception by developer on purpose |
try: raise RuntimeError("exception by developer on purpose") # 원하는 Error 를 발생시킬 수 있음 except RuntimeError as re: print("exception message :", re) exception message : exception by developer on purpose |
assert 를 이용하여 예외를 발생시킬 수 있음 asset 는 나와선 안 되는 조건을 검사할 때 넣는 명령어임 지정된 조건식이 False 일 때 AssertionError 를 발생시킴 >>> def func(i): assert i % 3 == 0, '3의 배수가 아님' print(i,'는 3의 배수임') >>> func(3) 3 는 3의 배수임 >>> func(1) Traceback (most recent call last): File "<pyshell#638>", line 1, in <module> func(1) File "<pyshell#636>", line 2, in func assert i % 3 == 0, '3의 배수가 아님' AssertionError: 3의 배수가 아님 |
예외를 직접 만들 수 있음 >>> class NotThreeMultipleError(Exception): def __init__(self): super().__init__('3의 배수가 아닙니다.') >>> try raise NotThreeMultipleError except Exception as e: print("error message", e) error message 3의 배수가 아닙니다. |
예외 메세지를 raise 에 붙일 수 있음 >>> class NotThreeMultipleError(Exception): pass >>> try: raise NotThreeMultipleError("3의 배수가 아님") except NotThreeMultipleError as e: print("message :", e) message : 3의 배수가 아님 |
객체(Object) : 파이썬의 모든 데이터. 인스턴스를 포함. 객체는 데이터(변수, 속성)와 코드(메소드)를 포함하는 자료구조 인스턴스(Instance) : 특정 클래스에서 생성된 객체 (특정 클래스의 '사례') 모든 인스턴스는 객체이지만, 모든 객체가 인스턴스는 아님 즉, 인스턴스는 객체의 부분집합 |
클래스에 dictionary 마냥 속성 추가 가능 class MyClass(): pass my_class = MyClass() my_class.name = "eyeballs" print(my_class.name) # eyeballs |
class 생성시 속성 초기화 실행 class MyClass(): def __init__(self, name, age): self.name = name self.age = age def printing(self) print("name : ", self.name, "age : ", self.age) my_class = MyClass("eyeballs", 625) my_class.printing() 여기서 self 는 클래스의 인스턴스(instance) 자신을 참조하는 변수임 __init__ 메소드를 포함한 모든 인스턴스 메서드는 호출될 때 첫 번째 인수로 자동으로 인스턴스 자신이 전달됨 따라서, self를 사용하여 클래스 내부에서 해당 인스턴스의 속성과 메서드에 접근할 수 있음 위에 MyClass 인스턴스에서 printing() 이 실행되면, printing() 의 첫번째 인수(self) 자리에 my_class 인스턴스가 전달됨 그래서 인스턴스(my_class) 의 name 과 age 를 사용할 수 있게 됨 참고로 __init__ 은 생성자가 아니라 단지 초기화 메소드임 왜냐면, __init__ 호출 전에 이미 객체가 만들어지기 때문 |
class Parent(): pass class Child(Parent): pass issubclass(Child, Parent) # True |
super() 를 사용하여 부모의 메소드를 이용하면, 부모 클래스 레벨에서 작업이 이루어짐 >>> class Parent(): def __init__(self, name): self.parent_name = name >>> class Child(Parent): def __init__(self, name, age): super().__init__(name) self.child_age = age >>> child = Child("eyeballs", 625) >>> dir(child) ['__class__', '__delattr__', '__dict__', ....... 'child_age', 'parent_name'] |
클래스가 갖고 있지 않은 메소드 혹은 속성을 참조하면 python 은 모든 부모 클래스를 다 조사함 만약 다중 상속을 받은 경우라면, 상속받은 순서대로 조사함 >>> class High(): def name(self): return "High" >>> class Middle1(High): def name(self): return "Middle1" >>> class Middle2(High): def name(self): return "Middle2" >>> class Low1(Middle1, Middle2): pass >>> Low1().name() 'Middle1' # Middle1 이 먼저 상속되었기 때문에 Middle1의 name() 이 호출됨 >>> class Low2(Middle2, Middle1): pass >>> Low2().name() 'Middle2' # Middle2 가 먼저 상속되었기 때문에 Middle2의 name() 이 호출됨 |
python 에는 class 의 속성에 접근하지 못하게 막는 private 접근지시어 등은 없음 대신, 속성 이름을 다른 이름으로 가려서 접근을 우회하여 막는 방법이 있음 class MyClass(): def __init__(self, name): self.private_name = name def getter(self): return self.private_name def setter(self, name): self.private_name = name public_name = property(getter, setter) my_class =MyClass("eyeballs") print(my_class.public_name) # MyClass 를 사용하는 시점에서 private_name 대신 public_name 을 사용함 my_class.public_name = "w" print(my_class.public_name) propert 에 getter 와 setter 를 주고 public_name 을 설정함으로써 private_name 에 접근하지 못하도록 함 (물론 "private_name" 이라는 키워드를 (dir 등으로) 알고 있는 개발자라면 접근 가능함.....) |
class 내의 @property 는 '계산된 값'에 접근하도록 돕기도 함 class MyClass(): def __init__(self, name): self.name = name @property def name_length(self): return len(self.name) my_class =MyClass("eyeballs") print(my_class.name_length) # name_length 는 메소드지만, 마치 변수에 접근하는 것 마냥 접근함 8 참고로 property 로 설정된 속성은 read-only 가 됨. 아래처럼 수정하려면, setter 를 설정해야 함 >>> my_class.name_length=1 Traceback (most recent call last): File "py.py", line 11, in <module> my_class.name_length = 1 AttributeError: can't set attribute |
property 를 통해 속성 이름을 다른 것으로 바꾸는 방법 외에 dunder (double underbar) 를 통해 이름을 다르게 바꿀 수 있음 >>> class MyClass(): def __init__(self, name): self.__name = name # 이름 앞에 dunder 를 붙이면, 인스턴스에서 __name 으로 접근이 불가능 >>> my_class = MyClass("eyeballs") >>> dir( my_class ) ['_MyClass__name', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__'] __name 대신 _MyClass__name 이 새로 생김 __name 에 접근하고 싶으면 getter, setter를 사용하던가, 아니면 (숨겨진 이름의) _MyClass__name 을 사용 이렇게 변수나 메소드의 이름을 컴파일 단계에서 일정한 규칙을 통해 바꾸는 것을 맹글링이라고 함 |
class method 는 인스턴스가 아닌 클래스 자기 자신 전체에 영향을 미치는 메소드임 (인스턴스가 아니라) 클래스 속성을 변경하거나 새로운 객체를 생성하는 용도로 사용됨 @classmethod 데코레이터를 추가하여 class method 생성 가능 class MyClass(): count = 0 def __init__(self): MyClass.count += 1 @classmethod def get_count(cls): # self 대신 cls 를 사용. 즉, class 자기 자신을 가리키며 참조함 return cls.count a = MyClass() b = MyClass() print(MyClass.get_count()) #2. 클래스를 통해 호출 print(b.get_count()) #2. 인스턴스를 통해 호출 self 는 클래스의 인스턴스 자신을 가리키는 변수이며 cls 는 클래스 자기 자신을 가리키는 변수임 |
class method 를 통해 인스턴스를 찍어내는 팩토리 패턴 구현 가능 class MyClass(): def __init__(self, name, age): self.name = name self.age = age @classmethod def get_instance(cls, name, birth_year): return cls(name, 2025-birth_year) # cls 를 이용하여 인스턴스를 생성한 후 반환 a = MyClass.get_instance("eyeballs", 1991) b = MyClass.get_instance("w", 1965) print(a.name, a.age) print(b.name, b.age) |
static method 는 클래스에서 곧바로 호출 가능한 메소드. 편의(유틸리티)를 위해 존재함 @staticmethod 데커레이터를 사용하여 정의 >>> class MyClass(): @staticmethod def read_me(): print("please read this first") >>> MyClass.read_me() please read this first |
타입을 미리 정하는게 아니라 실행이 되었을 때 해당 Method들을 확인하여 타입을 정하는 것을 '덕타이핑'이라고 함 def func(obj): print(obj.name()) class A: def name(self): return self.__class__.__name__ class B: def name(self): return self.__class__.__name__ func(A()) # 'A' 출력. func(B()) # 'B' 출력. func 메소드의 obj 에 뭐가 들어오던간에 name() 이라는 메소드가 있다면 실행하게 됨. 그것도 런타임에. |
dataclass 를 통해, 클래스에 데이터(속성)를 직관적으로 지정하여 설정할 수 있음 아래 두 가지 class 는 동일하게 name 속성을 갖는 class 임 class MyClass(): def __init__(self, name): self.name = name from dataclasses import dataclass @dataclass class MyDataClass(): name : str print(MyClass("eyeballs").name) # eyeballs print(MyDataClass("eyeballs").name) # eyeballs |
>>> from dataclasses import dataclass >>> @dataclass class MyDataClass(): name: str age: int = 30 >>> MyDataClass("A", 20) MyDataClass(name='A', age=20) >>> MyDataClass(20, "A") # 놀랍게도, 문법적으로 허용 됨. 왜냐면 파이썬은 동적 언어라서, 런타임에 실제 타입을 검사하거나 변환하지 않음. 위에 name: str 에서 str 은 단지 개발자한테 '이렇게 넣으셈' 하는 힌트일뿐이고, 문법적인 강제는 없음 MyDataClass(name=20, age='A') >>> MyDataClass(name="A", age=20) MyDataClass(name='A', age=20) >>> MyDataClass(age=20, name="A") MyDataClass(name='A', age=20) >>> MyDataClass("A") # age 생략, default 로 설정한 30이 대신 사용됨 MyDataClass(name='A', age=30) |
타입을 강제하고 싶다면, __post_init__ 에서 체커를 추가함 __post_init__ 은 dataclass 의 __init__ 메서드가 호출된 후 자동으로 실행되는 메서드(__init__ 이 __post_init__ 을 호출함) from dataclasses import dataclass @dataclass class MyDataClass: name: str def __post_init__(self): if not isinstance(self.name, str): raise TypeError(f"Expected str, got {type(self.name).__name__}") MyDataClass(name="A") MyDataClass(name=20) # exception |
데이터 클래스는 매직메소드(duner 가 들어가는 메소드들)를 자동으로 만들어주기도 함 예를 들어, __init__, __repr__, __eq__ 같은 메소드들을 자동으로 만들어 줌 위에서 __init__ 없이 데이터 클래스를 정의할 수 있었던 것도 다 이런 이유에서였음 >>> class A: def __init__(self, name): self.name = name >>> @dataclass class B: name : str >>> print(A("eyeballs")) <__main__.A object at 0x01562D00> # __repr__ 가 존재하지 않아, A 클래스의 메모리값을 대신 반환 >>> print(B("eyeballs")) # dataclass 가 __repr__ 를 대신 생성해주어, B 의 name 을 포함하여 반환 B(name='eyeballs') |
mymodule.py 과 mycode.py 가 한 dir에 존재하는 경우 다음과 같이 바로 import 가능 < mymodule.py > from random import choice mylist = [1,2,3,4,5] def pick(): return choice(mylist) < mycode.py > import mymodule print(mymodule.pick()) 혹은 < mycode.py > from mymodule import pick print(pick()) 혹은 < mycode.py > from mymodule import pick as p print(p()) |
"mypackage" 라는 dir 안에 mymodule.py 과 mymodule2.py 를 넣어둠 mycode.py 는 mypackage dir 와 동일한 위치에 존재함 < mypackage/mymodule.py > from random import choice mylist = [1,2,3,4,5] def pick(): return choice(mylist) < mypackage/mymodule2.py > from random import choice mylist = [6,7,8,9,10] def pick(): return choice(mylist) mypackage dir 안에 있는 mymodule.py, mymodule2.py 를 아래와 같이 from, import 로 나눠 불러올 수 있음 < mycode.py > from mypackage import mymodule, mymodule2 print(mymodule.pick()) print(mymodule2.pick()) 혹은 from mypackage.mymodule import pick from mypackage import mymodule2 print(pick()) print(mymodule2.pick()) |
위와 같이 하위 dir 에서 module 을 불러올 때 from, import 를 사용함 그럼 from random 은 어디서 불러올까? 이 모듈 파일은, 현재 작업중인 dir 이내에 없기 때문에, python 이 다른 위치에서 해당 module 을 불러옴 그 "다른 위치"라는 곳은 아래와 같이 확인 가능 >>> import sys >>> for p in sys.path: print("\"",p,"\"") " " "C:\Users\EYE\Python\" "C:\Users\EYE\Python\Python38-32\Lib\idlelib" "C:\Users\EYE\Python\Python38-32\python38.zip" "C:\Users\EYE\Python\Python38-32\DLLs" "C:\Users\EYE\Python\Python38-32\lib" "C:\Users\EYE\Python\Python38-32" "C:\Users\EYE\Python\Python38-32\lib\site-packages" (현재 윈도우에서 작업중이라 위와 같은 경로들이 나타남) 가장 먼저 path 에 나타나는 것이 빈 문자열(" ") 임 이 말은, python 실행시 현재 dir 를 기준으로 먼저 module 을 찾는다는 의미임 임의의 path 를 추가하려면 아래와 같이 실행 import sys sys.path.insert(0, "C:\Users\EYE\my\python\module\path") # 0순위로 찾게 됨 |
module 경로를 상대적으로 넣어줄 수 있음 예를 들어 mycode.py 와 동일한 dir 위치에 있는 mymodule.py 을 불러오려면 < mycode.py > from . import mymodule ... mycode.py 보다 상위 dir 위치에 있는 mymodule.py 을 불러오려면 < mycode.py > from .. import mymodule ... mycode.py 보다 상위 dir 위치의 mypackage 에 있는 mymodule.py 을 불러오려면 < mycode.py > from ..mypackage import mymodule ... |
놀랍게도, import 한 module 의 값을 직접 업데이트 할 수 있음 module 을 import 한 프로그램에 module 의 사본이 생성된다고 이해하면 됨 다시 import 해도 동일한 업데이트가 반영되며, 나중에 다른 프로그램에서 동일한 module 을 import 하면 그에 맞춰 새로운 사본이 생성되기 때문에 pi=3 으로 업데이트 한 내용이 다른 프로그램에 영향을 미치지 않음 >>> import math >>> math.pi 3.141592653589793 >>> math.pi = 3 >>> math.pi 3 >>> import math >>> math.pi 3 |
Deque 는 스택과 큐의 기능을 모두 갖고 있음 즉, 양쪽으로 pop 이 가능함 >>> from collections import deque >>> dq = deque([1,2,3,4,5]) >>> print(dq.pop()) # 가장 마지막에서 pop 5 >>> print(dq) deque([1, 2, 3, 4]) >>> print(dq.popleft()) # 가장 처음에서 pop 1 >>> print(dq) deque([2, 3, 4]) |
여러 시퀀스들을 차례대로 순회하기 위해 itertools.chain 을 사용 >>> import itertools >>> for item in itertools.chain([1], [2,3], (4,5,6)): # 3개의 다양한 시퀀스를 넣음 print(item) 1 2 3 4 5 6 |
하나의 시퀀스를 순회하며 누적 계산을 하기 위해 itertools.accumulate 를 사용 >>> import itertools >>> for item in itertools.accumulate([1,2,3,4]): print(item) 1 3 6 10 기본적으로 누적 합계를 계산함 합계가 아닌 다른 누적 계산을 진행하려면, def 를 추가로 넣어주면 됨 >>> def mul(a,b): return a*b >>> for item in itertools.accumulate([1,2,3,4], mul): print(item) 1 2 6 24 |
from pprint import pprint pprint 는 일반 print 보다 훨씬 가독성 좋게 출력해줌 dictionary 를 출력하면 정렬도 해 줌.... |
python 은 pip 를 통해 PyPI(Python package index. https://pypi.org) 로부터 패키지를 다운받아 설치할 수 있음 "패키지를 설치"한다는 것의 의미는, 해당 패키지의 코드와 관련 종속성(dependencies)을 Python 환경에 다운로드하고 적절한 위치에 배치하여 사용할 수 있도록 등록하는 과정을 의미 여기서 말하는 '적절한 위치'란, site-packages(Python의 라이브러리 디렉터리)를 의미함. 이 site-packages 에 패키지 파일을 복사함 패키지 설치 이후에 python 스크립트 내에서 import 를 통해 패키지 및 모듈 사용이 가능하게 됨 패키지 파일 위치는 아래와 같이 확인 가능 import requests print(requests.__file__) # 패키지의 실제 경로 출력 일반적으로 pip 로 설치한 패키지는 모든 python 프로젝트에서 사용 가능함 (global installation) 어느 특정 python 프로젝트 에서만 사용 가능하도록 패키지를 설치하려면 venv 또는 conda 같은 가상 환경을 사용해야 함 |
pip list # 설치된 패키지 목록 확인 pip show requests # 특정 패키지 정보 확인 pip 로 패키지 설치하기 pip install flask pip install flask==0.9.0 pip -r installations.txt # 해당 txt 파일에는 패키지 이름들이 개행 간격으로 적혀있고, txt 파일 내 모든 패키지가 설치됨 pip install --upgrade # 설치된 모든 패키지를 최신 패키지로 업그레이드 pip uninstall requests # 패키지 삭제 |
Python의 가상환경은 독립적인 실행 환경을 만들어서 각 프로젝트마다 별도의 패키지 및 Python 버전을 관리할 수 있도록 도와주는 기능 global package 와 다른 버전을 사용해야 할 때 사용되며, venv, virtualenv, conda 등이 있음 |
< venv (Python 내장 가상환경) > Python 3.3 이상에서 기본 제공되는 가상환경 도구 가상 환경 생성 명령어 : python -m venv my_env my_env 라는 dir 가 생성되고, 이 dir 내에 가상환경 관련 파일이 저장됨 해당 프로젝트에서 설치한 패키지들이 이 my_env dir 에 설치됨 가상환경 활성 명령어 : source my_env/bin/activate 가상환경 비활성 명령어 : deactivate 가상환경 제거 명령어 : rm -rf my_env (그냥 dir 를 지우는 거네) |
< virtualenv (확장 기능이 있는 가상환경) > venv보다 더 다양한 기능 제공함 Python 2.x 및 3.x 모두 지원하며, 하나의 가상환경에서 여러 Python 버전 사용 가능. virtualenv 설치 명령어 : pip install virtualenv 가상환경 생성 명령어 : virtualenv my_env 특정 python 버전으로 가상환경 생성하는 명령어 : virtualenv -p /usr/bin/python3.8 my_env 가상환경 활성 명령어 : source my_env/bin/activate 가상환경 비활성 명령어 : deactivate |
< conda (데이터 과학 및 패키지 관리 특화) > 데이터 과학, 머신러닝, 딥러닝 등에 최적화된 가상환경을 제공함 pip와 달리 Python 패키지뿐만 아니라 비(非)Python 패키지도 설치 가능 (예: numpy, tensorflow, R). virtualenv 와 동일하게, 여러 Python 버전 사용 가능. conda 를 통해 가상환경을 설정하려면, Anaconda 를 설치해야 함 설치 후 anaconda 가 제공하는 명령어를 통해 가상환경을 구축할 수 있음 특정 python 버전으로 가상환경 생성하는 명령어 : conda create --name my_env python=3.8 가상환경 활성 명령어 : conda activate my_env 가상환경 비활성 명령어 : conda deactivate 가상환경 제거 명령어 : conda remove --name my_env --all |
주피터 노트북 설치 명령어 : pip install jupyter 주피터 노트북 실행 명령어 : jupyter notebook 주피터lab 설치 명령어 : pip install jupyterlab 주피터lab 실행 명령어 : jupyter lab |
python unittest 사용 예제 첫 글자를 대문자로 바꾸는 함수를 테스트 할 예정 import unittest def func(text): if text is None : return text try: if i := int(text) : return text except: pass return text.capitalize() class Test(unittest.TestCase): def setUp(self): pass # 테스트가 진행되기 전 실행되는 메소드 def tearDown(self): pass # 테스트가 마무리 된 후 실행되는 메소드 def test1(self): text = "eyeballs" result = func(text) self.assertEqual(result, "Eyeballs") def test2(self): text = "hi eyeballs" result = func(text) self.assertEqual(result, "Hi eyeballs") def test3(self): text = 123 result = func(text) self.assertEqual(result, 123) def test4(self): text = None result = func(text) self.assertEqual(result, None) def test5(self): text = True result = func(text) self.assertEqual(result, True) if __name__ == '__main__' : unittest.main() |
테스트 성공 C:\Users\EYE\Desktop\python>python mycode.py ..... ---------------------------------------------------------------------- Ran 5 tests in 0.001s OK |
테스트 실패 C:\Users\EYE\Desktop\python>python mycode.py .F..F ====================================================================== FAIL: test2 (__main__.Test) ---------------------------------------------------------------------- Traceback (most recent call last): File "mycode.py", line 24, in test2 self.assertEqual(result, "Hi Eyeballs") AssertionError: 'Hi eyeballs' != 'Hi Eyeballs' - Hi eyeballs ? ^ + Hi Eyeballs ? ^ ====================================================================== FAIL: test5 (__main__.Test) ---------------------------------------------------------------------- Traceback (most recent call last): File "mycode.py", line 39, in test5 self.assertEqual(result, None) AssertionError: True != None ---------------------------------------------------------------------- Ran 5 tests in 0.003s FAILED (failures=2) |
로깅 모듈 사용하여 로깅할 때 필요한 개념들 메세지 : 로그 메세지 레벨 : 로깅의 심각한 정도 debug, info, warning, error, critical 로거 logger : 모듈과 연결되는 하나 이상의 객체 핸들러 handler : 터미널, 파일, DB 등으로 메세지를 전달하는 역할 포매터 formatter : 결과를 생성 필터 filter : 입력 기반으로 판단 import logging logging.debug("debug") logging.info("info") logging.warning("warning") logging.error("error") logging.critical("critical") 출력 결과 WARNING:root:warning ERROR:root:error CRITICAL:root:critical 기본적으로 debug, info 는 결과를 출력하지 않고 warning, error, critical 만 출력함 basicConfig 를 통해 기본 level 을 debug 로 지정하면, 이후부터 debug 부터 critical 까지 전부 출력 logging.basicConfig(level=logging.DEBUG) 출력 결과 DEBUG:root:debug INFO:root:info WARNING:root:warning ERROR:root:error CRITICAL:root:critical |
로거를 설정해두면, 어떤 위치에서 어떤 로거에 의해 출력되었는지 따로 구분할 수 있음 import logging logging.basicConfig(level=logging.DEBUG) A_logger = logging.getLogger('A') B_logger = logging.getLogger('B') A_logger.warning("warning") B_logger.critical("critical") 출력 결과 WARNING:A:warning CRITICAL:B:critical |
basicConfig 에 filename 을 추가하면, 로그를 stdout 이 아니라 file 로 저장할 수 있음 import logging logging.basicConfig(level=logging.DEBUG, filename='warning.log') logging.warning("warning") 프로그램을 실행한 위치에 warning.log 가 생성됨 |
basicConfig 에 format 을 추가하면, 로그의 포맷을 변경할 수 있음 import logging fmt = '%(asctime)s %(levelname)s %(lineno)s %(message)s' logging.basicConfig(level=logging.DEBUG, format=fmt) logging.warning("warning") 출력 결과 2025-01-30 21:24:58,157 WARNING 4 warning |
Python의 Global Interpreter Lock (GIL) 멀티스레드 환경에서 한 번에 하나의 스레드만 Python 바이트코드를 실행할 수 있도록 하는 메커니즘 Python 은 분명 멀티스레드를 지원하지만, GIL 에 의해 동시에 두 개 이상의 스레드가 Python 코드 실행을 병렬로 수행할 수 없음 GIL 은 여러 스레드가 하나의 자원을 수정하면 발생하는 race condition 을 방지하기 위해 존재하며, 여러 스레드가 동시에 실행될 시 GC 실행이 불안정해지는 것을 방지하기 위함 (메모리 관리 안정성을 보장) (이 GC 는 순환 참조 문제를 해결하기 위해 좀 더 복잡한 기법을 사용하는 GC라고 함...) |
< CPU bound 작업 실행시 > GIL은 멀티코어 CPU의 성능을 제대로 활용하지 못하게 만듦 실제로 멀티스레딩을 사용해도 실제 성능 향상이 거의 없음. GIL이 있기 때문에 여러 개의 CPU 코어를 제대로 활용할 수 없음 따라서 멀티프로세싱(multiprocessing) 대신 사용하거나, GIL 을 지원하지 않는 Numpy, Pandas, 혹은 PyPy 를 사용 |
< IO bound 작업 실행시 > GIL은 입출력(I/O) 중심 작업에서는 큰 영향을 주지 않음 Python은 GIL을 사용하지만, I/O 작업을 수행하는 동안에는 GIL을 자동으로 해제함. 따라서 멀티스레딩이 성능 향상에 도움이 될 수 있음. |
python 을 통해 새로운 process 를 실행하여 shell 명령어를 실행할 수 있음. (shell 명령어를 실행한다는 의미는, process 를 하나 실행한다는 의미가 됨) import subprocess result = subprocess.getoutput('ls -laht | grep 2025 | wc -l') # shell 명령어를 수행 후 stdout/stderr 결과를 반환 result = subprocess.getstatusoutput('ls -laht') # shell 명령어 수행 후 stdout/stderr 결과와 상태 코드를 튜플로 반환 result = subprocess.call('date') # shell 명령어 수행 후 결과의 상태 코드(0(성공) 혹은 0 외의 값)만 반환 result = subprocess.call(['date', '-u']) # shell 명령어를 수행 후 stdout/stderr 결과를 반환 result = subprocess.call('date -u', shell=True) # shell 명령어를 수행 후 stdout/stderr 결과를 반환 os 를 통해서도 shell 명령어 실행 가능 import os result = os.system("ls -laht") # shell 명령어 수행 후 결과의 상태 코드(0(성공) 혹은 0 외의 값)만 반환 |
python multiprocessing 예제 import multiprocessing import os def target_func(name): # multiprocessing 으로 처리 될 함수 print(f"process id : {os.getpid()} name : {name}") def run_multi_process(): target_func("main process") p1 = multiprocessing.Process(target=target_func, args=('first multiprocess',)) # args 는 튜플로 넣어줘야 함 p1.start() print(f"p1) process id : {p1.pid} name : {p1.name}") p2 = multiprocessing.Process(target=target_func, args=('second multiprocess',)) p2.start() print(f"p2) process id : {p2.pid} name : {p2.name}") p3 = multiprocessing.Process(target=target_func, args=('third multiprocess',)) p3.start() print(f"p3) process id : {p3.pid} name : {p3.name}") if __name__=='__main__': run_multi_process() 결과 process id : 16664 name : main process p1) process id : 10564 name : Process-1 p2) process id : 11740 name : Process-2 p3) process id : 9108 name : Process-3 process id : 10564 name : first multiprocess process id : 11740 name : second multiprocess process id : 9108 name : third multiprocess 위에 p1.pid 와 (target_func 내의) os.getpid() 가 동일한 것을 확인할 수 있음 즉, target_func 내에서 실행되는 명령어들은 모두 sub process 에서 동작함 multiprocessing.process 를 종료하려면 아래와 같이 terminate 실행 p1.terminate() p2.terminate() p3.terminate() |
python multi threading 예제 import threading import os def target_func(name): print(f"process id : {os.getpid()} name : {name}") def run_multi_thread(): target_func("main process") t1 = threading.Thread(target=target_func, args=('first thread',)) t1.start() print(f"t1) thread ident : {t1.ident} native_id : {t1.native_id} name : {t1.name} ") t2 = threading.Thread(target=target_func, args=('second thread',)) t2.start() print(f"t2) thread ident : {t2.ident} native_id : {t2.native_id} name : {t2.name}") t3 = threading.Thread(target=target_func, args=('third thread',)) t3.start() print(f"t3) thread ident : {t3.ident} native_id : {t3.native_id} name : {t3.name}") if __name__=='__main__': run_multi_thread() 결과 process id : 8900 name : main process process id : 8900 name : first thread t1) thread ident : 19272 native_id : 19272 name : Thread-1 process id : 8900 name : second thread t2) thread ident : 13024 native_id : 13024 name : Thread-2 process id : 8900 name : third thread t3) thread ident : 364 native_id : 364 name : Thread-3 process id 는 모두 동일한 것을 확인 ident 는 python 내부에서 관리하는 thread 의 고유 id 이며, os 레벨에서 관리할 수 없음 native_id 는 OS 가 관리하는 실제 thread 의 고유 id 이며, os 의 프로세스 관리도구(ps 등)로 확인 가능 |
'Python3' 카테고리의 다른 글
[Python] pyenv 설치 방법 (0) | 2022.08.10 |
---|---|
[Python] 내장함수, 외장함수 공식 문서 (0) | 2022.06.16 |
[Python] 문자열의 중간 데이터 제거 코드 (0) | 2021.09.06 |
[Python] 공부할 때 참고한 곳 (0) | 2021.05.16 |
[PySpark] 문법 예제 : expr (0) | 2021.05.05 |