본문 바로가기
프로그래밍 언어/Python

[Python] 기본적인 데이터형과 데이터 구조

by 연습장이 2023. 6. 27.
728x90
반응형

파이썬을 공부하면서 기본적인 데이터형과 데이터 구조에 대해 나름 정리하고자 이 글을 쓴다.

 

함수와 메서드 차이

함수 : 여러 개의 처리를 기능별로 모아 놓은 것

  • max(), min()과 같은 명령은 특정 요소에 관련돼 있지 않고 원하는 때에 호출할 수 있음
  • 특정 요소(= 객체)에 관련된 함수를 메서드라고 함

예를 들어 append와 insert는 아래처럼 쓰일 수 있다.

weekdays.append("hello")

weekdays.insert(1, "world")

 

반면 del은 아래와 같다.

del items[2]

append나 insert는 조작 대상이 명확하다. 따라서 이러한 함수가 메서드다.

이와 반대로 del은 파이썬이 원래부터 준비하고 있는 명령이다.

 

배열

  리스트 튜플 사전
작성 방법 [] 대괄호 () 소괄호 {} 중괄호
데이터 구조 시퀀스(나열) 시퀀스(나열) 사전
접근 방법 변수[번호] 변수[번호] 변수[키]
특징 뮤터블 이뮤터블 번호는 없음

  튜플은 변경이 불가하고 리스트는 변경이 가능하므로 언뜻 보기에 '그럼 더 유동적인 리스트로 다 선언하면 되지 않나?' 싶을 수 있다. 튜플은 값을 변경할 수 없는 대신, 메모리 소비가 훨씬 적다. 따라서 튜플은 좌표계(X,Y)와 같이 고정된 값을 사용하는 로직에 자주 사용된다.

 

  어떤 변수에 리스트와 튜플을 저장했다고 가정하자. 그 변수를 다른 변수에 대입해도 그 리스트 자체가 복사 되는 것은 아니다. 아래와 같다.

a = [1, 2, 3]
b = a
a[2] = 9
a
Out[30]: [1, 2, 9]
id(a)
Out[31]: 3172553456896
b
Out[32]: [1, 2, 9]
id(b)
Out[33]: 3172553456896

 

a의 특정 값만 바꿨는데 b 변수의 특정 값도 같이 바뀌었다. 이는 a와 b가 같은 [1, 2, 3]이 저장된 공간을 참조하고 있기 때문이다. 그렇다면 그 리스트 자체는 어떻게 복사할까? 아래처럼 하면 된다.

a = [1, 2, 3]
b = a.copy()
a[2] = 9
a
Out[35]: [1, 2, 9]
id(a)
Out[36]: 3172553625856
b
Out[37]: [1, 2, 3]
id(b)
Out[38]: 3172552639680

 

조건문

파이썬에서는 조건식 위치에 값이 기술되면, 그 값에 따라 조건의 성립 여부를 결정하는 규칙이 정해져 있다. 아래와 같다.

  조건식이 성립하지 않는다 조건식이 성립한다
수치 0 0이 아닐 때
문자열 빈 문자열 ''나 "" 왼쪽 항의 조건 이외일 때
리스트 빈 리스트 [] 왼쪽 항의 조건 이외일 때
튜플 빈 튜플 () 왼쪽 항의 조건 이외일 때

테스트를 해보면 다음과 같다.

a = 0
if a:
    print("a is not zero")
else:
    print("a is zero")
a is zero



if ():
    print("it is ture value")
else:
    print("it is false value")
it is false value

 

함수

거의 같은 값을 사용하지만, 때로는 다른 값을 지정하고 싶을 때도 있다. 이 때는 기본값을 아래와 같이 사용하면 된다.

def say_hello(name="Alex"):
    print("Hi! " + name)

say_hello()
Hi! Alex

say_hello("june")
Hi! june

함수를 정의한다(=def로 선언하고 함수명을 붙이고 인수명을 정해서 실제 처리를 기술한다) 정도는 아니지만 약간의 작업으로 함수를 사용하고 싶을 때도 있을 것이다. 이럴 때 아래와 같이 람다 함수를 사용한다.

# 람다 함수를 선언하고 이를 호출하기 위해 변수에 대입
is_even = lambda x: x % 2 ==0

# ()를 붙여 함수를 호출함
is_even(2)
Out[10]: True

is_even(3)
Out[11]: False

# 마찬가지로 람다 함수를 선언하고 이를 호출하기 위해 변수에 대입
say_hi = lambda name: print("Hi! " + name)

# ()를 붙여 함수를 호출함
say_hi("Ken")
Hi! Ken

하지만 위처럼 변수에 담아 호출하는 것은 람다 함수의 정체성과 맞지 않다. 람다 함수는 일회성이다. 위처럼할 바엔 def로 함수를 정의해서 재사용하는 것이 더 좋다. 아래와 같은 예가 좋은 예다.

# map 함수를 사용할 때
list(map(lambda x: x*2, [1, 2, 3]))
Out[16]: [2, 4, 6]

# filter 함수를 사용할 때
list(filter(lambda a: a%2==0, [0, 1, 2, 3, 4, 5]))
Out[17]: [0, 2, 4]

# sorted 함수로 정렬을 하되 key 파라미터에 무엇을 기준으로 나열할지를 지정하는 함수를 람다로 표현
sorted(["bread", "rice", "spaghetti"], key=lambda x: len(x), reverse=True)
Out[23]: ['spaghetti', 'bread', 'rice']

 

외부 py 확장자 파일의 script import

외부 py 확장자 파일의 코드는 아래와 같다. test_import.py로 저장한다.

# -*- coding: utf-8 -*-
"""
Created on Thu Jun 22 19:09:30 2023

@author: june
"""

import random
for _ in range(5):
    print(random.randint(0, 5))
print("done")

이 것을 어떻게 실행할까? 불러오는 파일과 같은 경로라면 아래처럼 심플하다.

# py 확장자명은 제외
import test_import

만약 경로가 다르다면 어떻게 해줘야 할까? 경로를 불러와 지정해주고 import를 아래처럼 해준다.

import sys
sys.path.append('C:/Users/june/.spyder-py3')

import test_import
3
2
1
2
5
done

 

변수

전역 변수는 간편하고 편리하다. 하지만 여러 곳에서 값을 수정하면 누가 언제 어떤 값을 써 넣은 것인지 알 수 없게 되고, 버그의 온상이 되기가 쉽다. 따라서 파이썬에선 아래와 같은 사양으로 정의 돼 있다.

  • 함수 안에서 전역 변수 참조(Read)는 문제없이 실시할 수 있다.
  • 함수 안에서 전역 변수와 같은 이름의 변수에 대입(Write)하면 전역 변수를 다시 쓰는 것이 아니라 같은 이름의 지역 변수가 작성된다. 즉, 전역 변수를 다시 쓰지 않는다.

아래 코드와 결과를 참조해보자.

# -*- coding: utf-8 -*-
"""
Created on Mon Jun 26 08:13:02 2023

@author: june
"""

""" scope3.py """
message = "Hello"

def say():
    message = "Hi"
    print("say:message="+message)
    obj_id = id(message)
    print("say:id(message)={0:d}".format(obj_id))
    
def main():
    say()
    print("main:message="+message)
    obj_id = id(message)
    print("main:id(message)={0:d}".format(obj_id))
    
if __name__ == '__main__':
    main()
    
runfile('C:/Users/june/.spyder-py3/scope3.py', wdir='C:/Users/june/.spyder-py3')
say:message=Hi
say:id(message)=3172551671088
main:message=Hello
main:id(message)=3172551358448

하지만 전역 변수를 의도적으로 함수 내에서 다시 쓰고 싶은 경우도 있다. 이 때엔 global 명령어를 아래처럼 쓰면 된다.

# -*- coding: utf-8 -*-
"""
Created on Mon Jun 26 08:28:03 2023

@author: june
"""

""" scope4.py """
message = "hello"

def say():
	# 전역 변수 message를 함수 안에서 변경한다
    global message
    message = "Hi"
    print("say:message="+message)
    obj_id = id(message)
    print("say:id(message)={0:d}".format(obj_id))
    
def main():
    say()
    print("main:message="+message)
    obj_id = id(message)
    print("main:id(message)={0:d}".format(obj_id))
    
if __name__ == '__main__':
    main()
    
runfile('C:/Users/june/.spyder-py3/untitled3.py', wdir='C:/Users/june/.spyder-py3')
say:message=Hi
say:id(message)=3172551671088
main:message=Hi
main:id(message)=3172551671088

 

객체

일상에서의 용어 객체지향에서의 용어
물건 객체 또는 인스턴스
물건의 특징 프로퍼티
물건의 조작 메서드

객체지향에서는 아래의 개념이 있다.

  • 클래스 : 객체로부터 공통적인 특징만을 모아놓은 추상적인 개념
  • 생성자 : 객체를 만들기 위한 전용 함수
  • 상속 : 어떤 특징을 이어 받는 것
  • 메서드 : 오브젝트(객체)를 조작하는 함수(혹은 기능)
  • 프로퍼티 : 오브젝트(객체)가 가지고 있는 고유한 특징(혹은 속성)
  • 인터페이스 : 메서드를 추상화한 것

 

클래스 설계

  게임을 개발한다고 할 때, 클래스 설계에 대한 접근법으로는 다음과 같다.

  • 명사(게임 중에서 움직이는 것)는 클래스로 할 수 있는 것이 많다.
  • 비슷한 특징을 가진 것은 같은 클래스 또는 상속관계를 만들 수 있는지 생각한다.
  • 반드시 클래스로 하면 좋은 것은 아니다(함수가 간단할 수도 있다).
  • 상속을 무리하게 사용하는 것보다 클래스를 조합하도록 한다.

  뛰어난 설계를 카탈로그로서 합한 것을 디자인 패턴이라고도 한다. 관심이 있다면 디자인 패턴 서적을 참고해 보자. 다음으로 클래스를 만들고 그 안에 아래와 같이 함수를 선언한다.

class Person:
    def __init__(self, name):
        self.name = name
        
he = Person("smith")
she = Person("alice")

객체지향 언어에서는 객체를 만드는 함수를 생성자라고 하며 __init__은 생성자에 해당한다. 이 함수의 첫 번째 변수에는 객체 자신이 전달된다. 첫 번째 인수는 파이썬이 설정해주므로, 우리가 그것을 명시적으로 지정하지는 않는다. 관습적으로 self란 인수명을 많이 사용한다. 이처럼 __init__는 인수로 주어진 객체를 초기화하는 기능을 하므로 "객체를 만든다"라기 보다는 "객체를 초기화한다"고 보는게 더 정확할지도 모른다.

  클래스 도(diagram)라는 것이 있다. 클래스에 대한 설계도를 의미하는데, 보다 복잡한 설계가 있지만 간단하게 클래스명, 프로퍼티명, 메서드명을 네모 박스에 그려 표현한다. 아래 코드를 보자

class Pen:
    def __init__(self, length, color):
        self.length = length
        self.color = color
    def write(self, how_namy_hours):
        self.length -= how_namy_hours / 10

my_pen = Pen(10, "black")        

my_pen.write(3)

my_pen.length
Out[23]: 9.7

Pen에 대한 클래스를 정의하고, 이를 my_pen 변수에 담아서 사용하고 있다. 이 것을 클래스 다이어그램으로 표현하면 다음과 같다.

이상으로 파이썬의 기초 중 데이터형과 구조에 대해 알아보았다. 파이썬을 입문하는데 도움이 되었으면 한다.

 

* 참고문헌

- 다카나 겐이치로 저, "게임으로 배우는 파이썬"

728x90
반응형