본문 바로가기
python

네카라쿠배 프론트엔드 취업완성 스쿨 2기 2차 테스트 5일차 학습

by 새우하이 2021. 6. 19.

이번에는 게임을 만들어 본다.

word.txt를 불러와 words에 추가한다.

strip()은 문자열이나 공백을 제거하는 함수로 그냥 사용하면 불러온 문자열의 양쪽끝 공백을 제거한다.

import random
import time
words = [] # 영어 단어 리스트 (1000개 로드)

n = 1 # 게임 시도 횟수
cor_cnt = 0 # 정답 개수

with open('./resource/word.txt', 'r') as f:
    for c in f:
        words.append(c.strip())
print(words)

input("준비 됐으면 enter 키를 눌러주세요.")

input()

input()은 사용자의 입력을 기다리는 함수이다.

  • input()은 입력되는 모든 것을 문자열로 취급한다.(숫자도 문자열로 취급하여 형변환하여 계산에 사용해야함)
a=input()
print(type(a)) #<class 'str'>

다시 print(word)아래에

start = time.time()
# print(start)

시작시간을 저장해준다.

while n <= 5:
    random.shuffle(words)
    q = random.choice(words)
    print("* 문제 #{}".format(n))
    print(q)
    x = input()
    print()


    if str(q).strip() == str(x).strip(): #입력확인 공백제거
        print("정답")
        cor_cnt += 1
    else:
        print("오답")
    n += 1
end = time.time()
et = end - start
et = format(et,".3f")

if cor_cnt >= 3:
    print("합격")
else :
    print("불합격")
print("게임시간 " , et, "초" , "정답 개수 : {}".format(cor_cnt))

if __name__ == "__main__":
    pass

while 문 (반복문)으로 5번을 실행할 것이다.

random의 shuffle은 words 리스트를 랜덤으로 섞어준다.

그리고 choice로 그중 하나를 뽑아와 q에 담고 출력한다.

그리고 x와 q를 비교하는데 입력한 값과 리스트에서 뽑아온 값에서 양쪽의 공백을 제거하고 비교를 해주고 같으면 정답 카운트를 +1 해준다.

이후 end의 시간을 구해서

end - start로 시간을 구하고 출력해주면 된다.

사운드 재생

ModuleNotFoundError: No module named 'winsound'

실습중에 MAC 사용시 이런 에러를 만날 수 있다. winsound는 windows의 사운드 재생 모듈이라 그런것 같다.

pygame이라는 외부라이브러리로 대체가능하다.

import random
import time
# 사운드 출력필요모듈
# import winsound
import pygame
import sqlite3
import datetime
#DB 생성 & auto commit
conn = sqlite3.connect('./resource/records.db', isolation_level=None)

#cursor 연결
cursor = conn.cursor()
cursor.execute('CREATE TABLE IF NOT EXISTS records(\
    id INTEGER PRIMARY KEY AUTOINCREMENT, cor_cnt INTEGER, record text, reg_date text\
)')
words = [] # 영어 단어 리스트 (1000개 로드)

n = 1 # 게임 시도 횟수
cor_cnt = 0 # 정답 개수

winsound 대신 pygame을 import 시켜준다. 아

import 전에

$ pip install pygame

으로 설치 부터하고.

이제 db 연결을 위한 작업

sqlite3.connect 해주고 isolation_level = None 으로 autocommit 설정해준다.

IF NOT EXISTS 는 records 라는 테이블이 있는지 없는지 검사해서 없으면 생성할 수 있게끔 안전하게 처리하는 것이다.

id는 Integer 형식의 primary key로 지정하고 AUTOINCREMENT 로 따로 id를 숫자로 지정해주지 않아도 자동으로 오름차순 숫자를 넣어준다.

# 파이게임 mixer 초기화
pygame.mixer.init() 
good = pygame.mixer.Sound('./sound/good.wav')
bad = pygame.mixer.Sound('./sound/bad.wav')

with open('./resource/word.txt', 'r') as f:
    for c in f:
        words.append(c.strip())
input("준비 됐으면 enter 키를 눌러주세요.")

start = time.time()
# print(start)

while n <= 5:
    random.shuffle(words)
    q = random.choice(words)
    print("* 문제 #{}".format(n))
    print(q)
    x = input()
    print()
        if str(q).strip() == str(x).strip(): #입력확인 공백제거
        print("정답")
        # 정답 사운드 재생
        pygame.mixer.Sound.play(good)
        cor_cnt += 1
    else:
        print("오답")
        # 오답 사운드 재생
        pygame.mixer.Sound.play(bad)
    n += 1

pygame은 mixer를 init해줘야 한다.

그리고 사운드 호출에 사용할 사운드파일을 연결시켜놓는다.

이후에는 이전에 했던 word.txt 불러오는 과정과 input으로 입력값을 받아 비교하는 과정이고. sound 재생은

pygame.mixer.Sound.play(good)

해당 구문을 통해 재생시킨다. 정답이면 정답사운드를 오답이면 오답사운드를 재생한다.

end = time.time()
et = end - start
et = format(et,".3f")

if cor_cnt >= 3:
    print("합격")
else :
    print("불합격")
cursor.execute("INSERT INTO records('cor_cnt', 'record', 'reg_date') VALUES(?,?,?)",\
    (cor_cnt,et,datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))\
)
print("게임시간 " , et, "초" , "정답 개수 : {}".format(cor_cnt))

if __name__ == "__main__":
    pass

그리고 이제 아까 연결하여 생성했던 DB에 값을 삽입해준다.

정답 갯수와 진행시간 그리고 현재 시간을 format으로 형태를 지정해서 저장할 것이다. MAC 사용자라면 또 수업에 사용되는 프로그램 실행을 못할테니.

sqlite3 명령어로 테이블을 확인해보자. 터미널에서

$ sqlite3 ./resource/records.db #DB 연결되며 커맨드상태가 바뀜
sqlite >> .tables; # records 테이블이 보임
sqlite >> select * from records; # records의 필드들을 보여줌

값이 잘 들어있는것을 확인

Obejct Reference

객체 참조 중요한 특성들

dir()

print('EX1-1 : ')
print(dir())

dir 함수를 root에서 하면 현재 이 상태(이 파일을 실행하는 주체) 에 대해서 나온다.

결과 :

['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']

dir() 함수는 인자가 없으면, 현재 지역 스코프에 있는 이름들의 리스트를 반환한다. 인자가 있으면, 해당 객체에 유효한 속성(attribute)들의 리스트를 반환하려고 시도한다.

그래서 이 리스트의 속성값들을

print(__name__)

출력해서 볼 수 있다.

id vs eq (==) 증명

x = {'name':'kim', 'age':33, 'city':'Seoul'}
y = x
print('EX2-1 : ', id(x), id(y))

딕셔너리를 생성해서 값을 복사한뒤 print를 통해 id 값을 확인해본다.

파이썬의 내장함수 id()는 객체의 <아이덴티티>를 반환한다. 아이덴티티는 객체의 수명동안 유일하게 바뀌지 않음이 보장되는 정수이다.

x와 y의 아이디 값이 같은데

x = {'name':'kim', 'age':33, 'city':'Seoul'}
y = x
y['name'] = 'park'
print(x,y, sep="\n")

이 상태를 얕은 복사(shallow copy)라고 한다.

얕은 복사는 복합 객체(리스트 또는 클래스 인스턴스 들과 같은 다른 객체를 포함한 객체)를 만들고 원본 객체를 가리키는 참조를 새로운 복합객체에 삽입한다.

이 말은 껍데기는 별도로 생성하지만 그 안에서는 원본객체를 가리키고 있다는 뜻이다. 따라서 y를 변경해도 x,y가 둘다 변경된다.

그러므로 id값이 같게 나온다.

x = {'name':'kim', 'age':33, 'city':'Seoul'}
y = x
y['name'] = 'park'
print(x,y, sep="\n")
print('EX2-1 : ', id(x), id(y))
print('EX2-2 : ', x == y) # 같은 value(값)을 가지고 있는지
print('EX2-3 : ', x is y) # 같은 object(객체)를 가리키고 있는지?

x['class'] = 10
print(x,y, sep="\n")

==과 is를 사용해서 확인해봐도 True를 반환한다.

그리고 이후에 class라는 key와 value를 추가해도 같이 추가되는 것을 확인 가능.

z = {'name':'park', 'age':33, 'city':'Seoul', 'class':10}
print('EX2-6', x, z, sep="\n")
print('EX2-7', x is z)
print('EX2-7', x == z)

이번에는 이후에 class를 추가해준 x와 새로 생성된 z를 비교해보자.

{'name': 'park', 'age': 33, 'city': 'Seoul', 'class': 10}
{'name': 'park', 'age': 33, 'city': 'Seoul', 'class': 10}

둘다 눈에 보이는 값은 같다.

하지만 x is z 를 출력하면 False가 출력된다.

물론 == 은 값을 비교하기 때문에 True를 출력한다. 왜냐면 값은 같지만 서로 가리키는 객체가 다르기 때문에 이런 결과가 나온다.

== 보다는 is가 더 빠르게 비교 가능하다== 는 값을 비교하기 때문에 많은 데이터를 비교하는데는 오래걸리지만 is는 같은 객체를가리키는지 비교하면 되기 때문이다.

깊은복사(deep copy)는 새로운 복합 객체를 만들고 재귀적으로 원본 객체의 사본을 새로만든 복합객체에 삽입한다. 깊은 복사는 모든 것을 복사하기 때문에 지나치게 많이 복사할 수 있다. 복사본 간에 공유할 의도가 있는것까지도.

튜플 불변형의 비교 (immutable)

tuple1 = (10,15,[1,2])
tuple2 = (10,15,[1,2])
print('EX3-1 : ', id(tuple1), id(tuple2))

튜플은 immutable 하다. 즉 수정이 불가능하다.

두 개의 같은 value를 가진 tuple 변수는 다른 id 값을 가진다. 별개의 객체라는 의미이다.

print('EX3-1 : ', tuple1 is tuple2)
print('EX3-1 : ', tuple1 == tuple2)
print('EX3-1 : ', tuple1.__eq__(tuple2))

당연하게도 다른 객체이므로 is는 False가 나오고 같은 값을 가지므로 ==(eq)는 True가 나올 것이다

복사

tl1 = [10,[100,105],(5,10,15)]
tl2 = tl1
tl3 = list(tl1)

print('EX4-1 : ', tl1 == tl2) 
print('EX4-2 : ', tl1 is tl2)
print('EX4-3 : ', tl1 == tl3)
print('EX4-4 : ', tl1 is tl3)

tl2 는 tl1을 그냥 복사했고, tl3는 자료형을 이용해서 복사해줬다. 값은 똑같기 때문에

tl1 == tl2 == tl3 는 모두 값은 같다. 하지만 자료형을 이용해서 복사한경우 다른 객체를 가리킨다. tl1 is tl3 는 False를 반환한다.

이렇게 사용하는게 안전하게 값을 복사할 수 있다.

tl1 = [10,[100,105],(5,10,15)]
print('리스트', id(tl1[1]))
print('튜플', id(tl1[2]))
tl2 = tl1
tl3 = list(tl1)

print('EX4-1 : ', tl1 == tl2) 
print('EX4-2 : ', tl1 is tl2)
print('EX4-3 : ', tl1 == tl3)
print('EX4-4 : ', tl1 is tl3)

tl1.append(1000)
tl1[1].remove(105)
print('리스트', id(tl1[1]))

print('EX4-5 : ', tl1)
print('EX4-6 : ', tl2)
print('EX4-7 : ', tl3)

tl1[1] += [110,120]
tl2[2] += (110,120)
print('EX4-5 : ', tl1)
print('EX4-6 : ', tl2)
print('EX4-7 : ', tl3)

print('튜플', id(tl1[2]))

이번에는 tl1 에 1000을 추가하고 tl1[1] tl1내부의 리스트에서 105를 삭제했다.

tl1[1]의 아이디는 제일 처음 tl1을 생성할때의 id값과 변함이 없다.

그리고 tl1,tl2,tl3 을 보면 tl1,tl2 의 값이 잘 변한것을 볼 수 있을것이다.

이번에는 tl1[1] 리스트의 값에 110,120 을 추가해보고

tl1[2] 튜플에도 110,120 을 추가한다. 다시한번 tl1,tl2,tl3 을 출력해보면 리스트와 튜플에도 값이 잘 추가된 것을 볼 수 있다.

이쯤되면 튜플은 immutable 속성을 가진다면서 왜 값이 변하는지 이해가 안될 수 있다.

그래서 tl1[2]의 id 값을 다시한번 출력해보자. 우리가 앞서 tl1을 생성했을 때 출력했던 id값과 다르다는 것을 알 수 있다.

이는 튜플이 immutable하여 값의 변경이 불가능하니 객체를 재생성하여 할당했음을 보여준다.

Deep Copy

import copy
class Bascket:
    def __init__(self, products=None):
        if products is None:
            self._products = []
        else:
            self._products = list(products)
    def put_prod(self, prod_name):
        self._products.append(prod_name)

    def del_prod(self, prod_name):
        self._products.remove(prod_name)

bascket1 = Bascket(['Apple', 'Bag', 'Tv', 'Snack', 'Water'])
bascket2 = copy.copy(bascket1)
bascket3 = copy.deepcopy(bascket1)

print('EX5-1 : ', id(bascket1), id(bascket2), id(bascket3))
print('EX5-1 : ', id(bascket1._products), id(bascket2._products), id(bascket3._products))

Bascket 이라는 클래스를 하나 선언하고 products=None 은 products 라는 매개변수가 없으면 기본값으로 None으로 사용하겠다는 의미이다. 즉 products라는 값이 넘어오면 list형태로 products를 self._products 에 저장하고 아니면 빈리스트를 하나 생성한다.

put_prod와 del_prod는 리스트에 추가와 삭제를 담당한다.

이제 인스턴스를 만들어 보자. bascket1로 클래스인스턴스를 하나 생성하고 이를 copy 패키지의 copy 함수와 deepcopy함수로 각각 복사한다.

 print('EX5-1 : ', id(bascket1), id(bascket2), id(bascket3))

첫 줄에서 보면 모두 다른 id 값을 보여준다. 객체를 복사했는데 왜 다 다른 값을 가지고 있을까 ?

print('EX5-1 : ', id(bascket1._products), id(bascket2._products), id(bascket3._products))

인스턴수는 현재 다른 객체를 가리키고 있지만 안에있는 객체 즉 실제 복사된 product 값도 어떻게 복사되었는지 확인해봐야한다.

확인해보면 얕은복사를 사용한 bascket1과 2의 id가 같은 것을 확인할 수 있다.

실제로 그런지 확인해보자

bascket1.put_prod('Orange')
bascket2.del_prod('Snack')
print('EX5-2 : ', id(bascket1._products), id(bascket2._products), id(bascket3._products))

bascket1에는 orange를 추가하고 bascket2에는 snack을 추가했다. 결과를 확인해보면 bascket1과 2 가 같은 리스트를 가지고 있다.

같은 객체를 가리키고 있는 것이다.

함수 매개변수 전달 사용법

def add(x,y):
    x += y
    return x

x = 10
y = 5

print(add(x,y), x, y) # 15 10 5

add라는 함수하나를 만들어서 리턴값과 x , y의 변화를 살펴보자.

값의 변화가 없다.

a = [10, 100]
b = [5, 10]

print(add(a,b), a, b) # [10, 100, 5, 10] [10, 100, 5, 10] [5, 10]

이번에는 리스트를 넘겨보자.

띠용 a의 값이 변했다. 이는 mutable한 list의 특성상 원본의 주소 , 레퍼런스 주소를 넘겨서 원본이 수정된다. 파이썬에서는 함수 호출시 인자를 Call by assignment 방식으로 값을 불러온다. 이 방식은 함수 호출때 Call by reference로 불러들여서 mutable인지 immutable인지에 따라 다르게 동작하게 한다. 이때 mutable객체라면 Call by reference로 계속 동작하지만 immutable일 때는 call by value로 동작한다. 따라서 mutable객체인 리스트의 경우 주소자체를 넘겨주어 값이 변해버리는 것이다.

print(add(c,d), c, d) # (10, 100, 5, 10) (10, 100) (5, 10)

반대로 immutable객체인 tuple은 원본이 변하지 않는다.

파이썬 immutable(불변형) 예외

파이썬에서 불변형인데 복사하지 않고 같은 주소값을 참조하는 예외가 몇개 있다.

str, bytes, frozenset, Tuple : 사본 생성 x → 참조 반환

tt1 = (1,2,3,4,5)
tt2 = tuple(tt1)
tt3 = tt1[:]

print('EX7-1 : ', tt1 is tt2, id(tt1), id(tt2))
print('EX7-1 : ', tt1 is tt3, id(tt1), id(tt3))

tt1은 튜플 tt2는 자료형을 활용하여 tt1을 복사했다. 원래대로라면 다른 id값을 가질 것이다.

그리도 tt1[:]을 슬라이싱하여 tt3에 저장

결과를 보면 알 수 있지만, 모두 같은 참조 값을 가지고 있다. 어차피 변경불가능하니 사본생성을 하지 않고 참조 반환을 하는것이다.

tt4 = (10,20,30,40,50)
tt5 = (10,20,30,40,50)

ss1 = 'Apple'
ss2 = 'Apple'

print('EX7-3 : ', tt4 is tt5, tt4 == tt5, id(tt4), id(tt5))
print('EX7-4 : ', ss1 is ss2, ss1 == ss2, id(ss1), id(ss2))

출력된 결과물을 보면 모두 같은 값을 가지고 있다.

효율성을 위해서 그럼 ㅇ.ㅇ

댓글