파일을 줄별로 저장할 때 - 예시

from openpyxl import *
from openpyxl.styles import Font, Alignmnet
from openpyxl.utils import get_column_letter


# excel sheet 시작하기
wb = Workbook()

# sheet 만들기 (기본적으로 1개 제공 추가 만들기)
worksheet = wb.create_sheet(sheetname)

# sheet 1개 더 만들기
worksheet1 = wb.create_sheet(sheetname1)

# field 이름 설정하기
field_name = ['이름', '나이', '주소', '연락처']

# field width 설정하기
field_width = ['10', '5', '30', '20']

# 특정 넣을 값을 만드는 로직 추가
member_list = Member.objects.all()


# 먼저 field 설정해주기 

for i, head in enumerate(field_name):
    # cell(1)을 해줘야지 가장 위에 줄에 입력해준다.
    # 해당 필드의 width 설정해주기
    worksheet.column_dimensions[get_column_letter(i + 1)].width = field_width[i]
    # 해당 필드의 값 주기
    worksheet.cell(1, i + 1).value = head
    # 폰트 설정해주기
    worksheet.cell(1, i + 1).font = Font(name='맑은 고딕', size=13, bold=True)
    # 폰트 위치 설정해주기 (정중앙정렬)
    worksheet.cell(1, i + 1).alignment = Alignment(horizontal='center', vertical='center')


# 해당 field들에게 값 주기
for i, data in enumerate(member_list):
    # i + 2를 해줘야지 2번째 줄부터 입력해준다.
    worksheet.cell(i + 2, 1).value = data.name
    worksheet.cell(i + 2, 2).value = data.age
    worksheet.cell(i + 2, 3).value = data.address
    worksheet.cell(i + 2, 4).value = data.phone


# 가장 기본적으로 셋팅되어 있는 sheet 삭제
wb.remove(wb['Sheet'])

# 저장하기
wb.save('member_name.xlsx')

 

파일을 읽어서 처리할 때 - 예시

 

import openpyxl


def open_pyxl():
    not_matching_membmer = []
    multiple_member = []
    what_error = []
    
    coupon = Coupon.objects.get(id=636)
    success_count = 0


    wb = openpyxl.load_workbook('account.xlsx')  # 해당 파일 불러오기
    ws = wb.active # 활성화 시트가지져오기
    wr = ws.rows # 열별로 가져오기 한줄 한줄 가져오기
    for i in wr:
        try:
            account = i[0].value (해당 줄의 첫 번째 행의 값 가져오기)
            member = Member.objects.get(account=account)
            coupon.issue([member])
            print('coupon _issue success : ', member.id, ' id: ', member.account)
            success_count += 1
        except Member.DoesNotExist:
            not_matching_membmer.append(account)
        except Member.MultipleObjectsReturned:
            multiple_member.append(account)
        except Exception as e:
            dict = {}
            dict[account] = str(e)
            what_error.append(dict)


    print('성공 갯수 :', success_count, '매칭 맴버 x : ', not_matching_membmer, '중복 멤버 :',
          multiple_member, '알 수 없는 에러 :', what_error)

 

python에서 비교 연산자를 할 때 가장 많이 쓰는 것은 == 이다.

또 is를 쓸 수도 있다.


하지만 이번에 아무 생각 없이 is를 사용하였는데, 그냥 넘어가는 케이스가 있어서 당황하게 되었다.

예를 들어 설명해보면

if ga_uid is not 'anonymous':
    print('here')

이런 경우 ga_uid에 분명 'anonymous'가 들어왔는데도

그냥 print문이 찍히게 되었다.

 

그래서 알아보니 is에 이런 설명이 있었다.

1. 'is'는 비교 연산자가 맞다. 하지만 중요한 건 변수의 값을 비교하는게 아니라 레퍼런스(C식으로 설명하자면 변수의 포인터)를 비교하는 연산자다.

2. Python의 변수는 내부적으로 데이터 그 자체를 가리키는게 아니라 인스턴스 포인터(값이 저장되어 있는 메모리의 주소, 즉 레퍼런스)를 가리킨다. 물론 C언어가 아니기 때문에 변수를 포인터처럼 엑세스 할 수는 없다.

 

이 말은 즉 memory에 올라간 주소를 체크한다는 것인데, 파이썬에서는 자주 쓰이는 것은 미리  memory에 올려놓고 사용한다.

 

따라서

a is a
True


라고 나온다. a는 파이썬에서 미리 memory에 올려놓았었기 떄문이다.

하지만 anonymous는 새롭게 memory에 올라가게 되고 이런 경우 ga_uid가 'anonymous'라고 해도 해당 부분에서 걸리지 못하게 되는 것이다.

 

따라서 is 커맨드는 되도록이면 None, True, False와 같은 것을 비교할 때만 하는게 좋을 것 같다.

 

그에 비해 ==은 정말 같은지를 비교하는 것이라 되도록 ==를 사용하는게 좋을 것 같다.

 

참고 

[[Python] 'is' 커맨드 - Seorenn SIGSEGV](http://seorenn.blogspot.com/2011/04/python-is.html)

[operators - Python != operation vs "is not" - Stack Overflow](https://stackoverflow.com/questions/2209755/python-operation-vs-is-not)

상황 : 

특정 통신을 하는 부분에서 평소에는 잘 일어나지 않지만, 특정 케이스로 통신하는데 10초 이상 걸려 걸려 있던 lock이 풀리는 케이스가 생겨버렸다. 해당 경우 통신하는 함수에 timeout을 걸어, 특정 시간 이상 회신이 오지 않게 되면 다시 retry를 하여서 넘어가도록 하였다.

그를 위해서 python function timeout을 찾아보게 되었다.

처음 고민은 파이썬은 한 줄씩 실행되는데, 이게 오래 끌고 있는지 어떻게 확인하지였다.

보통은 함수 시작 전에 print 문으로 시간을 적어주고 함수가 실행 이후에 print문을 찍어주어 시간을 측정할 수 있지만, 이번 케이스는 함수가 실행 도중에 시간을 넘게 되면 retry해주는 것을 체크해줘야 하므로, 당황 스러웠다.

생각나는 것은 파이썬의 thread를 같이 돌리거나 해야 할 것 같은데 매우 비효율적으로 보였다.

그러던 중에 signal이라는 것을 알게 되었다.

사용법은 간단하였다.

직접 만들어서 쓸수도 있지만 나는 앞으로도 쓸 것 같아 데코레이터를 만드는 방법을 택했다.

직접 만들고 보니 신기하다.

 

from functools import wraps
import errno
import os
import signal

class TimeoutError(Exception):
    pass

def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
    def decorator(func):
        def _handle_timeout(signum, frame):
            raise TimeoutError(error_message)

        def wrapper(*args, **kwargs):
            signal.signal(signal.SIGALRM, _handle_timeout)
            signal.setitimer(signal.ITIMER_REAL,seconds) #used timer instead of alarm
            try:
                result = func(*args, **kwargs)
            finally:
                signal.alarm(0)
            return result
        return wraps(func)(wrapper)
    return decorator


# 여기서 부턴 개인적인 testing함수
@timeout(2)
def testing():
    import time
    count = 0
    while count < 3:
        print('helloworld', count)
        count += 1
        time.sleep(1)
    return count

위에 나온 부분이 어디서든 검색해보면 볼 수 있는 timeout decorator를 만드는 방법이다.

나는 testing이라는 특정함수를 2초 안에 실행되지 못하면 5회 다시 시도할 것이고,

그래도 안되면 이후 모션들을 취할 예정이다.

count = 0

while count < 5:
    try:
        testing()
        break
    except Exception as e:
        print('timeout occur', str(e))
        count += 1

실행 결과
helloworld 0
helloworld 1
time out occur Timer expired
helloworld 0
helloworld 1
time out occur Timer expired
helloworld 0
helloworld 1
time out occur Timer expired
helloworld 0
helloworld 1
time out occur Timer expired
helloworld 0
helloworld 1
time out occur Timer expired

# testing 함수가 끝나기 위해서는 3초가 필요한데,
# timeout은 2초를 걸어줘서 이런 증상이 발생한다.

testing 함수가 끝나기 위해서는 3초가 필요한데, timeout은 2초를 걸어줘서 이런 증상이 발생한다

이를 위해 만약 timeout 시간을 5초 이렇게 설정해주면

@timeout(5)
def testing():
    import time
    count = 0
    while count < 3:
        print('helloworld', count)
        count += 1
        time.sleep(1)
    return count

# 하고 다시 실행시켜주면

helloworld 0
helloworld 1
helloworld 2

나오고 끝나게 된다. => 5초 exception이 일어나기 전에 해당 함수가 끝나고 곧바로 break함수를 타고 빠져나오게 됨

 

참고 사이트 :

[[PYTHON] 파이썬에서 함수를 타임 아웃하는 방법, 타임 아웃이 1 초 미만입니다. 복붙노트](https://cnpnote.tistory.com/entry/PYTHON-%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%97%90%EC%84%9C-%ED%95%A8%EC%88%98%EB%A5%BC-%ED%83%80%EC%9E%84-%EC%95%84%EC%9B%83%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-%ED%83%80%EC%9E%84-%EC%95%84%EC%9B%83%EC%9D%B4-1-%EC%B4%88-%EB%AF%B8%EB%A7%8C%EC%9E%85%EB%8B%88%EB%8B%A4)

[[Python] 함수(스크립트) 자동 타임아웃 설정 : 네이버 블로그](https://m.blog.naver.com/PostView.nhn?blogId=stop2y&logNo=221370575532&proxyReferer=https%3A%2F%2Fwww.google.co.kr%2F)

[python - 함수가 완료되는데에 너무 오래 걸릴 때, timeout시키기 | Hashcode](https://hashcode.co.kr/questions/2203/%ED%95%A8%EC%88%98%EA%B0%80-%EC%99%84%EB%A3%8C%EB%90%98%EB%8A%94%EB%8D%B0%EC%97%90-%EB%84%88%EB%AC%B4-%EC%98%A4%EB%9E%98-%EA%B1%B8%EB%A6%B4-%EB%95%8C-timeout%EC%8B%9C%ED%82%A4%EA%B8%B0)

 

zip

=> 순회 가능한 시퀀스형이나 이터레이터형 객체들을 결합하여 쌍으로 순회가능한 이터레이터 객체를 얻을 수 있다.

x = [10,20,30]
y = ["a","b","c"]

for i in zip(x,y):
	print(i)
    
(10, "a")
(20, "b")
(30, "c")

list(zip(x,y))

list
[(10,"a"),(20,"b"),(30,"c")]

range() -> 수열의 생성이다.

range(시작,끝,증가값)

대신 시작값과 증가값은 따로 넣지 않아도 된다.

list(range(10))

=> [0,1,2,3,4,5,6,7,8,9]

list(range(1,10,2)

=> [1,3,5,7,9]

 

리스트 항목과 인덱스 값을 동시에 얻는 법

enumerate()

=> 튜플로 해당 인덱스의 값과 리스트값을 동시에 반환

li = ['apple', 'see']

for i in enumerate(li):

    print(i)

(0, 'apple')

(1, 'see')

 

filter

filter(필터링할 방법, 특정 집단)

ex

li = [10,20,30]

NewL = list(filter(lambda i: i >20, li)

NewL 

[30]

 

map

=> 시퀀스형 객체를 순회하면서 모든 값을 갱신해야 할 때 사용

li = [1,2,3]

LI = list(map(lambda i: i+10), li))
LI
[11, 12, 13]

 

 

2019.08.07 파이썬 인자 및 매개변수

# 질문에 답하기

1.

파이썬 매개변수(parameter)와 실행인자(argument)

  • 매개변수 : 함수를 정의할 때 사용하는 이름
  • 실행인자 : 함수를 실행할 때 사용하는 변수 혹은 값
def fucntion(a, b)
    print("""{a}, {b}""".format(a=a, b=b))

function('byeonguk', 'hi')
  • 여기서 a와 b는 매개변수이고
  • 'byeonguk', 'hi'는 실행인자이다.

파이썬 함수 인자 (Arguments)

  • default value(기본값)을 주어서 값을 넘겨 줄 수 있다.
  • 기본 default값을 활용하여 2개의 매개변수가 필요해도 1개의 인자만 받을 수도 있다.
def fucntion(a, b='hello')
    print("""{a}, {b}""".format(a=a, b=b))

fucntion('byeonguk')

byeonguk, hello

fucntion('byeonguk', 'hi')

byeonguk, hi

파이썬 위치 인자(Position argument)

  • 기본적으로 매개변수에서 실행인자를 받는 것은 순서대로 받는다.
  • 해당 순서를 지키고 싶지 않을 때는 키워드 인자를 지원한다.

파이썬 키워드 인자(keyword argument)

  • 해당 키워드 인자는 특정 매개변수에 특정 인자를 사용하겠다고 지정해주는 것이다.
  • 위의 경우를 예로 들어서 추가 설명해보면
    function(b="byeonguk", a="hi")

hi, byeonguk

  • 위처럼 순서를 지키고 싶지 않거나, 그러기가 힘든 경우 키워드 인자를 활용해서 특정 인자를 지정해서 넣어줄 수 있다.

함수의 인자 수와 입력하는 매개변수의 수가 다르면 에러가 발생

  • 3개의 매개변수를 필요로 하는데, 2개의 인자만 입력해주면 에러 발생

따라서 만약에 매개변수에 2개 이상의 None이 섞이게 되면..

  • 꼭 넘겨 줄 때 키워드 인자를 활용해서 특정 부분을 지정해주자!

2019.08.07 파이썬 에러메시지(try, escept, else, finally

# 질문에 답하기

1.

try, except를 통한 에외처리

  • 파이썬에서 특정 구문을 실행하고 싶은데, 혹시라도 오류가 날까 두려울 때.
  • 어떤 오류가 날지 모를 때
  • try, except를 통해서 해당 문제를 해결할 수 있다.

try, except

try:
    print('이것부터 실행해봅니다.')

except:
    print('위에 try 구문이 안될 시 이것을 실행합니다')
  • 원래는 except 뒤에 일어날 에러명을 적어주면 된다.
  • 하지만 단순히 예외처리를 위한 것이라면 굳이 적어주지 않아도 되는 것 같다.

특정 경우 에러 일으키키 (raise)

  • raise를 통해 에러를 일으킬 수 있다.
  • try, except을 통해서 좀 더 효율적으로 쓸 수 있다.
try:
    if err_code != 0:
        raise Exception

except:
    print('err_code가 0이 아닙니다')
  • 해당 부분이 아니면 raise Exception 에러를 일으켜서 except로 넘어 간다.

파이썬 에러와 예외에 대한 좋은 자료

try와 except, else, finally를 통한 예시

def divide(x, y):
    try:
        result = x / y
    except:
        print('0으로 나눌수가 없어요')
        return False
    else:
        print('결과: ', result)
        return True
    finally:
        print('나누기  연산 종료')
  • try를 해보고 가능하면 else로 넘어가서 해당 결과값에 대해 출력한다.
  • finally는 항상 끝나기 전에 무조건 실행된다.
  • finally를 잘 활용하면 왠지 더 좋은 결과물을 낼 수 있을 것 같다.

12A9EF95-FE55-4858-AAAE-AD6579CB8370

2019.04.29 CSV파일 및 TXT파일 읽기, 저장하기, 쓰는 방법

# 질문에 답하기

  1. CSV파일 다루는 방법
  2. TET파일 다루는 방법

기본 전제

기본적으로 CSV와 TXT의 형태가 비슷하다.

CSV 다루기

  • CSV는 comma-separated value(쉼표로 구분된 텍스트)
  • 데이터를 저장하고 공유하는 매우 간편한 포맷이다.
  • 데이터가 일반 텍스트로 저장되어 어디서나 데이터를 쉽게 다룰 수 있다.
  • 단 따로 자료형이 없는 원시데이터이다.

CSV 열기(파일이 없다면 자동생성된다)

  • f = open('파일명', 'w', newline='')
  • w는 나는 write할 것이다를 알려줌
  • newline은 write할 때 줄 바꿈이 2줄씩 일어나지 않도록 해줌

CSV 데이터 입력하기

  • f.write('1,2,3\n')
  • f.write('4,5,6')
  • 마지막에 \n을 해주지 않으면 덮어쓰게 된다.

CSV 파일 저장하기

  • f.close()

csv.py

csv

1.csv

txt

TXT 다루기

  • 가장 간단한 에디터이다.

TXT 열기 및 생성

  • f = open('파일명', 'w', encoding = 'utf-8')
  • w는 나는 write할 것이다를 알려줌
  • w : 쓰기 모드
  • r : 읽기 모드
  • a : 추가모드 (마지막에 새로운 내용을 추가할 때 사용)
  • encoding은 한글이 깨지지 않도록 해준다.

TXT 데이터 입력하기

  • f.write('안녕하세요\n')
  • f.write('처음 연습합니다.')

TXT 파일 저장하기

  • f.close()

TXT 파일 읽기(read 모드)

  • readline : 한 줄 읽어오기
    f = open('text.txt', 'r' )
    line = f.readline()
    print(line)
    f.close()
  • readlines : 모든 줄 읽어오기
    f = open('text.txt', 'r' )
    lines = f.readlines()
    for line in lines:
      print(line)
    f.close()

새로운 내용 추가하기

  • 모드를 a로 연다.
    f = open('text.txt', 'a')
    for i in range(11, 20):
      data = "%d번째 줄입니다.\n" % i
      f.write(data)
    f.close()
  • 11번째줄부터 20번째 줄까지 데이터 삽입

open, 데이터 삽입 , close까지 한번에 처리하기

  • with로 처리하기
    f = open("foo.txt", 'w') 
    f.write("Life is too short, you need python") 
    f.close()
    이것을
with open("foo.txt", "w") as f:
    f.write("Life is too short, you need python") 

이렇게 한번에 적어줄 수 있다.
단 파이썬 2.5부터 지원한다.

2019.04.25 동적크롤링(Ajax, JSON)

# 질문에 답하기

  1. 동적크롤링
  2. Ajax
  3. JSON

동적크롤링의 큰 그림

  • 자바스크립트로 동작되는 애들은 지속적으로 데이터를 가지고 와서 업데이트 한다.
  • 따라서 현재 보이는 데이터를 기준으로 하면 안되고 그 원본 데이터의 주소를 찾아야 한다.
  • 먼저 해당 데이터를 불러오는 url을 찾는 것이 중요한데 그것은 network에서 확인할 수 있다.
  • XHR을 선택하면 아이젝스로만 활동하는 것을 확인할 수 있고, 거기서 해당 내용을 불러오는 url을 구성한다.
  • 그리고 그 url을 통해서 크롤링을 진행해야 하며 동적데이터이기 때문에 이럴 때 이용해야 하는 것은 json이다.

Introduction

  • 브라우저(웹클라이언트)에서 웹페이지를 요청하거나 링크를 클릭하면 화면전환이 발생하는데 이것은 브라우저와 서버와의 통신에 의한 것이다.
  • 서버는 요청받은 페이지(HTML)를 반환하는데 이때 HTML 뿐만 아니라 CSS 및 javascript파일들도 함께 반환한다.
  • 웹페이지가 반환되면 브라우저(웹클라이언트)는 page를 reload하여 화면을 보여준다.
  • 서버는 요청받은 페이지가 무엇이냐에 따라 정적인 파일들만 반환할 수도 있고, javascript가 섞인 동적인 파일들을 반환할 수도 있다.

Ajax(Asynchronous javascript and XML)

  • Ajax는 자바스크립트를 이용하여 비동기적으로 서버와 브라우저가 데이터를 교환하는 통신 방식이다.
  • 서버에서 한번 페이지를 반환했을 때 거기에는 HTML와 같은 정적인 파일들 뿐만 아니라 javascript와 같이 동적인 파일들도 함께 반환한다.
  • 만약 페이지에서 일부만 지속적으로 갱신해야 한다면 굳이 모든 파일을 다시 반환할 필요가 없다.
  • 따라서 브라우저(웹클라이언트)는 Ajax request를 통해 동적인 부분을 요청하고 서버는 json을 통해 reload하는데 필요한 정보들을 반환 해준다.
동적크롤링

Ajax의 요청과 처리

  • 웹브라우저는 XMLHttpRequest 객체를 이용하여 Ajax요청을 보낸다.
  • 서버는 JSON형태로 요청온 정보만을 담아 순수한 텍스트로 구성된 데이터 문자열을 반환한다.
  • 이 문자열을 객체화하는 것을 역직렬화라고 하고 역직렬화를 위해 JSON.parse()를 이용한다.
동적크롤링1

크롤러

크롤러 : 인터넷에 있는 데이터를 자동 수집하는 프로그램

  • 웹 브라우저를 흉내내는 프로그램

정적 크롤링(크롬에서 페이지 소스보기가 가능할 때)

  • request: 웹 사이트에 접속, 데이터를 받아오는 역할
  • BeautifulSoup: 데이터를 HTML로 해석하는 역할

동적 크롤링

  • Ajax : 웹클라이언트에서 서버에 데이터를 요청하는 역할
  • JSON : 웹서버로부터 반환되는 데이터
  • JSON.parse() : 반환된 데이터를 사용할 수 있도록 객체화 해주는 역할

실습하기

  • 목표
    • 지속적으로 변하는 인기검색 10위까지의 순위 & 이름 & 현재시가 크롤링해보기
동적크롤링예제

코드

import requests
import json            #json import하기

#custom_header을 통해 아닌 것 처럼 위장하기
custom_header = {
    'referer' : 'http://http://finance.daum.net/quotes/A048410#home',
    'user-agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'  }

#해당 접속 사이트가 아닌 원본데이터가 오는 url 추적. network에서 가지고 온다.
url = "http://finance.daum.net/api/search/ranks?limit=10"

req = requests.get(url, headers = custom_header)    #custom_header를 사용하지 않으면 접근 불가

if req.status_code == requests.codes.ok:    
    print("접속 성공")
    stock_data = json.loads(req.text)        #json에 반환된 데이터가 들어가 있다.
    for rank in stock_data['data']:         #stock_data는 'data' key값에 모든 정보가 들어가 있다.
        print(rank['rank'], rank['symbolCode'], rank['name'], rank['tradePrice'])

else:
    print("Error code")

접속 성공
1 A001000 신라섬유 2350
2 A068270 셀트리온 211500
3 A048410 현대바이오 12650
4 A005930 삼성전자 44650
5 A207940 삼성바이오로직스 338500
6 A020560 아시아나항공 6460
7 A002210 동성제약 20150
8 A007460 에이프로젠 KIC 4000
9 A066570 LG전자 77000
10 A000660 SK하이닉스 80200

2019.04.25 정적크롤링(request,beautifulsoup4)

# 질문에 답하기

  1. 정적 크롤링
  2. request
  3. beautifulSoup

크롤링 : 웹 페이지에 있는 자료를 자동으로 수집하는 프로그램

  • robots.txt : 검색엔진에게 어디까지 검색을 허용할 것이냐?
  • 선행지식 : HTML에 대한 이해 CSS Selection을 만드는 방법
  • 정적페이지 크롤링(페이지 소스보기에서 보여질 때)
    • request와 beautifulsoup4를 이용해서 크롤링 및 해석한다.
  • 동적페이지 크롤링(실시간으로 데이터가 바뀔 때)
    • selenum혹은 ajax, josn을 이용하여 크롤링 및 해석한다.
  • 정적페이지 동적페이지 확인 방법
    • 본인이 크롤링 할려는 데이터가 페이지소스보기 했을 때 있는지 확인한다.
    • 검사 및 셋팅(f1)에 들어가서 DEBUGGER의 disable javascript를 한후 새로고침을 해본다.
      • 데이터가 나오지 않으면 javascript를 통해 데이터를 받아온다.
disablejavascript
  • 오른쪽 하단에 위치

브라우저가 혹은 크롤러가 어떤식으로 서버에 접근해서 데이터를 가져오는가?

  1. 주소를 입력하면 해당 서버로 접근한다.(url 필수)
  2. 웹서버 프로그램이 해당 주소에 맞는 내용을 전달한다.(by source code)
    1. request라는 모듈을 이용한다.
    2. request(urllib의 wrapper 클래스)
  3. 웹 브라우저는 받은 소스코드를 해석해서 화면에 보여준다.
    1. 크롤러는 받은 내용을 해석해서 내가 원하는 데이터를 뽑아낸다.
      1. BeautifulSoup 모듈 이용
      2. 해석해서 : html 코드의 해석, CSS Selecter 만드는 방법
    2. 웹브라우저와 크롤러의 차이

정적 크롤링 필요 모듈 설치

  1. requests : 웹페이지에 접근해서 데이터를 가져온다.
    1. pip3 install requests
    2. requests는 aiax로 받아온 데이터를 실시간으로 반영할 수 없다.
      1. 페이지소스코드 보기까지 가능하다.
      2. 내가 원하는 자료를 계속 못 받아온다고 하면 aiax로 구성되서 그런가보다 하고 알아야 한다.
      3. 해결방안으로 selenium : 웹 브라우저를 원격 조작하는 방식의 크롤러이다.
      4. 크롤링 모듈 : request, selenium, scrapy등 상황에 맞는 크롤링 모듈 정리
  2. BeautifulSoup4 : 가져온 데이터를 HTML로 해석한다.
    1. pip3 install BeautifulSoup4
  3. 사용하기
import requests
from bs4 import BeautifulSoup

크롤러의 이용

웹 브라우저를 그대로 따라하기 때문에

  1. 명절표 예매
  2. 콘서트 티켓 예매 등을 할 수 있다.
  3. 빠른 정보 취득
  4. 정보 취득 및 삽입

크롤러자체는 불법이 아니나, 내가 크롤러로 무료로 취득한 정보를 바탕으로 돈을 벌면 불법이다.

만약에 자바스크립트와 같은 것을 통해서 지속적으로 업데이트 되는 웹사이트라면

  • selenum보다는 request가 훨씬 빠르다.
  • selenum : 웹 브라우저 자체를 컨트롤해서 크롤링
    • 요소를 선택해서 사용자의 동작을 흉내낸다. : 클릭, 키보드 입력
    • 선택자 xpath, css
    • xpath : //*[@id="main_content"]/div[2]/div/dl/dt[2]/a
    • css : #main_content > div.list_body.newsflash_body > div > dl > dt:nth-child(2) > a

2019.04.25 크롤링 기본(HTML과 선택자 이해하기)

(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)

# 질문에 답하기

  1. 크롤링의 그림
  2. HTML이란?
  3. 선택자란?

크롤링의 큰 그림

  1. 우리가 가지고 오고 싶은 부분을 눈으로 확인하고 ( 첫 번째 줄을 가지고 와보자)
  2. HTML 구조를 분석해서 선택자를 선별하고 (태그는 div 이고 class는 1234 이네!)
  3. 선택자를 컴퓨터가 알아먹게 변환 (div.1234)

HTML의 개념과 구조 이해하기

  • HTML은 hypertext markup language로서 hypertext는 링크로서 한 문서에서 다른 문서로의 이동을 이야기하며 markup langage는 태그를 이용해서 문서를 작성한다는 이야기입니다. 즉 웹페이지를 만들기 위한 언어로서 문서간의 이동을 목적으로 만들어졌고 태그들을 이용하여 문서를 작성하는 언어입니다.
  • 기본적으로 태그들은 각각의 고유한 역할을 가지며 들여쓰기를 통해 내부 단계를 구현합니다.
  • 들여쓰기를 통해 부모와 자식구조를 표현하며 이를 도식화하면 나무와 닮아있기 때문에 트리구조라고도 한다.
  • 너무 좋은 내용이 있어서 가지고와 본다. Stage 1 - HTML에 대해 알아보자 - 네이버 함께 정복하는 크롤링 스터디

HTML 구조 연습하기

제품리스트

출처 : Stage 2 - 선택자 대해 알아보자 - 네이버 함께 정복하는 크롤링 스터디

  • 제품리스트
    • 첫번째 줄
      • 첫번째 상품
        • 상품명
        • 최저가
        • 상품평
        • 판매처
      • 두번째 상품
        • 상품명
        • 최저가
        • 상품평
        • 판매처

    • 두번째 줄
      • 첫번째 상품
        • 상품명
        • 최저가
        • 상품평
        • 판매처
      • 두번째 상품
        • 상품명
        • 최저가
        • 상품평
        • 판매처

데이터를 특정짓기 위한 선택자

  • html은 반복적으로 태그들이 쓰이기 때문에 컴퓨터는 특정 태그에 대해서 혼동이 올 수 있다.
  • 따라서 태그에 특정 별명을 붙이는데 이 별명을 선택자라고 하며 ID와 클래스가 가장 많이 쓰인다.
  • id는 고유한 식별자로서 한 ID는 html 문서에 하나밖에 존재하지 않는다.

컴퓨터에게 선택자 알려주기

  • 태그 : 그냥 태그명을 적어주면 된다.
  • ID가 1234 인 div 태그 : div#1234
  • class가 1234인 div 태그 : div.1234
  • 우리가 가지고 오고 싶은 부분을 눈으로 확인하고 그 부분의 html 태그를 해석하여 선택자를 선택하고 컴퓨터에게 선택자를 알려준다.

연습하기

크롤링2

출처 : Stage 2 - 선택자 대해 알아보자 - 네이버 함께 정복하는 크롤링 스터디

  1. 자식선택자
    1. 바로 밑의 선택자를 찾아야 할 때 사용
    2. 우측의 영상제목들만 가지고 오기
    3. div.cds>span.title
  2. 자손 선택자
    1. 자식선택자를 건너띄고 자손선택자를 가지고 오기
    2. div#container span.title
  3. 위의 2개 모두 불러오는 것은 같으나 불러오는 상황이 다르다.

선택자 선택하기

  1. 간결성
    1. 결국 선택자 경로상에서 가장 중요한 것은 우리가 원하는 데이터가 있는 마지막 선택자이다.
  2. 정확성
    1. 해당 태그가 그 태그만 속한 것이 맞는지 확인이 필요하다.
      1. <span class ="hit"> ===> 컨트롤 + F 를 통해 span.hit 을 검색해본다.
  3. 세분화
    1. 어디까지의 조상을 결정 할지를 잘 확인하자.

2019.04.24 TIL

(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)

# 질문에 답하기

  1. 랜덤 숫자 추출 방법
  2. key값 기준으로 정렬하기
  3. value값 기준으로 정렬하기

랜덤 숫자 추출

import random

  1. random.randrange()
    1. random.randrange(1, 7) : 1이상 7 미만의 난수 생성
    2. random.randrange(1, 101, 2) : 1부터 100사이의 임의의 짝수
  2. random.randint()
    1. random.randint(1, 100) : 1부터 100사이의 임의의 정수

자료 섞기

  1. abc = ["a", "b", "c", "d", "e"]
  2. random.shuffle(abc)

아무 원소나 하나 뽑기

  1. abc = ["a", "b", "c", "d", "e"]
  2. random.choice(abc)

key값 기준으로 정렬하기

  • import operator
  • sorted(dic.items(), key=operator.itemgetter(0))
import operator
numbers_count = {1: 122, 2: 112, 3: 111, 4: 115, 5: 119, 6: 107, 7: 114, 8: 118, 9: 88, 10: 120, 11: 120, 12: 123, 13: 121, 14: 122, 15: 116, 16: 109, 17: 123, 18: 121, 19: 118, 20: 124, 21: 112, 22: 91, 23: 99, 24: 114, 25: 111, 26: 116, 27: 126, 28: 104, 29: 106, 30: 105, 31: 118, 32: 96, 33: 121, 34: 133, 35: 105, 36: 115, 37: 117, 38: 110, 39: 116, 40: 125, 41: 103, 42: 108, 43: 123, 44: 112, 45: 121}

# 오름차순 정리
sorted_num = sorted(number_count.items(), key=operator.itemgetter(0))
# 역순으로 정리
sorted_num = sorted(number_count.items(), key=operator.itemgetter(0), reverse=True)  

value값 기준으로 정렬하기

  • import operator
  • sorted(dic.items(), key=operator.itemgetter(1))
import operator
numbers_count = {1: 122, 2: 112, 3: 111, 4: 115, 5: 119, 6: 107, 7: 114, 8: 118, 9: 88, 10: 120, 11: 120, 12: 123, 13: 121, 14: 122, 15: 116, 16: 109, 17: 123, 18: 121, 19: 118, 20: 124, 21: 112, 22: 91, 23: 99, 24: 114, 25: 111, 26: 116, 27: 126, 28: 104, 29: 106, 30: 105, 31: 118, 32: 96, 33: 121, 34: 133, 35: 105, 36: 115, 37: 117, 38: 110, 39: 116, 40: 125, 41: 103, 42: 108, 43: 123, 44: 112, 45: 121}

# 오름차순 정리
sorted_num = sorted(number_count.items(), key=operator.itemgetter(1))
# 역순으로 정리
sorted_num = sorted(number_count.items(), key=operator.itemgetter(1), reverse=True)  

2019.03.28 TIL

(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)

# 질문에 답하기

  1. iterator
  2. generator

전체적인 그림

iterator generator

Iterator

  1. next()함수에 의해서 값을 하나씩 반환한다. (next()함수 호출시점)
  2. Stopiteration

list의 iterator

>>>    li = [1,2,3,4,5]
>>>    it = item(li)        # list의 내장함수 dir(list)를 통해 알 수 있다.
>>>    print(type(it))
list_iterator
>>>    next(it)         #li의 값을 1부터 하나씩 반환한다.
>>>
>>>    for e in li:
>>>        print(e)            #이거를 실행하는 순간 내부적으로 iter(li)가 만들어져서 하나씩 반환한다.
  • 실제로 list는 미친듯이 느리다.
  • 따라서 필요에 의해 list를 iterator로 바꾸어 사용한다.

나만의 iterator 만들기

>>>
>>>    class Myiter:
>>>        def __init__(self.li):            
>>>            self.continer = li
>>>            self.index = 0
>>>
>>>        def __iter__(self):
>>>            return self
>>>
>>>        def __next__(self):
>>>            if self.index >= len(self.continer):
>>>                raise StopIteration
>>>            ret = self.container[self.index]
>>>            self.index += 1
>>>            return ret
>>>
>>>    it_obj = Myiter([1,2,3,4,5])
>>>
>>>    next(it_obj)
1
>>>    next(it_obj)
2
>>>    it = iter(it_obj)    #중간에 iter을 만들어도 기존에 것 해당x
>>>    type(it)
__main__.Mylter
>>>    next(it)
3

파일을 불러와 iterator 추출하기

>>>
>>>    class Reader:
>>>        def __init__(self, filename):
>>>            self.f = open(filename, 'rt')
>>>        
>>>        def __iter__(self):
>>>            return self
>>>
>>>        def __next__(self):
>>>            a = self.f.readline()
>>>            if a == '':
>>>                self.f.close()
>>>                raise StopIteration
>>>            print(a)
>>>            

Generator

제너레이터는 특별한 종류의 이터레이터이다.

  • 제너레이터 역시 함수이다. 독특한 함수이다.

  • 함수 body안에 yield라는 키워드가 있어야 한다.(이게 나오면 자동으로 generator이다)

  • next와 yield이 서로 서로에게 실행을 넘겨주며 호출한다.

  • yield는 양보하다 라는 뜻을 가지고 있다.

    • 제너레이터 에러 막기
    • 제너레이터로 피보나치 만들기
    • coroutine function
    • send()
    • yield from

제너레이터 에러 막기

>>>    def gen():
>>>        print('gen start')
>>>        yield 1
>>>        print("a")
>>>        yield 2
>>>        print("b")
>>>        yield 3
>>>        return 'done'
>>>
>>>    g = gen()    #제너레이터 객체가 생성된다. 실행x
>>>    print(g)
<generator object gen at 0x1060a85c8>
>>>
>>>    next(g)   # 함수를 위에서 내려오며 yield을 만날 떄까지 진행
gen start    # yield가 1을 value로 반환한다.
>>>
>>>    print(next(g))
a           # yield 1을 반환하며 blocking되고 그 이후부터 실행
2              # yield 2를 만나면서 2를 반환하다.
>>>    
>>>    next(g) #b를 print하며 3을 반환하고 blocking
b
>>>
>>>    #next(g)를 하게 되면 StopIteration error가 발생
>>>    #에러를 막기 위해 try exception을 이용
>>>    
>>>    try:
>>>        next(g)
>>>    except StopIteration as exc:
>>>        print exc.value
>>>        pass
>>>    
c
done
>>>

제너레이터로 피보나치 구현하기

>>>    def fib():
>>>        prev, curr = 0, 1
>>>        while True:
>>>            yield prev    #prev 넣으면 0부터 curr 을 넣으면 1부터
>>>            prev, curr = curr, prev+curr
>>>
>>>    f = fib()
>>>    for i in range(10):
>>>        print(next(f), end = ' ')
0 1 1 2 3 5 8 13 21 34
>>>

coroutine function

  • 원래 함수는 실행할 때 스텍프레임이 생성되었다가 종료되면 스텍프레임이 없어진다. 하지만 제너레이터를 보면 실행 중에 멈추어서 다른 일을 하다가 또 호출을 해도 그 이전의 진행했던 사항들을 기억하고 있다. 이것을 가능하게 해주는 것이 coroutine function이다.

함수 실행 도중에 실행 주도권을 다른 함수에 넘길 수 있고, 내가 원하는 시점에 다시 실행 주도권을 가져올 수 있는 함수!!

함수 실행 도중이라는 것은 스텍프레임을 쌓아놓고 다른 곳에 실행주도권을 주는데 그렇게 하기위해 어디에 스텍프레임이 살아 있어야 한다.
명시적으로 실행주도권을 넘긴다.

원래는 os의 스케줄러가 라운드 로빈을 통해 선점형으로 실행되는데 내가 원하는 때 준다는 것은 비선점형이다.

중요한 것은 파이썬의 스텍 프레임이 heap에 할당된다는 것이다. 파이썬의 스텍프레임이 실행주도권을 넘기더라도 살아있을 수 있다. (힙에 저장되어 있으므로)
다른 언어에는 coroutine이 없는가라는 질문이 있을 수 있다.

c나 c++에서는 언어차원에서는 지원하지 않으나,

c : finite state machine기법 -> FSM

  1. 엔트리포인트의 위치를 적절하게 저장하여서 구현(실제로는 스텍프레임이 사라지지만 실행의 순서가 이어지듯이 구현

c++ : stack swapping

  1. 이미 스텍프레임에 쌓인 것은 어쩔수 없지만 heap영역에 마음대로 쌓을 수 있다. stack와 heap이 서로 오가며 스텍프레임을 쌓음

위와 같은 방법을 통해 해결한다.

파이썬의 코루틴은 generator 기반으로 만들어졌고 파이썬의 스텍프레임이 heap에 저장되므로 가능하다.

send()

  • send()는 제너레이터의 내장함수이다.
  • next는 실행주도권을 넘기는꺼지 가능하지만 send를 통해 실행주도권뿐만 아니라 데이터까지 함께 넘길 수 있다.

코드를 통해 send를 알아보자.

>>>    def gen():
>>>        print('gen start')
>>>        deta1 = yield 1  #yield 1이 실행되면 blocking이 걸리므로 아직 data1 변수는 만들어지지 않는다.(오른쪽에서 왼쪽으로 이동)
>>>        print(f"gen[1] : {data1}")
>>>        data2 = yield 2
>>>        print(f"gen[2] : {data2}")
>>>        return 'done'
>>>    g = gen()
>>>    g
generator object gen at 0x1060a8a40
>>>
>>>    first = g.send(None)     #보통 시직할 때는 None을 넣어준다. 
gen start
>>>
>>>    first       #yield 1로부터 1을 받아온다.
1
>>>
>>>    second = g.send('world')     #yield 1에 다시 'world'를 보내준다.
gen[1] : world
>>>    second # yield 2로부터 2를 받아온다.
2 
>>>
>>>    try:
>>>        g.send('hello')
>>>    except StopIterator as exc:
>>>        print(exc.value)
gen[2] : Hello
done
>>>

yield from

  • delegate(중계)만을 한다.
  • 아직까지 명확하게 어떻게 사용해야 할지는 감이 오지 않는다.
>>>    def gen():
>>>        print('gen start')
>>>        deta1 = yield 1
>>>        print(f"gen[1] : {data1}")
>>>        data2 = yield 2
>>>        print(f"gen[2] : {data2}")
>>>        return 'done'
>>>    
>>>    def delegate():
>>>        g = gen()
>>>        print('start')
>>>        ret = yield from g   #g는 따로 관여하지 않고 delegate만 한다. gen()
>>>        print('end')
>>>        print(f'return value:{ret}')
>>>        return ret
>>>
>>>    g = delegate()
>>>    print(g)
generator object delegate 
>>>    first = g.send(None)       #yield from g를 받게 되면 gen으로 이동하며 gen실행이 끝날 때까지 다시 delegate로 내려 올 수 없다.
start
gen start
>>>    print(first)
1
>>>    second = g.send('world')
gen[1] : world
>>>    second
2
>>>    g.send('hello')
gen[2] : hello
end
return value : done

StopIteration  Traceback (most recent call last)
<ipython-input-130-40ccd6fc328d> in <module>
----> 1 g.send('hello')

StopIteration: done
>>>
  • yield from에 대해서는 어떤 상황에 쓰는지 좀 더 공부를 해봐야 할 것 같다.

2019.03.23 TIL

(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)

# 질문에 답하기.

  1. 단순 객체 복사/ 얕은 복사 / 깊은 복사에 대해 설명하기
  2. 단순 객체 복사/ 얕은 복사 / 깊은 복사 만드는 방법

코드를 통해 알아보는 복사

단순 객체 복사

>>>    #단순 객체 복사
>>>    li = [1,2,3,4]
>>>    li2 = li1
>>>    li2   #[1,2,3,4]
>>>    li2.append(5)    
>>>    li2   #[1,2,3,4,5]
>>>    li   #[1,2,3,4,5]
>>>

li라는 변수는 [1,2,3,4]라는 메모리에 올라온 객체를 가르키고 있다.
li2 = li을 통해 li2는 li의 변수를 복사해온다.
따라서 li2와 li은 같은 객체를 가르키고 있으므로 li2와 li1 가르키는 객체는 완벽하게 동일하다. 따라서 li2를 통해 변경을 하게 되면 li 객체 역시 완벽하게 변경된다.

얕은 복사

>>>    #얕은 복사
>>>    li = [1,2,3,4]
>>>    li3 = li.copy() # or copy.copy(li)도 가능
>>>    li3     #[1,2,3,4]
>>>    li3.append(5)
>>>    li3      #[1,2,3,4,5]
>>>    li       #[1,2,3,4]
>>>

위의 내용을 이해하기 위해서는 단순히 객체가 [1,2,3,4]의 형태처럼 메모리에 올라가 있다고 생각하면 안된다.
실제적으로 파이썬에서 list를 생성한다고 하더라도 모두 모여서 메모리에 올라가지 않는다. [* , * , * , * ]와 같은 형태로 생성되어 첫번째 * 은 1을 가르키고 두 번째 * 은 2를 가르키고 이런 형식이다. 얕은 복사는 [* , * , * , * ]를 복사해 온 것이다. 따라서 li3는 li의 [* , * , * , * ]를 복사해온 것이다. 따라서 li3에서 append를 통해 새로운 요소를 추가하게 되면 li3의 [* , * , * , * ]가 [* , * , * , * , * ]가 되어서 마지막 * 가 새로운 요소를 가르키게 된다. 따라서 li3가 변경되더라도 li는 변경되지 않는다.
하지만 여기에서도 예외는 있다.

>>>    #얕은 복사
>>>    li = [1,2,3,[4,5]]
>>>    li3 = li.copy()
>>>    li3    #[1,2,3,[4,5]]
>>>    li3[3].append(6)
>>>    li3    # [1,2,3,[4,5,6]]
>>>    li     # [1,2,3,[4,5,6]]
>>>

위에서 보면 알 수 있듯이 원래 얕은 복사에서라면 li3를 바꾼다고 해서 li가 바뀌어서는 안된다. 하지만 li는 변경되었다. 그렇다면 li3는 왜 변경되었을까? 위에 언급한대로 li3는 li의 [* , * , * , * ]를 복사해 온 것이다. 위에 예시에서 마지막 * 은 3번째 인덱스 [4,5]을 가르키고 있다. li3와 li 모두 [4,5]을 가르키고 있는 것이다. 따라서 li3를 통해 [4,5]를 변경하게 되면 마지막 *가 가르키는 것이 변경되는 것이므로 li 역시 변경되게 된다.(즉 li와 li3의 내부리스트는 각은 객체를 참조하기 떄문이다.)

이러한 것을 맡기 위해서는 깊은 복사가 필요하다.

깊은 복사

>>>    import copy     #deepcopy를 위해서는 copy를 import 해주어야 한다.
>>>    li = [1,2,3,4]
>>>    li4 = copy.deepcopy(li)
>>>    li4        # [1,2,3,4]
>>>    li4.append(5)
>>>    li4        # [1,2,3,4,5]
>>>    li         # [1,2,3,4]
>>>    li = [1,2,3,[4,5]]
>>>    li4 = copy.deepcopy(li)
>>>    li4        # [1,2,3,[4,5]]
>>>    li4[3].append(6)
>>>    li4        # [1,2,3,[4,5,6]]
>>>    li         # [1,2,3,[4,5]]

깊은 복사를 통해서는 완전히 동일한 새로운 객체를 생성하는 것이므로 li4에 어떤 요소를 추가해도 li는 전혀 영향을 받지 않는다. 따라서 상황에 맡게 단순 객체 복사, 얕은 복사, 깊은 복사를 사용해야 한다.

2019.03.23 TIL

(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)

# 질문에 답하기.

  1. 객체지향이란?
  2. 객체지향의 특징 3가지

OOP를 시작하기 전에 보면 좋은 것을 지난 번에 TIL한 것에서 가져와보았다.

class - 객체를 만들어 내기 위한 틀

class는 객체를 만들어내기 위한 약속이다.
class는 객체의 속성(property)과 동작(method)을 정의한다.

object - class라는 틀을 가지고 만들어낸 실물(ex. 자동차)

파이썬에서의 객체

  • 컴퓨터가 보는 객체 : 클래스를 이용해 만들어진 변수와 함수를 가진 메모리 공간
  • 우리가 보는 객체 : 현실 세계의 사물을 모델링 한 것

object란?

  1. 관련 있는 변수와 함수를 한곳에 모아(bundling) 놓은 곳(메모리의 영역)
    1. 변수 - 상태정보
    2. "관련 있는" 변수, 함수" "한곳에 모아" 가 중요하다.
  2. class를 통해 생성되므로 class의 속성을 가지고 있어야 한다.
  3. 속성(attribute)
    1. instance member(변수)
      1. 각자의 객체가 가지는 값(값 = 상태정보)
      2. init에 보면 변수가 나와 있음
      3. 같은 속성을 가지고 있으나 속성 값이 다르다.
      4. 현재 객체의 담고 있는 인스턴스 멤버 정보 보기 acnt._dict_
    2. instance method(함수)
      1. 객체가 할 수 있는 행동, 기능

—> 절차지향에서는 전체 프로그램을 다 봐야하지만.
—> 객체지향에서는 단위로 프로그램을 유추해볼 수 있다.

절차지향 vs 객체지향

###procedural vs object-oriented

객체지향 절차지향

2개 다 추상화를 하는 기법이다.

  • 추상화란?
    • 상세한 정보는 무시하고 필요성에 의해 있어야할 정보만을 추림. why?
    • 우리가 구현하고 싶은 것들을 설계하기 위해 멤버와 메서드를 뽑아내는 일

procedural : 함수를 이용해 추상화를 했다. 추상화의 도구로 함수를 이용 (절차지향)

  1. 이 프로그램은 어떤 일을 하는가?
  2. 기계와 비슷한 사고 방식
  3. 함수를 이용

object-oriented : 객체를 통해 추상화를 했다.

  • 현실세계에서 객체를 나타내려면 변수와 함수만 있으면 된다. 현실 세계를 모델링하거나 프로그램을 구현하는데 이처럼 변수와 함수를 가진 객체를 이용하는 패러다임을 객체지향이라고 한다.
  1. 현실 세계(에 존재하는 객체(object)를 어떻게 모델링(modeling)할 것인가?
    • 객체(object) : 사람, 동물, 자동차 등을 어떻게 프로그램으로 모델링 할 것인가?
  2. 사람과 비슷한 사고 방식
  3. 객체를 이용

OOP의 3대 특징

  • 캡슐화
  • 정보은닉
  • 다형성

1. Encapsulation(캡슐화)

정의 : 관련 있는 멤버(변수=데이터)와 메서드(함수=행동)를 하나의 단위(대부분 클래스)로 묶는 것

  • 관련 있는 멤버와 메서드가 중요하다.

  • 하나의 단위 : java, python, c++ 에서는 모두 클래스로 묶지만 꼭 클래스가 아니라도 하나의 단위로만 묶을 수 있다면 OOP로 볼 수 있다.

  • 정보 은닉을 포함

    • 멤버와 메서드를 묶으면서 어떤 멤버를 공개 혹은 공개 하지 않을 것인가 결정
    • 어떤 메서드를 공개 혹은 비공개 할 것인가?
    • 멤버과 메서드를 공개하지 않을 것을 정보 은닉이라고 한다.
    • 정보은닉은 캡슐화 안에서 일어나므로 캡슐화에 포함되었다고 생각

2. 정보은닉

정의 : 특정 멤버와 메서드를 공개하지 않을 것

  1. 파이썬에서는 완벽한 정보은닉을 지원하지 않는다.

    • 그럼 완벽한 정보은닉은 무엇이냐?
      1. c에서는 private를 통해 정보를 은닉시킴
      2. 접근조차 못하게 한다. java, c#, c++모두 지원
  2. 하지만 약간의 기법을 통해 특정 멤버와 메서드가 정보은닉을 바라는 것인지 알 수 있다.

    1. name-mungling
      • self.__ balance = balance —> acnt.__ balance를 하면 실행이 안됨
        • __ balance 를 _Account__balance로 바꾸어서 저장하는 것이다.
      • 함수에도 __를 붙여서 외부에서 쉽게 호출하지 못하도록 한다.
        • 지원을 할려고 노력은 하되 100%지원은 하지 않음
    2. property
      • OOP 호출 전제 조건
        • OOP에서는 외부에서 변수(멤버)에 직접 접근하는 것은 절대 금지이다.
        • 객체의 멤버에 접근이나 수정할 떄는 반드시 메서드를 이용해야 한다.
        • 따라서 객체의 멤버에 접근하기 위해서는 get_ or set_등과 같은 함수를 이용해야 한다.
      • 하지만 property를 이용하여 접근을 일관되게 할 수 있다.
        • 멤버를 호출하면 내부적으로는 함수를 호출하도록 설계한다.
        • 함수를 설계할 때 특정한 정보은닉을 할 수 있다.
  • name-mungling과 멤버에 대한 접근
>>> class Account:
>>>     def __init__(self, name, money):
>>>            self.user = name
>>>            self.__balance = money
>>>    
>>>        def get_balance(self):
>>>            return self.__balance
>>>    
>>>        def set_balance(self, money):
>>>            if money < 0:
>>>                return
>>>            self.__balance = money
>>>    
>>> if __name__ == '__main__':
>>>        my_acnt = Account("greg", 5000)
>>>        my_acnt.__balance = -3000
>>>    
>>>        print(my_acnt.get_balance())
>>>        print(my_acnt.set_balance(6000))
>>>        print(my_acnt.get_balance())
>>>        print(my_acnt.__dict__)
>>>
>>> 5000
>>> None
>>> 6000
>>> {"user" : "greg", '_Account_balance' : 6000, '__balance : - 3000'}
  • name-mungling을 통해 바로 접근할 수 없어졌고, 클래스안에서 멤버 앞에 언더바 2개를 붙이게 되면 이후에 이 객체가 만들어 질 때 멤버의 이름은 _ 클래스 이름 __ 멤버 로 설정된다.
  • 객체의 멤버에 접근하기 위해 get_balance와 set_balance 함수를 통해 접근하였다.
  • property를 활용해 이를 해결 할 수 있다.
>>>    class Person:
>>>    def __init__(self, name, money):
>>>       self.name = name
>>>            self.money = money
>>>
>>>        @property
>>>        def money(self):
>>>            print('getter executed')
>>>            self._money = money
>>>
>>>        @money.setter
>>>        def money(self, money):
>>>            print('setter executed')
>>>            if money < 0:
>>>                print("음수보다 큰 값을 넣어주세요")
>>>                return
>>>            
>>>            self._money = money    
>>>            
>>>    if__name__ == "__main__":
>>>        john = Person('john' , 5000)
>>>        print(john.__dict__)
>>>        john.money = -5000
>>>        print(john.money)    
>>>
>>> setter excuted
>>> {'name': 'john', '_money': 5000}
>>> setter excuted
>>> 음수보다 큰 값을 넣어주세요
>>> getter excuted
>>> 5000
  • \ __ init __ 을 실행하여 객체를 만들 때 money 멤버에 오면 setter 메서드가 실행되면서 _money로 저장이 된다.
  • 이러한 방법을 통해 우리는 정보를 은닉하고 멤버에 대한 접근을 일관되게 할 수 있게 된다.
  1. 멤버는 어느 메서드에서도 생길 수 있다!!!!

    1. 파이썬에서는 다른 어떤 함수에서도 멤버가 생성될 수 있다.
    2. 따라서 안쓰더라도 모든 멤버들은 __init__에 넣어줘라!
    3. 인스턴스 멤버를 만든다고 하면 되도록이면 생성자에서 None으로 두고 만들어라
  2. setter를 통해 특정 조건에 대해 멤버 생성을 조절 할 수 있다.

    1. -가 들어오는게 막고 싶다면(특정 조건) 그 기능을 한 함수에 모아서 setter에 집약시킨다.

생성자에 모든 인스턴스 멤버들을 만들기 위해 노력하되 특정 생성자에 대한 생성 조건이 있다고 하면 그것은 하위에 한 함수를 통해 조절한다.

정보은닉은 name-mungling과 property를 함께 사용하여 확실한 은닉을 하기도 한다.

3. polymorphism(다형성)

정의 : 상속받은 같은 인스턴스 메서드를 호출하는데 호출의 주체인 object가 서로 다른 클래스에서 온 인스턴스기 때문에 다른 behavior을 나타낼 때 다형성이라고 한다.

  • 상속을 전제로 한다.
  • 다형성은 추상클래스로 부터 오기 때문에 추상클래스에 대해 먼저 알아야 한다.
  • 추상클래스를 통해 추상메서드를 만들어내면 이후에 만들어지는 객체에서는 추상메서드를 오버라이딩하여 새로운 인스턴스 메서드를 만들어내고, 각각의 메서드들은 다른 behavior을 나타내게 되는데 이를 다형성이라고 한다.
>>>from abc import ABCMeta, abstractmethod
>>>
>>>    class Animal(metaclass=ABCMeta):
>>>        @abstractmethod
>>>        def say(self):
>>>
>>>            print('noting')
>>>
>>>
>>>    class Lion(Animal):
>>>        # def say(self):
>>>        #     print("어흥")
>>>        pass  # 이렇게 say를 빼먹으면 상속받은 메서드를 쓰지 않으면 Lion도 추상 클래스가 된다.
>>>
>>>    class Duck(Animal):
>>>        def say(self):
>>>            print("꽥꽥")
>>>
>>>    class Dog(Animal):
>>>        def say(self):
>>>        print("멍멍")
>>>
>>>    class Deer(Animal):
>>>        def say(self):
>>>            print("사슴")
>>>
>>>    if __name__ == "__main__":
>>>        animals = []
>>>        animals.extend((Lion(), Duck(), Deer(), Dog(), Duck()))
>>>
>>>        for animal in animals:
>>>            animal.say()                 #"어흥", "꽥꽥", "멍멍", "사슴"
>>>
>>>        ani = Animal()  # 이 세상에 그냥 동물은 없으므로 이것을 못되게 해야 한다.
>>>        ani.say()
  • from abc import ABCMeta (abstract base class)
  • 추상클래스는 매개변수로 metacalss = ABCMeta를 받아야 한다.
  • 추상매서드를 1개 이상 포함하고 있어야 한다.
  • 추상클래스를 상속받아 생성된 클래스는 모두 추상매서드를 오버라이딩해야한다.

추상클래스

정의 : 동물은 고양이과, 개과로 나눌 수 있다. 또 고양이과에는 사자, 고양이, 호랑이 등등 더 세분화 되어 들어갈 수 있다. 하지만 단순히 고양이과의 객체를 만들어서는 그 객체를 구체화 시킬 수 없다. 따라서 객체를 만들기에 너무나 추상적이라 객체를 만들 수 없도록 한 것이 추상클래스이다..

  • [참고] (https://itewbm.tistory.com/entry/%EC%B6%94%EC%83%81%ED%81%B4%EB%9E%98%EC%8A%A4abstract-class%EC%9D%98-%EC%A1%B4%EC%9E%AC-%EC%9D%B4%EC%9C%A0) : 추상클래스의 존재 이유

  • abstract class의 특징

    1. 인스턴스를 만들 수 없다.
    2. abstract method를 가져야 한다.
      1. 추상 메서드 (= 순수 가상 함수)는 몸체가 없어야 한다.
      2. 파생 클래스는 추상메서드를 반드시 overriding을 해야 한다.!!!!!!!!!!
      3. 상속받은 메서드를 사용하지 않으면 그 클래스도 추상 클래스가 된다.(상위)—> 구현을 강제 할 수 있다.
      4. 함수 시그니처는 함수 인터페이스 , 함수 사용법이다.
        1. 객체는 공개된 메서드만 가져와서 사용한다.
        2. 유저가 사용할 수 있는 메서드의 목록 및 사용방법을 보여주는 것을 인터페이스라고 한다.
        3. 객체에서 인터페이스라고 하면 유저프로그래머는 내부 프로그램을 알 필요가 없다. 공개되어 있는 메서드만 가져다 쓰면된다.
        4. 이게 객체에서의 인터페이스이다.
        5. 인터페이스를 공개한다는 말은
          1. 유저에게는 인터페이스를 제공하고
          2. 클래스를 상속받아 쓰는 사람들에게는 다형성을 제공해준다.
  • 메서드 오버라이딩(메서드의 재정의)

    1. 상속 일때만 해당된다.
      1. 정의 : 상속을 하는데 자식 클래스가 어떤 함수(이미 상속을 받아서 이미 있는 함수)를 replace한 경우
      2. 부모로부터 부여 받은 함수를 자신에게 맞추어 메서드를 재정의 하는 것
    2. 메서드 오버라이딩을 통해 구현 가능 한 것
      1. 객체에 따라 함수 결과 값을 다르게 바꿀 수 있다.
        1. 함수를 호출하는 객체가 다르므로
    3. 다형성을 구현하기 위한 기술적인 특징
    4. 이것을 기반으로 다양한 패턴이 나온다.
    5. 메서드 오버라이딩을 하는 순간 다 가상 함수라고 생각해도 된다.
  • 연산자 오버로딩(메서드의 중복정의)
    1. 다형성의 한 종류
    2. 직접 정의한 클래스에서 생성된 객체는 기본적으로 연산이 불가능하다.
    3. 직접 정의한 클래스의 객체에 +,-,와 같은 일반 연산을 적용하려면 *연산자 오버로딩**을 통해 객체를 연산 가능한 상태로 만들어야 한다. (기존에 있던 연산자의 기능을 바꾸어 중복으로 정의)
    4. 파이썬에서는 다른 자료형에 적용되는 모든 연산을 사용자가 정의하는 클래스에서 동작할 수 있도록 구현할 수 있다.

2019.03.22 TIL

(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)

# 질문에 답하기

  1. is-a / has -a에 관해 이야기 해보시오

궁극적으로 코딩이란 클래스 간의 계층 구조를 만들고 설계하는 것

좋은 설계와 나쁜 설계

  • 다 크게 만들어 놨는데 클라이언트가 특정 기능을 추가해달라고 했는데 미친듯이 클래스가 변경해야되면 나쁜 설계이다.
  • 좋은 설계는 (궁극적인 목표) 기능이 추가되는 그 클래스 한개만 변경이 되고 나머지는 완벽히 닫혀져 있는 상태(Open-closed)
  • 이것을 위해 design pattern이 필요하고 이게 GOF(gang of Four) - 20개 이렇게 하면 open-closed에 근접해 갈 수 있다.
  • 이것을 가기 위해 가장 밑바닥의 지식이 상속이다.

클래스와 클래스 사이의 관계 설정 방법

  • 클래스와 클래스 사이의 관계에는 Is -A 와 Has - A 가 있다.
  1. 클래스와 클래스 사이의 관계 설정 방법
    1. is-a ( is - a는 상속이용)
      1. 첫번째 조건 모든 멤버와 메서드가 중복이 되어야 한다.
        1. 부모가 가진 멤버와 메서드 중 단 한개라도 가지지 않으면 사용할 수 없다.
      2. 상속 받은 모든 멤버와 메서드를 가지게 되어 재사용성이 가능해진다.
        1. is-a가 아닌데 재사용만하고 싶으면 composition을 통해 구현해야 한다.
      3. 언제 상속을 쓰나?(1번 조건을 전제하에)
        1. 멤버가 추가될 때
        2. 메서드가 추가 될 때
    2. has - a (논리적으로 조금 차이가 있다)
      1. 객체의 멤버로서 다른 객체를 포함해서 사용한다.(composition, aggregation 둘다 대전제)
      2. 상속이 아니다. 서로 has-a관계 일 때 composition 혹은 aggregation을 활용
      3. composition(합성)
        1. 코드 재사용을 위해서 관계를 구축하는 거라면 상속보다 컴포지션을 이용한다.
        2. same life cycle
          1. 객체들의 생성 시점과 소멸 시점이 같다.
        3. strong coupled
          1. 객체들의 생성 시점이 같다.
          2. 객체들의 소멸 시점이 같다.
          3. cpu, ram, computer는 강하게 커플링 되어 있다.
      4. aggregation(통합)
        1. Not same life cycle
        2. weak coupling
        3. 내가 필요할 때만 받아서 쓰고 필요없으면 반납한다.

정리

IS-A

  1. A laptop IS - A computer
  2. ~클래스은 ~클래스의 한 종류이다.
  3. inheritance(상속)으로 구현

HAS-A(합성).

  1. A policeman has a gun.
  2. ~이 ~을 가지고 있다. or 포함하고 있다.
  3. composition(합성) or aggregation(통합)

+ Recent posts