본문 바로가기

문돌이 존버/프로그래밍 스터디

File I/O, 객체 지향 프로그래밍 비전공자 문돌이가 설명하는 파이썬(Python) 기본 문법 (3)

반응형

1. File I/O란?

File I/O = 파일을 입력하고 출력한다는 뜻으로 I/O는 Input/Output의 약자입니다. 간단히 말해, File I/O는 이미 존재하는 데이터 파일을 파이썬에서 읽고, 쓰는 것을 말합니다.

file = open('example.txt', 'r')
contents = file.read() # 파일 내용을 문자열로 만들고, 그대로 리턴
file.close() # 열었던 파일은 반드시 닫아주자! 

print(contents)
My first example...

결과값은 example.txt 에 무엇이 적혀 있는지에 따라 다르겠지만, 결국 해당 파일에 있는 문장을 모두 읽어올 것입니다. 

위의 코드에서 file.close() 가 귀찮다면 아래처럼 with ~ as ~ 를 사용하셔도 됩니다. 사실 아래 방법을 더 선호한다고 합니다. 

with open('example.txt', 'r') as file: # with 블록이 close() 역할을 하게 됨
    contents = file.read()

print(contents)
My first example...

참고로 파일을 열 때 선택할 수 있는 옵션은 'r' - reading / 'w' - writing / 'a' - appending 입니다. 

파일을 불러올 때 파일이 위치한 디렉토리(경로)를 파악하는 것이 매우 중요합니다. 

import os
os.getcwd() # 현재 디렉토리 경로 
os.chdir('/User/user/env/') # 디렉토리 변경
os.chdir('../') # 바로 이전 디렉토리로 변경

# ./ -> 현재 위치한 디렉토리에 practice라는 폴더가 있고, 해당 폴더에 example.txt가 있는 경우
with open('./practice/example.txt', 'r') as file:
    contents = file.read()

파일에서 읽어올 문자 개수도 정할 수 있는데요, 보통 이렇게 해야할 경우는 극히 드물어서 궁금한 분들은 한 번 시도해볼만 합니다. 첫 번째 결과물이 My인 경우는 띄어쓰기가 문자 개수에 포함되기 때문입니다. 두 번째 contents2는 contents 이후의 문자를 읽어오는데, 이때 커서를 생각해보면 쉽습니다. 문자 3개까지 읽고, 커서가 해당 위치에서 쉬고 있으면서 다시 2개를 읽으라고 하니 처음부터 시작하는 것이 아니라 위치한 곳에서 문자를 읽기 시작하는 것입니다. 

(주의!) 파일 커서는 절대 뒤로 돌아가지 않습니다. 문자 3개까지 읽었으면 그 다음 문자에 커서가 깜빡이고 있으며, 뒤로 가려면 처음부터 다시 파일을 열어야 합니다.

with open('example.txt', 'r') as file:
    contents = file.read(3) # 읽어오는 문자 3개
    contents2 = file.read(2) # 읽어오는 문자 2개
    
print(contents)
print(contents2)
My 
fi

줄 단위로 읽어서 리스트로 출력하는 방법도 있습니다. 각 줄에 있는 문장은 문자열로 리턴하지만 이를 모아서 리스트로 출력해줍니다. 

with open('example.txt', 'r') as file:
    lines = file.readlines()
['My first example...', 'My second example...', 'My third example...']

아래는 위에서 배운 기본적인 것을 바탕으로 응용한 버전입니다. 인터넷에서 긁어온 파일은 저작권 문제로 #로 시작하는 문장이 있는 경우가 많습니다. 이런 (쓸데없는?) 문장을 제외한 실질적인 문장만을 출력하고 싶다면 아래와 같이 코드를 작성할 수 있습니다. 

참고
파이썬은 인터넷에서 긁어온 파일 정보를 기본적으로 바이트로 읽어들입니다. 따라서 이를 바이트가 아닌 문자열로 읽게 하려면 아래 코드를 추가해야 합니다.
line.decode("utf-8")
def skip_header(input_file: TextIO) -> str:
    line = input_file.readline()
    while line.startwith('#'): # '#'로 시작하는 문장이라면
        line = input_file.readline() # 다음 문장 읽고 line이란 변수에 저장
    
    # 커서 위치는 다음 다음 문장 맨 앞으로 옮겨진 상황
    return line

def pre_file(input_file: TextIO) -> None:
    line = skip_header(input_file) # '#'로 시작하지 않는 첫 번째 문장
    print(line.strip())
    for line in input_file: # 여기서 input_file은 전체 파일이 아니라 skip_header 이후의 문장부터 읽기 시작
        print(line.strip()) # strip(): 문장 양 옆 띄어쓰기 및 줄바꿈 문자 모두 삭제
        
with open('example.txt', 'r') as file:
    pre_file(file)

위의 코드는 for 문을 사용했는데, 아래처럼 while 문으로 1줄이라도 줄일 수 있습니다. 참고만 해보시길 바랍니다.

def skip_header(input_file: TextIO) -> str:
    line = input_file.readline()

    while line.startwith('#'): 
        line = input_file.readline()
    return line

def pre_file(input_file: TextIO) -> None:
    line = skip_header(input_file) 
    while line:
        print(line.strip())
        line = input_file.readline()
        # line = line.split() -> split(): 띄어쓰기를 기준으로 어절을 나눔
        # ex) ['My', 'first', 'example'] <- 리스트로 리턴
        
with open('example.txt', 'r') as file:
    pre_file(file)

읽기 모드 이외에 쓰기 모드, 추가 모드도 존재한다고 말씀드렸죠. 아래 'w' 는 기존의 내용을 모두 무시하고 사용자가 직접 작성한 내용을 입력하는 것이고, 'a' 는 기존의 내용을 유지한채 바로 뒤에 새로운 내용을 추가하는 것입니다. 

with open('second_example.txt', 'w') as output_file:
    output_file.write('This is dangerous overwriting mode')
    
with open('thrid_example.txt', 'a') as output_file:
    output_file.write('This is safe writing mode')

2. 객체 지향 프로그래밍(Object-oriented programming, OOP)이란?

객제 지향 프로그래밍은 말로 아무리 설명해도 코드를 통해 살펴보는 것만 못합니다. 그럼에도 객체 지향 프로그래밍이 무엇인지 전체적인 그림을 알아야 하기 때문에 말로 먼저 설명하겠습니다. 뒤에 코드를 같이 보면 더 이해가 잘 될 것입니다. 

객체 지향 프로그래밍은 클래스와 오브젝트라는 개념으로 구성되며, 프로그래밍 작업을 오브젝트 단위로 나누게 됩니다. 오브젝트 A가 특정 메서드를 사용해서 출력한 결과를 오브젝트 B에 넘겨주고, 오브젝트 B는 오브젝트 C에 넘기고 등입니다. 우리가 흔히 보던 .append('xx'), .clear() 처럼 마침표 뒤에 메서드를 부르는 방식이 객체 지향 프로그래밍입니다. 

객체 지향 프로그래밍과 반대되는 말로 절차적 프로그래밍(procedure programming)이 있습니다. 데이터, 변수, 함수 등이 글로벌 공간(global space)에 존재하기 때문에 외부에 있는 데이터를 외부에 있는 함수에 넣으면 결과값이 외부로 나오는 것을 의미합니다. 이렇게 되면 변수명이 헷갈리거나 중복 선언을 할 경우 코드가 엄청나게 복잡해지겠죠. max(3, 5), convert_to_celsius(60) 등 마침표 없이 해당 함수를 직접 부르는 방식입니다. 

객제 치향 프로그래밍의 4가지 원칙

1. Encapsulation: 나만의 공간을 설계하자! 

객체 지향 프로그래밍은 관련성이 있는 데이터, 변수(속성), 함수들을 하나로 묶어서 하나의 오브젝트에 넣습니다. 이를 Encapsulation이라고 하며, 각각의 오브젝트는 자기만의 독립된 공간을 가집니다. 즉 오브젝트 A에 오류가 나더라도 일반적으로 오브젝트 B는 영향을 받지 않죠. 

오브젝트 내 함수 파라미터는 별도로 정의할 필요가 없습니다. self 를 파라미터로 그대로 받아오면 되기 때문이죠, 이를 "functions with no param"라고 합니다. 

# Procedure Programming
copmany_list = []
company_first = []
company_second = []

def get_total(first, second):
    return first * 0.4 + second * 0.6

get_total(company_first[0], company_second[0])

# Object oriented Programming
def get_total(self): # self: 오브젝트 자체를 파라미터로 받아옴
        return self.first * 0.4 + self.second * 0.6
        
copmanies[0].get_total()

객체 지향 프로그래밍의 장점은 아래에서 확인할 수 있습니다. 회사 상반기, 하반기 실적 외에도 특별 실적을 평가에 추가할 경우 절차적 프로그래밍은 무려 4개의 요소(incentive 관련)를 추가해야 합니다. 하지만 객제 지향 프로그래밍은 메서드 안에 한 요소만 추가하면 되죠. 

# Procedure Programming
# copmany_list = []
# company_first = []
# company_second = []
company_incentive = []

# def get_total(first, second, 
              incentive):
    # return first * 0.4 + second * 0.4 +\
    + incentive * 0.2

# get_total(company_first[0], company_second[0], 
           company_incentive[0])

=============================================================================================
# Object oriented Programming
# def get_total(self): 
        # return self.first * 0.4 + self.second * 0.4 +\
        + self.incentive * 0.2
        
# copmanies[0].get_total()

2. Abstraction: 거까진 몰라! 

기본적인 코드를 다 알 필요가 없는 Abstraction 성격이 강합니다. 다시 말해, 어떤 오브젝트의 메서드를 사용할 때 그 메서드가 작성된 코드를 몰라도 해당 메서드를 사용할 수 있다는 것입니다. 아래에서 upper(), lower() 메서드도 필요에 의해 사용하면 되는 것이지, 이들을 이루는 코드를 다 이해할 필요는 없습니다. 

example = 'abcd'
example.upper()
example.lower()

upper(), lower()을 이루는 내부 코드가 변화했더라도 유저들은 똑같이 대문자화, 소문자화 기능을 사용할 수 있습니다. 

3. Inheritance: 부모와 자식 관계를 맺자!

기업에 있는 국민이든, 공공기관에 있는 국민이든 다 같이 한 나라의 국민입니다. 국가가 관리하는 명단은 사실상 똑같은 것이죠. 다른 것은 어느 기업에 다니는지, 어느 공공기관에 다니는지입니다. 따라서 이름, 주소, 나이, 직업 등 공통적인 요소는 더 큰 틀의 국민 클래스에서 가져오고, 각 기관마다 다른 점은 따로 지정해주면 편리합니다. 이를 Inheritance 라고 하며, 부모와 자식 간의 관계로 설명합니다. 이를 위해선, 자식 클래스는 부모 클래스를 파라미터로 받아야 합니다. 

4. Polymorphism: 자식은 자식대로 살자! 

자식은 부모 클래스를 상속하지만 모든 것을 그대로 따를 필요는 없습니다. 자식도 자식 나름의 삶을 살아야 하기 때문이죠. 국민 클래스에 있는 거주지 이동 메서드를 기업에서, 공공기관에서 그대로 수행하지 않고, 자기만의 방법을 써서 커스터마이징(or 오버라이딩)할 수 있습니다. 따로 정의하지 않을 경우, 부모 클래스에서 정의한대로 수행하게 됩니다. 동일한 명칭의 메서드지만 서로 다른 역할을 수행할 수 있다고 하여 Polymorphism 즉, many forms를 가진다고 표현합니다. 

실제 예시를 통해 클래스를 구현해보도록 하겠습니다. 

class Nations():
    # 정의 단계에서 __init__ 메서드를 사용
    def __init__(self, name: str, address: str, email: str, age: int) -> None:
        self.name = name
        self.address = address
        self.email = email
        self.age = age
        self.infoList = [self.name, self.address, self.email, self.home]
    
    # 나만의 메서드 정의
    def printInfo(self):
        print(self.infoList)
        
        
person1 = Nations('Jun', 'SEOUL', 'example@example.com', 100)
print(type(person1) == Nations)
person1.printInfo()

아래에서는 상속을 진행해보겠습니다. 

class Company(Nations):
    def __init__(self, name: str, address: str, email: str, age: int, company_num: str) -> None:
        # super().__init__ -> 부모 클래스에서 정의한 속성들 그대로 가져오기
        super().__init__(name, address, email, age)
        self.company_num = company_num
        # 파라미터로 가져오지 않는 정보들은 여기서 작성
        self.company_name = ''
        self.working_years = 0
        self.infoList += [self.company_num, self.company_name, self.working_years]
        
 class Public(Nations):
    def __init__(self, name: str, address: str, email: str, age: int, public_num: str) -> None:
        super().__init__(name, address, email, age)
        self.public_num = public_num
        self.public_name = ''
        self.working_years = 0
        self.infoList += [self.public_num, self.public_name, self.working_years]
        
company1 = Company('Jun', 'SEOUL', 'example@example.com', 90, "Corporate-21")
public1 = Public('Ban', 'SEOUL', 'example@example.com', 90, "Institude-510")

# Company, Public 클래스 내 printInfo()를 정의하지 않았지만 부모 클래스에서 그대로 가져와서 사용 가능
company1.printInfo()
# public1.printInfo()

 

아래는 Polymorphism을 구현한 예시입니다. 부모 클래스에서 출력값의 앞부분을 'Nations'라고 정의했지만, Company는 'Company member'로, Public은 'Public member'로 덮어쓰겠습니다. 

class Nations():
    def __init__(self, name: str, address: str, email: str, age: int) -> None:
        self.name = name
        self.address = address
        self.email = email
        self.age = age
        self.infoList = [self.name, self.address, self.email]
    
    def printInfo(self):
        print(self.infoList)

    def switch_home(self, new_address: str) -> None:
        print('Nations', self.name, 'moves house from', self.address, 'to', new_address)
        
class Company(Nations):
    def __init__(self, name: str, address: str, email: str, age: int, company_num: str) -> None:
        # super().__init__ -> 부모 클래스에서 정의한 속성들 그대로 가져오기
        super().__init__(name, address, email, age)
        self.company_num = company_num
        self.company_name = ''
        self.working_years = 0
        self.infoList += [self.company_num, self.company_name, self.working_years]
    # 커스터마이징
    def switch_home(self, new_address: str) -> None:
        print('Company member', self.name, 'moves house from', self.address, 'to', new_address)
        self.address = new_address 
        
class Public(Nations):
    def __init__(self, name: str, address: str, email: str, age: int, public_num: str) -> None:
        # super().__init__ -> 부모 클래스에서 정의한 속성들 그대로 가져오기
        super().__init__(name, address, email, age)
        self.public_num = public_num
        self.public_name = ''
        self.working_years = 0
        self.infoList += [self.public_num, self.public_name, self.working_years]
    # 커스터마이징
    def switch_home(self, new_address: str) -> None:
        print('Public member', self.name, 'moves house from', self.address, 'to', new_address)
        self.address = new_address 
        
company1 = Company('Jun', 'SEOUL', 'example@example.com', 90, "Corporate-21")
public1 = Public('Ban', 'SEOUL', 'example@example.com', 90, "Institude-510")

company1.switch_home('U.S.A')
public1.switch_home('ENGLAND')

마지막으로 개발자가 클래스를 구현하고 배포한다고 했을 때, 다른 사람들이 커스터마이징 못하도록 하는 private 기능도 있습니다. 보통은 클래스 외부에서도 클래스 내부 속성이나 메서드에 접근할 수 있지만, 특정 기능을 건드리면 클래스 자체가 작동되지 않을 경우 건드리지 못하도록 해야겠죠.

이를 비공개 속성 및 메서드라고 표현하며 아래와 같이 앞에 __ 를 붙여줍니다.

class Something():
    def __pay(self, amount):
        self.__wallet -= amount

다음에 이어 설명하겠습니다. 비전공자 문돌이가 설명하는 파이썬(Python) 기본 문법 (4)

 

비전공자 문돌이가 설명하는 파이썬(Python) 기본 문법 (4)

검색(Search)이란? 1. Linear Search: 정직하게 처음만 찾자! 순차 검색 알고리즘은 말 그대로 찾고자 하는 값을 순차적으로 탐색합니다. 처음부터 차근차근 해당 값을 찾고, 똑같은 값이 이후에 존재하

moondol-ai.tistory.com

 

728x90
반응형