https://eyeballs.tistory.com/648
[IT] CS 면접 대비 Python 질문 모음
< First-Class 함수 > First-Class 함수 : 프로그래밍 언어가 함수(Function)를 first-class 시민으로 취급하는 것 함수가 다른 함수의 인자로 전달될 수 있고, 함수의 결과로 리턴될 수 있고, 변수에 함수를 할
eyeballs.tistory.com
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) 언어임 런타임에 타입 검사를 엄격하게 수행 타입 불일치로 인한 오류를 방지함 강타입 언어는 타입 검사를 엄격하게 수행하여 타입 불일치를 허용하지 않음 약타입 언어는 암시적 타입 변환을 통해 서로 다른 타입 간의 연산을 허용 객체개발자 입장에서 알아보기 쉽도록 타입 힌트를 넣어줄 수 있음 예를 들어 def add(a: int, b: int) -> int: return a + b ...근데 이렇게 넣어주는건 진짜 힌트인거라, int 부분이 문법에 적용되진 않는 듯 함 add('a', 'b'), add(True, True) 이렇게 넣어줘도 실행됨 |
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 라고 부름 함수에 인자를 전달할 때, 값 자체를 복사해서 전달하는 것이 아니라, 객체에 대한 참조값을 전달한다는 의미 따라서 함수 내부에서 전달된 객체를 수정하면 원래의 객체에도 영향을 미침 - Call by Value (값에 의한 호출) : 함수에 값을 복사하여 전달. 함수 내부에서 값을 변경해도 원래 변수에는 영향을 주지 않음 - Call by Reference (참조에 의한 호출) : 함수에 변수의 메모리 주소를 전달. 함수 내부에서 값을 변경하면 원래 변수에도 영향을 미침 Call by reference는 메모리 주소를 직접 전달하여 원본 데이터에 대한 직접적인 접근을 허용 - Call by Object Reference (객체 참조에 의한 호출) : 객체에 대한 참조를 함수에 전달. 변경 가능한 객체(예: 리스트, 딕셔너리)의 경우, 함수 내부에서 해당 객체를 수정하면 외부의 원래 객체에도 영향을 미침 불변 객체(예: 숫자, 문자열)의 경우, 함수 내부에서 새로운 객체를 생성하므로 외부 객체에는 영향을 미치지 않음 (call by reference 와 다르게) 객체의 참조(주소가 아닌)를 전달 객체 자체의 변경은 원본에 영향을 주지만, 새로운 객체를 할당하는 것은 원본에 영향을 주지 않음. 아래 자세한 예시 |
변경 가능한 객체를 받는 함수 예 def modify_list(my_list): my_list.append(4) # 리스트 내부 변경 original_list = [1, 2, 3] modify_list(original_list) print(f"{original_list}") # 출력: [1, 2, 3, 4] 함수 내에서 my_list = [1,2,3] 으로 할당한 것과 동일. 위 예제에서 a[0] 에 99를 넣은 게 b 에도 영향을 미친 것과 동일 a[0] = 99 print(a) # [99,2,3] print(b) # [99,2,3] |
불변 객체를 받는 함수 예 def modify_number(num): num = num + 10 # 새로운 숫자를 할당 (불변 객체) original_number = 5 modify_number(original_number) print(f"{original_number}") # 출력: 5 함수 내에서 num = 5 로 할당한 것과 동일. 'num' 은 단순한 포인터이므로, num+10 (새로운 객체) 를 num 에 다시 할당하는 것이 original_number 에 영향을 미치지 않음 |
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(id(a)) # 4382802160 print(id(b)) # 4382802160 print(id(c)) # 4382802160 |
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 를 사용함 즉, 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. 왜냐면 p3 는 불변이라 수정이 불가능하니 새로 생성 이게 왜 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] extend는 + 연산과 기능 동일함 >>> a = [1,2,3] >>> print(a + [4,5]) [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) # 3 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 는 얕은 복사임. 중첩된 list 까지는 복사하지 못 함 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} dictionary 덮어쓰기 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:'one', 2:'two'} a = d.pop(1) # pop 에 'key'값을 넘겨주면, 'value' 를 삭제하고 반환받음 print(a) # 'one' print(d) # {2:'two'} a = d.pop(-99) # exception 발생 a = d.pop(-99, 'Nothing here') # key 가 없는 경우, default 값 반환 print(a) # Nothing here d.pop() # exception 발생. 반드시 'key' 값 넘겨줘야 함 |
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 defaultdict 는 한 마디로 "있는건 주고 없는건 만든다" |
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") a = False if a : print("True") 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] 가 나옴. 왜냐면 인수의 기본값이 유지되기 때문 함수에 리스트 같은 가변 인수가 들어가는 경우엔, 함수 파라미터에 리스트의 참조값이 복사되기 때문에 함수 내부에서 가변 작업한 것이 리스트에 그대로 적용됨 위 call by object reference 부분 참고 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 |
애스터리스크(Asterisk)가 두 개 붙으면, 받은 keyword 값 쌍들을 함수 내부에서 dictionary 로 만들어 받음 즉, 애스터리스크 두 개는 dictionary 로 취급됨 >>> def func(**kwargs): print(kwargs) >>> func() {} >>> func(a="AA", b="BB") {'a': 'AA', 'b': 'BB'} >>> func(c="CC") {'c': 'CC'} # 애스터리스크 사용시 인수의 기본값 유지되지 않음 dict를 직접 넣고싶으면 함수 인자 앞에 더블 애스터리스크 사용 >>> func(**{'a':'a', 'b':'b'}) {'a': 'a', 'b': 'b'} |
단일 애스터리스크(*args 가 아니라 그냥 진짜 하나만 있는 *)는 함수의 위치 기반 인수와 키워드 전용 인수 사이에 넣어, 이 둘을 구분하는 역할을 함 예를 들어 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) 에 있는 변수들을 보려면 globals() 을 실행하여 확인 가능 local scope(namespace) 에 있는 변수들을 locals() 을 실행하여 확인 가능 (dir() 와 비슷한 기능) |
클로저를 사용하면, 내부 데이터를 은닉(캡슐화)할 수 있음 아래 예제에서 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 함수는, 콜론 ( : ) 뒤의 명령어를 실행하고 그 결과를 반환함 (def 에서 반환을 위해 사용하는 'return' 문장이 필요 없음) >>> 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 lambda 에서 pass 를 사용할 수 없음 왜냐면 lmabda 는 반드시 값을 반환해야 하는데, pass 는 반환값이 없기 때문에. lambda : pass # invalid syntax 에러 발생 |
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) - 참조 카운트 : 증가 - 객체 생존 : GC 에 의해 삭제되지 않음 - 활용례 : 일반적인 객체 - 약한 참조 (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 는 예외가 발생하지 않았을 때 실행됨. 반복문 내 break 가 발생하지 않았을 때와 동일하게 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의 배수가 아님 이렇게 코드 내에 바로 assert 를 넣어도 동작하는구나 assert 를 꼭 test 코드 내에서만 사용하라는 법은 없지 |
예외를 직접 만들 수 있음 >>> 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) : 특정 클래스에서 생성된 객체 (특정 클래스의 '사례') 객체와 인스턴스의 차이는 미묘함. 내 생각에 둘이 가리키는 것은 동일하고, 입장차이를 구분하기 위한 것인 듯 함 class 와의 관계 관점에서 설명하면 인스턴스임. |
클래스에 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를 사용하여 클래스 내부에서 해당 인스턴스의 속성과 메서드에 접근할 수 있음 self 는 "메소드의 첫번째 인자", "class 내 속성(변수) 앞" 에 붙어야 함 위에 MyClass 인스턴스에서 printing() 이 실행되면, printing() 의 첫번째 인수(self) 자리에 my_class 인스턴스가 전달됨 그래서 인스턴스(my_class) 의 name 과 age 를 사용할 수 있게 됨 참고로 __init__ 은 생성자가 아니라 단지 초기화 메소드임 왜냐면, __init__ 호출 전에 이미 객체가 만들어지기 때문 실제 생성자는 __new__ 메소드 코드 : class MyClass: def __new__(cls): instance = super().__new__(cls) # 이런식으로 instance 를 만듦 print("__new__ id :", id(instance)) return instance # 만든 instance 를 무조건 return 해줘야 함 def __init__(self): print("__init__ id :", id(self)) MyClass() 결과 : __new__ id : 5275389328 __init__ id : 5275389328 __new__ : 객체 생성 및 메모리 할당을 담당 (최초 호출) __init__ : 생성된 객체의 필드나 초기 상태를 설정함 일반적인 실무에서는 __init__ 을 “생성자”처럼 사용하고, 대부분 이 부분만 오버라이드함 |
__new__ 를 사용하여 singleton class 를 만들 수 있음 아래와 같이 class 내 global scope 에 _instance 를 생성하고 _instance 가 존재하지 않을때 새로 생성하고 _instance 가 존재할 때 _instance 를 반환함 class Singleton: _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) else: pass return cls._instance a = Singleton() b = Singleton() print(a is b) # True |
__new__ 를 조작하여 불변 class 를 만들 수 있음 class Point(tuple): def __new__(cls, x, y): return super().__new__(cls, (x, y)) # (x,y) 를 부모 클래스의 *args 에 넣음. 이렇게 넣은 값은 class 의 속성이 됨 def __init__(self, x, y): pass p = Point(3, 4) print(p) # (3, 4) print(p[0]) # 3 print(isinstance(p, tuple)) # True |
아래처럼 __new__ 를 조작하면 class 인스턴스를 pool 에 새로 넣고, pool 내 인스턴스를 재사용하도록 만들 수 있음. class Connection: _pool = [] def __new__(cls): if cls._pool: print("Reusing object from pool") return cls._pool.pop() else: print("Creating new object") return super().__new__(cls) def close(self): print("Returning to pool") self.__class__._pool.append(self) # 사용 예 conn1 = Connection() # 새로 생성 conn2 = Connection() # 새로 생성 conn1.close() # conn1 을 풀에 반납 conn3 = Connection() # conn1 재사용 print(conn1 is conn3) # True |
class Parent(): pass class Child(Parent): pass issubclass(Child, Parent) # True 아래는 또다른 상속 예제 class Parent(): a = 1 class Child(Parent): b = 2 c = Child() print(c.a) # 1 print(c.b) # 2 |
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) # "w" 가 출력됨 property 에 getter 와 setter 를 주고 public_name 을 설정함으로써 private_name 에 접근하지 못하도록 함 (물론 "private_name" 이라는 키워드를 (dir 등으로) 알고 있는 개발자라면 접근 가능함.....) 여기서 property()는 Python에서 getter/setter 메서드를 속성처럼 사용할 수 있게 해주는 내장함수. 위 예제처럼 직접 getter, setter 설정해서 property 에 넣는 것도 좋지만 더 pythonic 하게 사용하려면 아래처럼 decorator를 사용하는 게 좋음 class MyClass: def __init__(self, name): self._name = name @property def name(self): return self._name # getter @name.setter def name(self, value): self._name = value # setter my_class = MyClass("eyeballs") print(my_class.name) # eyeballs 출력 my_class.name = "w" print(m.name) # w 출력 위에 빨간색 name 부분은 모두 같은 이름이어야 함 저 이름(name)을 기준으로 getter, setter 가 적용됨 함수지만 함수가 아닌 일반 속성(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 는 메소드지만, 마치 변수에 접근하는 것 마냥 접근함 참고로 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") >>> print(my_class._MyClass__name) # 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 을 사용 이렇게 변수나 메소드의 이름을 컴파일 단계에서 일정한 규칙을 통해 바꾸는 것을 맹글링이라고 함 대체 이 기능은 언제 어디에 쓰는거야..? |
@classmethod 데코레이터를 추가하여 "class method" 생성 가능 "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 는 클래스 자기 자신을 가리키는 변수임 dir(MyClass) 를 이용하여 어떤 class method 들이 존재하는지 확인 가능함 |
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) # 인스턴스를 만들지 않은 상태. class method 만 이용해서 인스턴스를 받음 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 class method 에 첫번째 인자로 cls 가 존재함 static method 에 인자가 존재하지 않음 |
타입을 미리 정하는게 아니라 실행이 되었을 때 해당 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 # self 를 이용하여 직접 속성 정의 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 |
데이터 클래스는 매직메소드(dunder 가 들어가는 메소드들)를 자동으로 만들어주기도 함 예를 들어, __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') |
mycode 에서 mymodule 을 import 하는 상황 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 의 값을 직접 업데이트 할 수 있음 >>> import math >>> math.pi 3.141592653589793 >>> math.pi = 3 >>> math.pi 3 >>> import math >>> math.pi 3 module 을 import 한 프로그램에 module 의 사본이 생성된다고 이해하면 됨 다시 import 해도 동일한 업데이트가 반영되며, 나중에 다른 프로그램에서 동일한 module 을 import 하면 그에 맞춰 새로운 사본이 생성되기 때문에 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]) >>> dq.append(100) deque([2, 3, 4, 100]) >>> dq.appendleft(100) deque([100, 2, 3, 4, 100]) |
여러 시퀀스들을 차례대로 순회하기 위해 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 install flask # pip 로 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 사용 예제 첫 글자를 대문자로 바꾸는 함수를 테스트 할 예정 def func(text): # 테스트 할 메소드 if text is None : return text try: if i := int(text) : return text except: pass return text.capitalize() import unittest class Test(unittest.TestCase): def setUp(self): pass # 테스트가 진행되기 전 실행되는 메소드 def tearDown(self): pass # 테스트가 마무리 된 후 실행되는 메소드 def test1(self): # 메소드 이름이 꼭 'test' 로 시작해야 함 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을 자동으로 해제함 (IO 작업에도 race condition 있을텐데 왜 해제하는거지..?) 따라서 멀티스레딩이 성능 향상에 도움이 될 수 있음. |
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 등)로 확인 가능 |
index 를 추가하여 for 문을 도는 방법으로 for i in range(...) 를 사용하는데, enumerate 를 사용한다면, 굳이 range 를 사용하지 않아도 index 추가하여 for 문을 돌 수 있음 enumerate 를 사용하여 for 문을 돌면, 결과로 index 와 element 가 묶여져서 나옴 - for n in enumerate([321, 523, 447]): print(n) 결과) (0, 321) (1, 523) (2, 447) - for index, n in enumerate([321, 523, 447]): print(index, n) 결과) 0, 321 1, 523 2, 447 - for n in enumerate([321, 523, 447], start = 7): print(n) 결과) (7, 321) (8, 523) (9, 447) |
'Python3' 카테고리의 다른 글
| [IT] CS 면접 대비 Python 질문 모음 (0) | 2024.02.17 |
|---|---|
| [Python] pyenv 설치 방법 (0) | 2022.08.10 |
| [Python] 내장함수, 외장함수 공식 문서 (0) | 2022.06.16 |
| [Python] 문자열의 중간 데이터 제거 코드 (0) | 2021.09.06 |
| [Python] 공부할 때 참고한 곳 (0) | 2021.05.16 |