목차


내용이 많아 두 글에 나눠서 작성하게 되었습니다. 복사본 생성시 모르면 자칫 혼란이 올 수 있는 딥 카피부터 시작해서 파일 입/출력하는 방법까지 다루고 자료 구조에 대한 글을 마치도록 하겠습니다. 다음 글 부터는 numpy와 pandas 등 데이터 분석 모델에 직접 넣을 데이터 셋을 다루는 파트입니다.



딥 카피

Python의 기본적인 사상 중 메모리 누수 방지를 이유로 일반적으로는 특정 데이터에 대한 복사본을 만들어도 서로 분리되지 않고 메모리를 공유하는 성질이 있습니다. 즉, 저장되는 공간은 똑같고 이름만 다르게(Alias) 되는 것입니다. 그렇기 때문에 백업시에 이를 주의하지 않으면 복사본을 만드는 의미가 없어집니다. 하나를 수정하게 되면 복사본이라고 생각했던 변수도 같이 수정이 되어버리기 때문입니다.

딥 카피(deep copy)는 저런 기본 사상을 따르지 않고 복사본 생성시 저장할 메모리를 분리하는 것을 의미합니다. 즉, 위에서처럼 단순 복사를 하는 경우는 딥 카피가 발생하지 않았다고 볼 수가 있습니다.

감이 안 잡힌다면 예시를 통해 자세히 살펴봅시다.

# L1 = [1, 2, 3]를 복사

# 단순 복사
L2 = L1 # 메모리 공유

# 딥 카피
L3 = L1[:] # 메모리 분리

# L1의 첫 번째 원소를 10으로 수정
L1[0] = 10; L1
Out : [10,2,3]

# 단순 복사
L2
Out : [10, 2, 3]
# 메모리를 공유하기 때문에 L1과 L2의 내용물이 같음

L3
Out : [1, 2, 3]
# 메모리를 공유하지 않아 L1과 내용물이 다름


[참고] - 객체의 메모리주소를 확인하는 함수 : id

객체들은 각각 메모리주소라는 것을 가지고 있는데 메모리주소가 같은 객체는 이름만 다른 같은 객체라고 볼 수 있습니다. 메모리주소를 확인하는 함수 id가 있는데 문법은 id(변수 이름) 입니다.

​위 예시의 L1, L2, L3를 예로 들면, id(L1)이 3226141541832 일경우 단순복사한 변수인 L2의 경우에도 id(L2)가 3226141541832이지만 딥 카피로 복사한 L3의 경우에는 id(L3)는 다른 숫자를 불러오게 됩니다. 즉, 두 변수에 대한 함수 실행 후 서로 같은 값을 출력하게 되면 메모리 공유를 하고 있다는 것을 말합니다.

id(L1)
Out[43]: 3226141541832

id(L2)
Out[44]: 3226141541832

id(L3)
Out[45]: 3226141718088
딥 카피가 실행된 L3만 메모리주소가 다른 것을 알 수 있습니다.




지역 변수 & 전역 변수

  1. 지역 변수(Local Variable) : 함수 내부에서만 사용할 수 있는 변수로 함수 외부에서는 사용이 불가능한 변수입니다.

  2. 전역 변수(Global Variable) : 변수 선언 이후 어디서든 사용할 수 있는 변수입니다.

함수 내에는 함수 내의 코드에서 사용되는 변수의 이름이 정해져 있습니다. 그렇다면 함수 내의 변수 이름과 사용자가 임의로 생성한 변수의 이름이 겹치면 함수를 실행할 때 오류가 발생할 것입니다. 그렇기 때문에 지역 변수와 전역 변수가 구분되어 있으며, 전역 변수와 지역 변수의 이름이 동일한 경우 함수 내에서는 반드시 지역 변수를 사용합니다. 아래 예시를 통해 각각 어떻게 실행되는지 확인해봅시다.

# v1 지역변수 설정한 함수 생성
def f1():
    v1 = 1
    print(v1)
# 지역변수 없는 함수 생성
def f2():
    print(v1)

# 전역변수 생성
v1 = 10

# 위에서 만든 함수들의 출력 값
f1();
Out : 1

f2();
Out : 10


이처럼 v1가 지역변수로 존재하는 함수 f1에서는 전역변수 v1(=10)을 무시하고 함수 내 지역변수를 출력하는데 반면 지역변수가 없는 함수 f2에서는 전역변수를 출력합니다.

그러면 함수 내에서 설정한 변수는 함수 외부에서는 사용하지 못할까요? 그렇지는 않습니다. 함수 내에서 선언한 변수를 전역변수 선언하여 함수 밖에서도 사용하게 할 수 있습니다. 변수 생성 전에 ‘global 변수이름’을 붙이면 함수 밖에서도 호출 할 수 있습니다.

1. 전역 변수 선언(global) 하지 않은 경우

# v2 지역변수 설정한 함수 생성
def f1():
    v2 = 1
    print(v2)
# 지역변수 없는 함수 생성
def f2():
    print(v2)

# 위에서 만든 함수들의 출력 값
f1();
1

f2();
Traceback (most recent call last):

  File "<ipython-input-53-55649989190d>", line 1, in <module>
    f2()

  File "<ipython-input-51-0872c36d0cb9>", line 6, in f2
    print(v2)

NameError: name 'v2' is not defined

2. 전역 변수 선언(global)한 경우

# v2 지역변수 설정한 함수 생성
def f1():
    global v2
    v2 = 1
    print(v2)
# 지역변수 없는 함수 생성
def f2():
    print(v2)

# 위에서 만든 함수들의 출력 값
f1();
1

f2();
1


이처럼 함수 내의 변수도 전역변수로 사용할 수 있습니다.



매개변수 전달방식

다음은 함수 생성시 변수를 지정하는 방식에 대해 설명하도록 하겠습니다. 이전에 배운 def function(...): 방식 외에 다른 방법입니다.

1) * : 가변형 인자 전달 방식으로 함수에 넣는 변수의 갯수에 제한이 없습니다. 내부 반복문이 필요합니다. ​

[예제] 함수에 입력된 모든 값의 곱을 출력하는 함수 생성

# *를 이용한 함수 생성
def all_product(*para):
    val = 1
    for i in para :
       val *= i
    return val

all_product(1,2,3,4,5)
Out : 120

2) ** : KEY-VALUE 인자 전달 방식으로, 마찬가지로 내부 반복문이 필요합니다.

[예제] 함수에 입력된 모든 값의 키를 이용한 print 함수

# **를 이용한 함수 생성
def printer(**para):
    for i in para.keys() :
       print(f'{i}는 {para[i]}입니다.')

printer(a=1, b=2, c=3)
a 1입니다.
b 2입니다.
c 3입니다.

​ ​ 3) zip : 동시에 변수를 묶어서 전달 시 필요

[예제] 길이가 같은 두 리스트의 같은 위치끼리 더하는 함수 생성 ​

# zip을 이용한 함수 생성
def fsum(v1, v2):
    result = []
    for i,j in zip(v1,v2)) :
       result.append(i+j)
    return result

fsum([1,2,3],[11,22,33])
Out : [12,24,36]




모듈 생성 및 패키지

모듈 생성

Python을 재시작 하거나 기타 이유로 세션이 만료되면 기존에 불러왔던 모듈과 만들어둔 함수가 초기화되기 때문에 다시 불러와야합니다. 그런데 분석 작업을 하다보면 모듈 한 두개만 사용하는 것이 아닌데 일일이 코드로 친다면 초기 실행 시 시간이 오래 걸립니다. 그렇기 때문에 원하는 함수를 파일에 담아 기본 디렉토리에 저장하면 모듈 생성을 할 수 있습니다. 기본 디렉토리는 Python에서 기본적으로 사용하는 폴더라고 생각하시면 되는데 파일을 불러올 때 기본 디렉토리에 있는 파일은 파일명과 확장자만 입력하여 불러올 수 있습니다. 기본 디렉토리는 아래 코드로 확인해볼 수 있습니다.

# 기본 디렉토리 확인
import os
os.getcwd()
Out : 'C:\\Users\\본인_컴퓨터_이름'

원하는 함수들을 파일에 다 입력했으면 기본 디렉토리를 확인하고 해당 위치에 파일을 저장합니다. 불러오는 방법은 기본 모듈을 불러오듯이 import 파일이름을 사용하시면 됩니다.


참고 : profile 만들기

Python 실행시 기본적으로 사용하는 모듈들이 많기 때문에 모듈별로 일일히 불러오면 초기 기동시에 시간이 오래 걸립니다. 그렇기 때문에 불러와야할 모듈들을 한 파일에 묶어서 .py로 생성 후 그 파일을 호출하는 것만으로 기본적으로 사용할 모듈들을 모두 호출할 수 있습니다.

[실행 순서]

1) 파일명.py의 형식으로 디렉토리 폴더에 저장합니다.

2) 이후 'run 파일명'을 실행시키면 모듈을 동시호출할 수 있습니다.


패키지

여러 개의 모듈을 저장한 폴더라고 생각하시면 됩니다. 현재 작성 예정인 데이터 분석 글에서는 패키지까지 생성하여 여러 모듈을 관리하는 것까지는 계획에 없기 때문에 다음 내용으로 넘어가도록 하겠습니다.



파일 입/출력

다음으로는 기본적인 파일 입출력에 대해 알아보도록 합시다.

파일 입력

  1. open : 파일을 열어서 메모리상으로 불러오는 작업입니다. (cursor 선언)

  2. fetch : 선언된 cursor(임시 메모리 공간)에 저장된 data를 한 건씩 불러옵니다.

  3. close : 객체 선언 완료 후 cursor에 할당된 메모리영역 close. cursor를 닫지 않으면 메모리 누수 현상이 발생할 수 있습니다.

​ 1단계 : open

open이라는 함수를 사용하며 디렉토리 내의 해당하는 이름의 파일을 불러옵니다.

직접 과정을 보여드리기 위해 아래 이미지의 내용을 입력한 read_test1.txt라는 텍스트 파일을 만들었으며 변수 이름은 임의로 c1로 했습니다만 원하는 이름으로 하셔도 상관없습니다.

c1 = open('read_test1.txt')


2단계 : fetch

불러온 파일을 열어보는 과정으로 함수로는 readline, readlines가 있습니다.

1. readline : open으로 불러온 파일을 한 번 실행시마다 한 줄씩만 불러옵니다.

v1 = c1.readline()
print(v1)
1 2 3 4
v1 = c1.readline()
print(v1)
5 6 7 8

2. readlines : open으로 불러온 파일을 한 줄 단위로 리스트에 넣습니다.

v2 = c1.readlines()
print(v2)
['1 2 3 4\n','5 6 7 8']

3단계 : close

close라는 함수를 사용하며 open에서 불러온 파일을 닫는다.​

c1.close()


파일 출력

이번에는 외부 파일을 불러오는 입력 대신 Python에서 외부로 파일을 출력하는 법에 대해 알아봅시다.

1단계 : open

읽기에서도 open이라는 함수를 썼지만 입력이 아닌 출력을 하기 때문에 옵션에 'w'를 추가하여 쓰기모드를 선언합니다.

예시로 L1 = [[1,2,3,4],[5,6,7,8]]이라는 리스트를 생성하여 이 리스트를 파일 형태로 출력해봅시다. 입력 부분의 코드와 구분하기 위해 c2로 했지만 큰 상관은 없습니다.

L1 = [[1,2,3,4],[5,6,7,8]]
c2 = open('write_text.txt', 'w')


2단계 : fetch

fetch과정에 주의할 점이 있는데 문자만 입력할 수 있기 때문에 리스트 형태로는 바로 입력할 수 없습니다. str을 씌우면 입력은 되지만 리스트가 통째로 입력됩니다. for문을 사용하여 리스트 원소 하나하나씩 삽입해야하며, ‘\n’을 통해 한 줄 띄우기도 가능합니다.

c2.writelines(L1)
Traceback (most recent call last):

  File "<ipython-input-66-1567a9427b2a>", line 1, in <module>
    c2.writelines(L1)

TypeError: write() argument must be str, not list
리스트는 바로 출력이 불가능합니다

3단계 : close

c1.close()

fetch 과정의 예시




Python Part 1이 끝났습니다. Python을 이용한 데이터 분석에 필요한 자료 구조의 개념과 가장 기본적인 지식들을 다뤄보았습니다. 현재 글을 작성하는 목적이 데이터 분석에 필요한 기초적인 지식을 혼란스럽지 않게 전달하는 것이기 때문에 사용했던 것만 설명드렸습니다만, 프론트엔드/백엔드를 공부하거나 코딩 테스트를 준비하시는 분들은 보다 많은 자료 구조를 공부하셔야 합니다.

Part 2에서는 numpypandas 모듈을 불러와서 배열(array)데이터 프레임(DataFrame)을 다룹니다. Python에 엑셀 파일을 불러와서 데이터의 정렬은 어떻게 하며 그룹 함수는 어떻게 지정하는지, 서로 다른 데이터끼리는 어떻게 합치는지 등등 MS 엑셀이나 SQL에서 하던 작업을 해보는 것입니다.

엑셀에서 할 수 있는 작업을 굳이 Python에서 하는 이유는 다양한데요. 우선 데이터를 처리하는 속도가 보다 빠릅니다. 물론 적은 양의 데이터를 처리하는데는 엑셀 프로그램으로도 충분히 기초적인 통계 분석이 가능합니다만, 몇 백만 개의 행을 처리하는 경우에는 엑셀로 작업하려면 엄청난 잔렉과 씨름해야합니다. 게다가 머신러닝/딥러닝(ML/DL)을 돌리기 위해서는 어차피 Python으로 데이터를 불러와야 합니다. 간단하게 요약하자면, 빠른 전처리다양한 모델 분석이 Python을 사용하는 목적이라고 할 수 있습니다.