[ICT인턴십] Python 기초 학습

김유상·2022년 10월 19일
0

ICT인턴십

목록 보기
7/21

Python call 함수의 사용

init 함수는 Class의 생성자를 정의할 때 사용하는 함수이다. 하지만 call함수의 경우에는 Instance에서 바로 호출하는 경우에 사용하는 함수이다.

class Person():
  def __init__(self):
    print('init')
  
  def __call__(self):
    print('call')
      
#클래스이므로 __init__이 호출됨
person = Person()
#인스턴스이므로 __call__이 호출됨
person()

위 예시처럼 Class scope으로 호출하는 경우에는 init, Instance scope으로 호출하는 경우에는 call이 호출된다는 것을 확인할 수 있다. (Java, C, C++에서는 지원하지 않음)

Referenced: https://wjunsea.tistory.com/61

Python 객체 기본 함수

init: 객체 생성자로 Class() 형식으로 객체를 생성할 때 자동으로 호출

getitem: 객체를 생성한 뒤 return 값을 정의할 수 있음

len: 이 함수를 구현함으로써 해당 객체를 len()함수를 이용해 개수를 구할 수 있음

contains: 이 함수를 구현함으로써 in 키워드를 사용할 수 있음

Referenced: https://skeo131.tistory.com/m/75

Python return 과 yield의 차이점

return은 말 그대로 해당 라인에 도착하면 바로 우항에 있는 값을 호출한 곳에 전달하는 역할을 한다.

하지만 yield는 return과 같이 우항에 있는 값을 호출한 곳에 전달하지만 준비가 되는 순서대로 전달한다는 차이점이 있다.

import time

def yield_abc():
  for ch in "ABC":
    time.sleep(1)
    yield ch
    
for ch in return_abc():
  print(ch)    

위와 같은 코드를 실행했을 때는 ABC가 각각 1ms의 시차를 두고 yield에 의해 전달되는데

이렇게 되면 바로 return되지 않고 A가 준비되면 전달 B가 준비되면 전달 C가 준비되면 전달하게 된다.

1ms의 간격을 두고 모든 정보를 순차적으로 전달하는 꼴이 된다. 이러한 특성을 가지는 이유는 yield의 반환 타입 때문인데

<generator object yield_abc at 0x7f4ed03e6040>

위에서 보이는 타입과 같이 제너레이터 타입을 가진다.

제너레이터라는 객체를 반환하기 때문에 값을 그대로 전달하는 return과 달리 모든 값이 준비되기 전에 미리 반환을 할 수 있는 것이다.

이렇게 반환된 제너레이터는 위에서 본 코드와 같이 준비된 결과 값을 차례대로 메모리에 올리기 때문에 lazy iterator라고도 불린다.

이러한 특성은 메모리에 한 번에 올리기에는 부담스럽게 대용량의 파일을 읽거나, 스트림 데이터를 처리할 때 상당히 유용하게 사용될 수 있다.

Referenced: https://www.daleseo.com/python-yield/

Python 사용자 정의 예외

파이썬에서는 다른 프로그래밍 언어와 같이 사용자 정의 예외를 정의할 수 있다.

아래 코드에서 볼 수 있는 NotThreeMultipleError 클래스처럼 자신이 만들고 싶은 예외에 대한 클래스가 Exception 클래스를 상속 받도록 구현하면 된다.

class NotThreeMultipleError(Exception):
    def __init__(self,msg):
        self.msg=msg
    def __str__(self):
        return self.msg
        
def three_multiple():
    x = int(input('3의 배수를 입력하세요: '))
    if x % 3 != 0:                                 # x가 3의 배수가 아니면
        raise NotThreeMultipleError('3의 배수가 아닙니다.')    # 예외를 발생시킴
    print(x)

try:
    three_multiple()
except NotThreeMultipleError as e:                             # 하위 코드 블록에서 예외가 발생해도 실행됨
    print('예외가 발생했습니다.', e)

 위 코드와 같이 raise 키워드를 이용하면 Exception을 일으킬 수 있고 해당 Exception은 자신을 호출한 코드 블록 중 try-except문으로 감싸진 곳까지 타고 올라간다. 그리고 try-except에서 처리할 수 있는 예외인지 확인하고 예외 처리를 수행한다.

Referenced: https://dojang.io/mod/page/view.php?id=2400, https://wikidocs.net/105490

Python 두 리스트의 교집합

  1. for문을 이용한 방법

    def intersection(lst1, lst2):
        lst3 = [value for value in lst1 if value in lst2]
        return lst3
  2. set을 이용한 방법

    def intersection(lst1, lst2):
        return list(set(lst1) & set(lst2))
    
    def Intersection(lst1, lst2):
        return set(lst1).intersection(lst2)
  3. filter를 이용한 방법

    def intersection(lst1, lst2):
        lst3 = [list(filter(lambda x: x in lst1, sublist)) for sublist in lst2]
        return lst3

위 3가지 방법 중 시간 복잡도에서 가장 이득을 볼 수 있는 방법이라고 하면 아마 set을 이용하는 방법이 될 것이다. 하지만 set 자료구조의 경우 데이터의 순서를 보장하지 않기 때문에 순서를 보장하기 위해서 정렬을 사용해야 할 수도 있다.

Referenced: https://www.geeksforgeeks.org/python-intersection-two-lists/

Python JSON 모듈로 Json 파일 읽기

파이썬에서는 json문서를 이용해 file data input을 하고자 할 때 간편하게 사용할 수 있도록 json이라는 모듈을 모듈을 제공하고 있다.

API

loads(): 파일에 저장되어 있는 json 형식을 읽거나, http request의 body를 읽을 때 자주 사용

json_object = json.loads(json_string)

dumps(): json형식으로 작성한 python object를 json 형식으로 변환하기 위해서 사용

#indent값은 json에서 들여쓰기의 칸 수를 나타낸다.
json_string = json.dumps(json_object, indent=2)

load(): json 파일에 저장된 데이터를 읽어서 python object로 불러오고 싶은 경우에 사용

with open('input.json') as f:
    json_object = json.load(f)

dump(): python object를 json 문자로 변환한 결과를 파일에 바로 쓰고 싶은 경우에 사용

with open('output.json', 'w') as f:
    json.dump(json_object, f, indent=2)

Referenced: https://www.daleseo.com/python-json/, https://docs.python.org/3/library/json.html

Python 열거형 타입

파이썬에서는 자바와 마찬가지로 열거형 타입(enum)을 제공한다. (단, python version >= 3.4일 때)

from enum import Enum

class ImageType(Enum):
    ACTUAL = 1
    BOX = 2
    PREDICT = 3

위 코드에서 볼 수 있는 것처럼 enum은 이름과 값을 가지게 되는데
일반적으로 (클래스명.이름)으로 어떤 데이터의 종류를 구분하기 위해 사용한다. 필자는 이미지 파일의 종류가 실제, 바운딩 박스, 전체 모델 이미지를 나누게 되어 이렇게 enum 타입을 사용하게 되었다. 이렇게 열거형 타입을 사용하게 되면 앞으로 이미지의 종류가 늘어나게 될 가능성에 대비할 수 있고 타입의 이름을 변경하고자 할 때에도 리팩터를 이용해 쉽게 바꿀 수 있다.

처음에는 isPredict 변수를 통해 true, false 값으로 Actual과 Predict를 판별하였는데 가독성도 별로고 오류를 찾기도 어려웠다. 하지만 enum으로 바꾼 지금은 너무나 쉽게 코드를 관리할 수 있게 되었다. 다른 사람들도 비슷한 상황에서 열거형 타입을 적극적으로 활용하길 바란다.

Referenced: https://docs.python.org/ko/3/library/enum.html, <https://www.daleseo.com/python-enum

Python 여러 개의 매개변수 사용

파이썬에서는 입력 시에 매개변수의 개수를 정하지 않아도 동적으로 처리할 수 있다. 아래의 코드와 같이 *(Asterisk)를 매개변수 명 앞에 표기함으로써 사용할 수 있다.

self.file_name_list = self.getMatchedImageList(predict_file_name_list, actual_file_name_list, box_file_name_list)

def getMatchedImageList(self, *list_tuple):
    matched_list = [-1]
    for file_name_list in list_tuple:
        temp_list = []
        for file in file_name_list:
            temp_list.append(file.split(FILE_SPLITER, maxsplit=1)[1])
        if matched_list[0] == -1:
            matched_list = temp_list
        else:
            matched_list = list(set(matched_list) & set(temp_list))

    return sorted(matched_list)

이렇게 여러 개의 매개변수들은 파이썬 튜플 자료구조로 정리되어 하나의 값으로 전달된다. 실제 호출받은 함수에서는 튜플을 이용해 인덱스로 개별 전달 값에 접근해야 하며 이는 내부 구조를 알 수 없는 callee의 입장에서 caller에서 전달한 변수를 정확하게 알아야 한다는 단점?이 있지만 너무 많은 매개변수는 객체로 전달해야 한다는 리팩토링의 원리를 적절하게 적용했다고 생각한다.

Referenced: https://chancoding.tistory.com/146

Python 생성자 오버로딩

Java, C++에서는 클래스 생성자 오버로딩 뿐만 아니라 모든 메서드에 대해 오버로딩을 지원한다. 이에 반해 Python은 객체지향 프로그래밍에 필요한 대부분의 개념을 지원하지만 이런 부분에 대해서는 닫혀있는 모습이다. 결론적으로 Python은 오버로딩이라는 개념을 지원하지 않는다.

class Person:
    def __init__(self):
        self.name = None
        self.age = None
        self.gender = None

    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

따라서 위와 같은 클래스의 객체를 생성하면 오류가 발생할 수 있다. 따라서 다른 방법을 사용해야 한다.

  1. 매개변수 기본값

    class Person:
        def __init__(self, name = None, age = None, gender = None):
            self.name = name
            self.age = age
            self.gender = gender

    위 코드와 같이 변경해주면 기존에 발생하던 오류를 피할 수 있다. 하지만 항상 이렇게 단순한 형식의 클래스만 존재하지는 않기 때문에 다양한 방법을 이용해서 생성할 필요가 있다.

  2. if문을 이용해 분기

    class Person:
        def __init__(self, name = None, age = None, gender = None, flag = False):
            if flag == True:
                self.name = "diff name"
                self.age = "20"
                self.gender = "man"
            else:
                self.name = name
                self.age = age
                self.gender = gender

    if문을 이용하면 간단하게 생성 형태를 분기할 수 있지만 flag와 같은 새로운 매개변수가 필요하고 기본 생성자를 알기 쉽게 보기 어렵다는 단점이 있다. 따라서 가능하면 매개변수 기본값을 이용하도록 하자.

  3. @classmethod 데코레이터

    @classmethod
    def from_str(cls, person_from_str) -> 'Person':
    	person = person_from_str.split(',')
    	return cls(name=person[0], age=int(person[1]), gender=bool(int(person[2])))

    실제로 이러한 방식으로 코드를 작성해 본 적은 없는데 별로 좋은 방식이라는 생각은 들지 않는다. 왜냐하면 실제로 처음 보는 코드인데 동작 방식을 한눈에 알기 힘들고 다른 사람이 보더라도 이게 생성자라는 생각이 바로 들 정도의 가독성이 있다고 생각하지 않는다.

Referenced: https://gongmeda.tistory.com/12#1.1.1. 1. 코드의 반복과 추가 분기

Python 파일 또는 폴더 삭제하기

파이썬에서는 os 모듈을 이용해 파일 및 폴더에 대한 대부분의 관리 기능을 제공한다. 대표적으로 삭제 기능을 파일의 경우 os.remove, 폴더의 경우 os.rmdir 함수를 호출함으로써 사용할 수 있다.

file_path = "./somefile"

if os.path.isfile(file_path):
    os.remove(file_path)
folder_path = "./somefolder"

if os.path.isdir(folder_path):
    os.rmdir(folder_path)

더불어 os.path.isfile 과 os.path.isdir과 같이 해당 오브젝트가 파일인지 폴더인지 구분하고 삭제를 할 수 있도록 설정할 수 있다.
이러한 방식을 LBYL(Look Before You Leap)이라고 하며 어떤 기능을 디버깅 해보기 전에 오류가 발생할 만한 코드인지 확인하는 것을 뜻한다.

하지만 파이썬은 EAFP(Easier to Ask Forgiveness than Permission)방식으로 만들어졌으며 일단 코드를 실행해보고 실제 동작하지 않을 경우에 대응한다는 것을 뜻한다. 따라서 위에서 설명한 코드처럼 if문을 통해 파일 및 폴더가 없는 경우에 대한 처리를 해주는 것은 파이썬스럽다고 볼 수 없다. 아래의 코드는 EAFP 방식으로 다시 작성한 코드이다.

try:
    os.remove(file_path)
except FileNotFoundError as e:
    logger.error(e)

솔직히 Java로 먼저 공부한 나로서는 이해하기 힘들지만 개발 철학의 차이라고 생각하고 Python을 작성할 때는 EAFP 방식을 적용해야겠다.

Referenced: 파이썬 클린코딩/ 마리아노 아야나/ p.98

profile
continuous programming

0개의 댓글