advanced python 1.0

chance·2020년 7월 7일
1

python

목록 보기
1/1

파이썬은 대학 수업 또는 여러 경로를 통하여 비전공자/전공자를 불문하고 배우는 언어가 되었습니다. 그러나, 가볍게 다뤄져서 그런지 심화 지식은 소홀히 하고 넘어가는 경우가 많은 것 같습니다. 알고리즘 문제를 풀기 전에 기초를 다지기 위해 제가 한번 정리해 보았습니다.

OOP in python

커스터마이징 할 수 있는 데이터 타입을 만들기 위하여 class를 만들고, 생성자를 호출하여 인스턴스를 생성합니다. 기본적인 방식은 Java, C++ 등의 class-based oop를 채택한 언어들과 같습니다.

sample code

class Car:
  def __init__(self, size, color = 'black', capacity = 1):
    self.size = size
    self.color = color
    self.capacity = capacity

myCar = Car(50,'navy', 10)
  • 내부적인 객체 생성 과정:

    • 오브젝트 생성자(Car(...))가 호출되었을 때 __new__ 함수와 ___init___ 함수가 차례로 호출됩니다.
    • __new__는 우리가 정의해준 class를 오브젝트 형태로 인자에 받습니다. 받은 class 오브젝트로 인스턴스를 생성하여 반환합니다. 이를 관례적으로 cls라 부르고 있습니다. __new__ 함수의 경우 특별한 경우가 아니면 우리가 직접 정의하지 않습니다.
    • __init____new__에서 생성된 인스턴스와 constructor(Car(50,'navy', 10))의 인자를 조합하여 새로운 인자 목록을 만듭니다. 첫번째 인자로 인스턴스가 들어오고, 생성자에 있던 인자들이 차례대로 나머지 인자에 들어갑니다. 함수 내부에서는 인스턴스의 멤버를 초기화해줍니다. 첫번째 인자로 들어오는 인스턴스를 관례적으로 self라 부르고 있습니다.

    팁: 함수의 인자 정의 부분()에 할당문을 넣으면 해당 값이 정의가 되지 않았을 때 default value를 할당하여 실행합니다.

is vs ==

python에는 독특하게도 is라는 연산자가 있습니다. is 연산자와 == 연산자 모두 두 오브젝트가 같은지 확인하기 위해 쓰입니다. 하지만 동작 방식은 다릅니다.

sample code

empty1 = []
empty2 = []
empty3 = empty1

print(empty1 == empty2)
print(empty1 is empty2)

print(empty1 == empty3)
print(empty1 equals empty3)

empty3 = emtpy3 + empty2
print(empty1 == empty3)

실행결과:

True
False
True
True
True
False
  1. ==는 피연산자의 값을 비교합니다. list가 피연산자이면 안에 요소의 값과 순서가 같은지 확인하고, dictionary가 피연산자이면 key value쌍이 모두 같은지 확인힙니다.

  2. is는 피연산자가 참조하는 오브젝트가 같은지 확인합니다.

출력에 대한 해설:

  • empty1과 empty2가 모두 빈 배열이므로 내용물이 같다. True

  • empty1와 empty2는 서로 다른 리스트 오브젝트를 참조하고 있다. 리스트는 할당될 때마다 새로 성성되는데, 이 때 새로운 메모리 공간에 할당된다. False

  • empty1와 empty3가 모두 빈 배열이므로 내용물이 같다. True

  • empty3에 empty1이 할당되면서 empty1이 참조하는 리스트를 empty3가 똑같이 참조하고 있다. True

  • 리스트의 덧셈 연산으로 새로운 공간에 배열이 생성되고, 이를 empty3가 참조하고 있다. 더해봤짜 모두 빈 배열이므로 내용물이 같다. True

  • 위의 이유로 인해 empty3는 empty1과 다른 공간을 참조하게 된다 False

더 해보기

empty1 = []
empty2 = []
empty3 = empty1

print(empty3 += empty1)

dict1 = { 'a': 1, 'b': 2 }
dict2 = { 'b': 2, 'a': 1 }

print(dict1 == dict2)

이 부분은 지금까지 배운 내용을 바탕으로 스스로 답을 찾아보시면 좋겠습니다!

참고: 커스텀 오브젝트(클래스로 만든 인스턴스)의 경우에는 ==가 is와 동일하게 작동합니다. 즉, 참고하고 있는 곳이 같은지 확인합니다. 사용자가 바꿀 수도 있으니 자세한 내용은 여기 를 참고하세요.

Garbage Collector

garbage collector는 '쓰레기 수집가'라는 이름답게 참조되고 있지 않은 오브젝트(또는 값)을 찾아냅니다.

  • reference counting: python interpreter는 오브젝트가 참조되고 있는 횟수를 셉니다. 이를 reference counting이라고 부르는데, 누구도 오브젝트를 참조하고 있지 않은 순간(reference count이 0)이 되면 python에서 자동으로 해당 오브젝트의 할당을 해제합니다.

  • circular renferences: 둘 이상의 오브젝트가 서로를 참조하고 있는 경우를 말합니다. 오브젝트가 서로를 가리키고 있어서 이 경우에는 항상 reference counting이 1이 넘어가게 되므로, reference counting으로 할당을 해제할 수가 없습니다.
    이런 상황이 많아지게 된다면 메모리 누수가 발생하게 되는데, 파이썬은 이를 위한 이를 감지하고 제거할 수 있는 특별한 알고리즘을 가지고 있다고 합니다. 이를 통해 해당 오브젝트들을 할당 해제합니다.

비교 연산자

여타 다른 언어와 다르게 비교 연산자를 두 번 이상 이어지게 쓸 수 있다. 수학적인 표현인 a < b < c와 같은 표현을 쓴다면 a < b and b < c로 해석된다. 세 개 이상에서도 마찬가지이다.

dicitonary에 대한 질문

dictionary는 자료가 들어온 순서를 기억할까?
정답은 '대게의 경우 그렇다.'
key: value로 구성되는 javascript의 object, java의 HashMap는 대부분의 경우에 python의 dictionary와 대부분의 상황에서 동일한 기능을 수행하겠죠. 하지만 데이터의 순서가 보장되어야 하는 경우는 어떨까요? javascript와 java에서 dicionary의 역할을 하는 친구들은 들어온 순서대로 요소에 접근할 것이라고 보장하지 못합니다. 하지만 dictionary는 python 3.7부터 새로운 구현 방법을 사용하여 순서를 보장한다고 합니다. Python 3.7은 2018년에 배포되었으니, 일반적으로는 순서를 유지한다고 보는 것이 맞겠지요.

(참고: 순서가 보장되는 자료형으로 java에서는 TreeMap, javascript는 Map을 지원합니다.)

iterator vs iterable

iterable

여러개의 element를 포함할 수 있고 요소별로 하나씩 접근할 수 있는 데이터 타입을 지칭합니다. set, list, tuple, dictionary 등이 모두 iterable에 속합니다. iterable object에 속하는 데이터 타입은 __iter__라는 method를 정의하고 있어, 어떠한 방식으로 iterator를 반환하게 됩니다.__iter__가 반환하는 iterator를 통해 방금 말씀드렸던 여러개의 요소를 하나씩 접근하는 행위가 가능합니다. for loop를 실행할 때 iterator에 접근하여 개별적 요소에 대한 접근을 자동적으로 해줍니다.

iterator

iterable에 정의된 data를 꺼내올 수 있는 일종의 stream입니다. __next__라는 함수를 통하여 iterable의 현재 요소 다음의 요소를 꺼내올 수 있습니다. 그런데 여기서 재미있는 점은 iterator도 iterable이 가지고 있던 __iter__ method를 정의하고 있습니다. 따라서 iterator는 일종의 iterable이라고 볼 수 있고, iterable이 들어갈 수 있는 자리에 iterator를 넣어도 대부분 잘 작동합니다. 쓰면 안되는 경우도 있는데, 아직 이해를 잘 못했습니다. 원문에서 iterator에 대한 설명에 exception이 있는데, 이를 보시고 혹시 이해가 되신다면 댓글로 달아주세요!😆

예제

total_barns, total_paths = list(
        map(int, stdin.readline()))
path_list = [sorted(map(int, stdin.readline().strip().split(' ')))
                 for i in range(total_paths)]
print(path_list)

백준에서 문제를 풀다가 본 지식을 알게해준 싱싱한 예제를 들고 왔습니다. 보기가 조금 복잡하니까 핵심만 끌고올게요.

a = list(map(int, ['2','1','3']))
c = list(sorted(a))
print(c)
  • list 함수는 iterable을 인자로 받아서 list를 반환한다고 설명합니다. 원문
  • map 함수는 첫번째 인자로 함수, 두번째 인자로 iterable을 인자로 받아서 iterable의 각각의 요소를 함수에 넣은 반환값을 iterator에 넘어 반환시킵니다. 원문
  • sorted 함수는 iterable을 인자로 받아 정렬한 결과를 리스트로 반환합니다. 여기를 보시면,

Q. 주어진 지식이 어떻게 적용되었는지 말해봅시다.
A. 첫번째 줄: map() 함수는 iterator를 반환값으로 넘기고 iterator는 iterable 자리에 올 수 있으므로 list() 함수는 iterator를 받아 리스트로 만든다.

주어진 코드를 이렇게도 바꿀 수 있겠죠.

a = map(int, ['2','1','3'])
b = list(sorted(a))
print(b)

Q. 무엇이 바뀌었을까요?
A. a를 list로 감싸주는 부분을 제거했습니다. sorted() 함수는 iterable을 인자로 받는데, map은 iterator를 반환합니다. 위에서 iterator는 iterable이 될 수 있다고 했으므로, 정렬된 리스트가 반환되는 평범한 코드입니다.

여기서 잠깐, 선행되야 하는 지식을 간단하게 보고 넘어갈게요.

list comprehension

Q. for - in loop가 뒤로 갔네? 잘못된 코드 아닌가?
파이썬에서는 list comprehension이라 부르는 리스트 생성 방법이 존재하는데, 문법이 다음과 같습니다.

list_name = [expression for varaible_name1 in iterable1 if expression2 for varaible_name2 in iterable2 if expression3 for varaible_name3 in iterable3 ...]

...이라고 표시해 놓은 것처럼 뒤에 for과 if문이 계속 달릴 수 있습니다. 이는 다음과 동일한 의미를 가집니다.

list_name = []
for variable_name1 in iterable1:
  if expression2:
    for variable_name1 in iterable1:
      if expression3:
    	for variable_name1 in iterable1:
          list_name.append(expression)
          ...

else문이 달려도 당황하지 마세요!

list_name = [expression2 if expression1 else expression3 for variable_name in iterable]

이건 다음 코드와 같아요.

list_name = []
for variable_name in itearble:
  if expression1:
    list_name.append(expression2)
  else:
    list_name.append(expression3)

이제 한번 실행 결과를 예측해보세요.

list_name = [i if i == 1 else i + 100 for i in [1,2,3]]
print(list_name)

정답은 [1, 102, 103]!

이제 다시 본론으로 와서, 다음 예제를 봅시다.

Generator

Generator란 list comprehension에서 [] 대신에 ()을 사용한 expression입니다. list comprehension처럼 뒤에 for, if, else가 달려있습니다. list comprehension과는 다르게 반환하는 것이 list가 아닌 iterator입니다. 이 차이를 꼭 숙지하세요!

a = (i for i in range(3))
print(list(a))

list는 a를 인자로 받는데, a는 generator가 반환하는 iterator입니다. iterator는 출력해봤자 데이터가 나가고 들어오는 문이기 때문에 a를 출력해보면 <generator object <genexpr> at 0x035B81E8>와 같이 generator로 생성된 iterator object에 대한 정보만 표시됩니다. 따라서 list의 형태로 보고싶다면 iterator(iterable)을 받아서 list를 반환하는 list 함수를 호출해야 합니다.

심화 예제

마지막으로 좀 어려운 예제입니다. 앞의 개념들이 모두 숙지되어야 이해하기 수월합니다.

a = [list(map(int, ['1', '2', '3'])) for i in range(3)]
print(a)

list comprehension(줄여서 listcomps)를 이용한 예제입니다.
리스트 ['1','2','3']을 생성하고, 이 리스트(iterable)을 map의 인자로 넣어 iterator를 만듭니다. iterator는 iterable이므로 list 함수의 인자가 되어 [1, 2, 3]을 반환합니다. 이것을 for문이 세 번 실행될 동안 반복하므로 실행 결과는 [[1, 2, 3], [1, 2, 3], [1, 2, 3]] 이 됩니다.

from operator import itemgetter

a = (list(map(int, ['1' + str(i),'2'  + str(i),'3'  + str(i)])) for i in range(3))
b = sorted(a, key=itemgetter(0))
print(b)

generator expressions(줄여서 genexps)를 이용한 예제입니다. 앞의 예제와 마찬가지로 map을 이용하여 생성된 iterator를 list에 넣어서 리스트를 만들었으나, 전체를 괄호로 감싸주면서 최종적으로 반환되는 것은 생성된 리스트들에 접근할 수 있는 iterator가 되었습니다. iterator를 sorted는 반환된 iterator(iterable)인 a를 인자로 받아서 정렬된 리스트로 반환합니다. itemgetter를 sorted의 또다른 인자로 넣어줬는데, 배열의 0번째 요소를 기준으로 정렬하려는 의도가 들어있습니다.

generator의 원문

참고

is와 ==의 차이

iterator vs iterable

profile
프론트엔드와 알고리즘을 주로 다룹니다.

0개의 댓글