강의노트 12. 함수 호출방식(call-by-value, call-by-reference, call-by-assignment)

|

패스트캠퍼스 컴퓨터공학 입문 수업을 듣고 중요한 내용을 정리했습니다. 개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.

함수의 호출방식

call-by-value (값에 의한 호출)

  • 함수가 호출될 때, 메모리 공간 안에서는 함수를 위한 별도의 임시 공간이 생성된다. (c++의 경우 stack frame) 함수가 종료되면 해당 공간은 사라진다.
  • 스택 프레임(Stack Frame) : 함수 호출시 할당되는 메모리 블록(지역변수의 선언으로 인해 할당되는 메모리 블록)
  • call-by-value 값에 의한 호출방식은 함수 호출시 전달되는 변수의 값을 복사하여 함수의 인자로 전달한다.
  • 복사된 인자는 함수 안에서 지역적으로 사용되는 local value의 특성을 가진다.
  • 따라서 함수 안에서 인자의 값이 변경되어도, 외부의 변수의 값은 변경되지 않는다.
  • Java의 경우 함수에 전달되는 인자의 데이터 타입에 따라서 (원시자료형 / 참조자료형) 함수 호출 방식이 달라진다.
    • 원시 자료형 (primitive type) : call-by-value 로 동작 (int, short, long, float, double, char, boolean )
    • 참조 자료형 (reference type): call-by-reference 로 동작 (Array, Class Instance)

call-by-reference (참조에 의한 호출)

  • 함수가 호출될 때, 메모리 공간 안에서는 함수를 위한 별도의 임시 공간이 생성된다. (예: stack frame) 함수가 종료되면 해당 공간은 사라진다.
  • call-by-reference 참조에 의한 호출방식은 함수 호출시 인자로 전달되는 변수의 레퍼런스를 전달한다. (해당 변수를 가르킨다.)
  • `따라서 함수 안에서 인자의 값이 변경되면, 아규먼트로 전달된 객체의 값도 함께 변경된다.

call-by-value, call-by-reference 예시

#include <iostream>
using namespace std;

//두 수를 함수 내에서 바꿉니다.
void func_by_value(int a, int b)
{
	int temp = a;
	a = b;
	b = temp;
}

//두 수를 함수 내에서 바꿉니다.
void func_by_ref(int & a, int & b) //포인터로도 구현 가능
{
	int temp = a;
	a = b;
	b = temp;
}

int main(void)
{
	int a = 10;
	int b = 20;
	cout << "before function" << endl;
	cout << "a : " << a << endl;
	cout << " b : " << b << endl<<endl<<endl;

#if 0
	func_by_value(a, b); // a, b 의 값은 그대로
#else
	func_by_ref(a, b); // a, b 의 값이 서로 바뀌어 있다.
#endif
	cout << "after function" << endl;
	cout << "a : " << a << endl;
	cout << " b : " << b << endl << endl << endl;

	return 0;
}

call-by-assignment

  • 파이썬의 경우 함수의 호출 방식으로 call-by-assignment을 사용한다.
  • 파이썬에서는 모든 것이 객체이고, 객체에는 2가지 종류가 있다.
  1. immutable object
    • int, float, str, tuple
    • immutable 객체가 함수의 arguments로 전달되면 처음에는 call by reference로 받지만, 값이 변경되면 call by value로 동작한다.
      즉 함수 내에서 formal parameter 값이 바뀌어도, actual parameter에는 영향이 없다.
  2. mutable object
    • list, dict, set
    • mutable 객체가 함수의 argument로 넘어가면 call by reference로 동작한다. 즉, object reference가 전달되어 actual parameter의 값에 영향을 미칠 수 있다.
def spam(eggs):  
   eggs.append(1) # call by reference로 동작 / caller scope까지 적용
   eggs = [2, 3] # call by value로 동작, 새로운 객체를 가르킨다. / callee scope에는 적용되지만, caller scope까지는 적용되지 않는다.
   print(eggs)

ham = [0]  
spam(ham) # [2, 3]
print(ham) # [0, 1]

Parameter, Argument, 함수 호출방식 참고자료

강의노트 11. 객체지향 방식 Data analysis, 캐시(cache)와 프록시서버

|

패스트캠퍼스 컴퓨터공학 입문 수업을 듣고 중요한 내용을 정리했습니다. 개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.

OOP Data analysis

개요

  • class_A.bin : 딕셔너리 형태로 이름과 점수 데이터를 가진 바이너리파일
  • evaluate.py : 평균과 표준편차를 연산하는 Evaluate class를 포함하는 파일
  • datahandler.py : DataHandler class를 포함하는 파일
    • filename, clsname을 매개변수로 받아서,
    • 데이터를 원하는 딕셔너리 형태로 재조합
    • 점수 데이터만 리스트 형태로 재조합
    • 평균, 분산, 표준편차 연산
    • 종합 평가결과 도출
    • 최고 득점자, 최고 점수, 최저 득점자, 최저 점수 도출
  • main.py : DataHandler class에 실제로 매개변수를 넣어서 인스턴스를 생성후 테스트

class_A.bin

  • 딕셔너리 형태로 이름과 점수 데이터를 가진 바이너리파일
 {'greg': 95},
 {'john': 25},
 {'yang': 50},
 {'timothy': 15},
 {'melisa': 100},
 {'thor': 10},
 {'elen': 25},
 {'mark': 80},
 {'steve': 95},
 {'anna': 20}

evaluate.py

  • Evaluate class를 포함하는 파일
    • average method : 평균
    • variance method : 분산
from functools import reduce


class Evaluate:
    def average(self, scores):
        return reduce(lambda a, b: a + b, scores)/len(scores)

    def variance(self, scores, avrg):
        return reduce(lambda a, b: a + b, map(lambda s:(s-avrg)**2, scores))/len(scores)

# 테스트 코드를 꼭 작성
if __name__ == '__main__':
    e = Evaluate()  
    li = [100, 95, 90]
    result1 = e.average(li)
    print(result1) # 95.0
    result2 = e.variance(li, result1)
    print(result2) # 16.666666666666668

datahandler.py

  • DataHandler class를 포함하는 파일
    • evaluator 멤버변수 : Evaluate class의 인스턴스
    • GetRawdataInDic 클래스 메소드 : class_A.bin 부터 데이터를 가져와 딕셔너리 형식으로 재조합한다.
    • init 초기화 함수 : 인스턴스 변수 (rawdata, clsname, cache) 정의
    • get_scores 메소드 : rawdata 에서 점수만 가져와서 리스트에 담는다.
    • get_average 메소드 : self.cache 에서 average 키에 해당하는 값을 가져온다. 없으면 추가한다.
    • get_variance 메소드 : self.cache 에서 variance 키에 해당하는 값을 가져온다. 없으면 추가한다.
    • get_standard_deviation 메소드 : self.cache 에서 standard_deviation 키에 해당하는 값을 가져온다. 없으면 추가한다.
from evaluate import Evaluate
from functools import reduce
import pickle
import math

# 이런 류의 클래스에는 Handler, Manager, Controlloer 라는 말을 자주 사용한다.
class DataHandler:
    evaluator = Evaluate() # 클래스 변수 : 클래스의 모든 인스턴스들이 공유하는 변수 (연산기)
                           # 객체합성 (상속을 받지 않고 객체합성을 한다.)


    # class method : 전역함수처럼 사용
    @classmethod # 인스턴스 메소드로 정의해도 문제는 없다
    def GetRawdataInDic(cls, filename):
        rawdata = {}
        with open(filename, 'rb') as f:
            while 1:
                try:
                    data = pickle.load(f)
                except EOFError:
                    break
                rawdata.update(data)

        return rawdata

    def __init__(self, filename, clsname):
        self.rawdata = DataHandler.GetRawdataInDic(filename)
        self.clsname = clsname
        self.cache = {}         # 연산한 값을 저장해 두는 저장소
                                #(필요할 때 연산하되 이미 연산된 값이면 연산 없이 저장된 값을 반환)

    def get_scores(self): # cache기법 사용
        if 'scores' not in self.cache:
            self.cache['scores'] = list(self.rawdata.values())

        return self.cache.get('scores')

    def get_average(self): # cache 사용
        if 'average' not in self.cache:
            self.cache['average'] = self.evaluator.average(self.get_scores())

        return self.cache.get('average')

    def get_variance(self): # cache 사용
        if 'variance' not in self.cache:
            vari = round(self.evaluator.variance(self.get_scores(), self.get_average()), 1)
            self.cache['variance'] = vari

        return self.cache.get('variance')

    def get_standard_deviation(self):
        if 'standard_deviation' not in self.cache:
            std_dev = round(math.sqrt(self.get_variance()), 1)
            self.cache['standard_deviation'] = std_dev

        return self.cache.get('standard_deviation')

    def GetEvaluation(self):
        print('*' * 50)
        print("%s 반 성적 분석 결과" % self.clsname)
        print("{0}반의 평균은 {1}점이고 분산은 {2}이며,따라서 표준편차는{3}이다".\
              format(self.clsname, self.get_average(), self.get_variance()\
                     , self.get_standard_deviation()))
        print('*' * 50)
        print("%s 반 종합 평가" % self.clsname)
        print('*' * 50)
        self.evaluateClass()

    def evaluateClass(self):
        avrg = self.get_average()
        std_dev = self.get_standard_deviation()

        if avrg <50 and std_dev >20:
            print("성적이 너무 저조하고 학생들의 실력 차이가 너무 크다.")
        elif avrg > 50 and std_dev >20:
            print("성적은 평균이상이지만 학생들 실력 차이가 크다. 주의 요망!")
        elif avrg < 50 and std_dev <20:
            print("학생들간 실력차는 나지 않으나 성적이 너무 저조하다. 주의 요망!")
        elif avrg > 50 and std_dev <20:
            print("성적도 평균 이상이고 학생들의 실력차도 크지 않다.")

    def who_ist_highest(self):
        if 'highest' not in self.cache:
            self.cache['highest'] = reduce(lambda a, b: a if self.rawdata.get(a) > self.rawdata.get(b) else b, self.rawdata.keys())
        return self.cache.get('highest')

    def get_highest_score(self):
        return self.rawdata[self.who_ist_highest()]

    def who_is_lowest(self):
        if 'lowest' not in self.cache:
            self.cache['lowest'] = reduce(lambda a, b: a if self.rawdata.get(a) < self.rawdata.get(b) else b, self.rawdata.keys())
        return self.cache.get('lowest')

    def get_lowest_score(self):
        return self.rawdata[self.who_is_lowest()]


캐시(cache) 와 프록시서버(proxy server)

  • 참고
  • 컴퓨터 시스템의 성능을 향상시키기 위해 주로 CPU 칩 안에 포함되는 빠르고 작고 비싼 메모리이다. 프로그램에서 직접적으로 읽거나 쓸 수 없고 하드웨어의 메모리 관리 시스템이 내부적으로 제어한다. 대부분 프로그램은 한 번 사용한 데이터를 다시 사용할 가능성이 높고, 그 주변의 데이터도 곧 사용할 가능성이 높은 데이터 지역성을 가지고 있다. 데이터 지역성을 활용하여 캐시보다는 느리지만 용량이 큰 메인 메모리에 있는 데이터를 캐시 메모리에 불러와 두고, CPU가 필요한 데이터를 캐시에서 먼저 찾도록 하면 시스템 성능을 향상시킬 수 있다.
  • 캐시 메모리는 데이터 지역성(Locality)의 원리를 사용한다. 데이터 지역성은 대표적으로 시간 지역성(Temporal locality)과 공간 지역성(Spatial Locality)으로 나뉘는데, 시간 지역성이란 for나 while 같은 반복문에 사용하는 조건 변수처럼 한 번 참조된 데이터는 잠시 후에 또 참조될 가능성이 높다는 것이고, 공간 지역성이란 A[0], A[1]과 같은 데이터 배열에 연속으로 접근할 때 참조된 데이터 근처에 있는 데이터가 잠시 후에 사용될 가능성이 높다는 것이다.
  • 쉽게 예를 들자면 무지하게 지랄맞고 부지런한 상사가 2010년 재무결산 보고서를 가져오라고 했을 때, 무슨 일인지는 몰라도 또 가져오라고 할지도 모르니까 2010년 재무결산보고서를 일단 준비해 놓고, 2009년이나 2011년, 2012년 재무결산보고서도 가져오라고 할지 모르니까 그것도 준비해 놓는 식이다. 또 다른 예로는 캐시는 지갑이라고 생각하면 된다. 지갑 혹은 주머니가 없다면 우리가 현금이 필요할 때마다 매번 은행이나 ATM에 가야 할 것이다. 이는 당연히 매우 귀찮고 시간도 많이 걸린다. 하지만 우리가 현금을 지갑에 넣고 다님으로써 시간을 절약할 수 있다.
  • CPU가 데이터를 요청했을 때 캐시 메모리가 해당 데이터를 가지고 있다면 이를 캐시 히트라 부르고, 해당 데이터가 없어서 DRAM에서 가져와야 한다면 캐시 미스라 부른다. 캐시 미스 발생시의 처리 방법은 캐시 정책에 따라 다르며, 데이터를 읽어 오는 시점으로 사용하기도 한다.

프록시서버 (proxy server)

  • 프록시서버
  • 프록시 서버 는 클라이언트가 자신을 통해서 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해 주는 컴퓨터나 응용 프로그램을 가리킨다. 서버와 클라이언트 사이에서 중계기로서 대리로 통신을 수행하는 기능을 가리켜 ‘프록시’, 그 중계 기능을 하는 것을 프록시 서버라고 부른다. 프록시 서버는 프록시 서버에 요청된 내용들을 캐시를 이용하여 저장해 둔다. 이렇게 캐시를 해 두고 난 후에, 캐시 안에 있는 정보를 요구하는 요청에 대해서는 원격 서버에 접속하여 데이터를 가져올 필요가 없게 됨으로써 전송 시간을 절약할 수 있게 됨과 동시에 불필요하게 외부와의 연결을 하지 않아도 된다는 장점을 갖게 된다. 또한 외부와의 트래픽을 줄이게 됨으로써 네트워크 병목 현상을 방지하는 효과도 얻을 수 있게 된다.

목적

  • 프록시 서버의 사용 목적은 잠재적으로 다양하다:
  • 익명으로 컴퓨터를 유지 (주로 보안을 위하여)
  • 캐시를 사용하여 리소스로의 접근을 빠르게 하기 위해. 웹 프록시는 웹 서버로부터 웹 페이지를 캐시로 저장하는 데 흔히 쓰인다.
  • 네트워크 서비스나 콘텐츠로의 접근 정책을 적용하기 위해. (이를테면 원치 않는 사이트를 차단)
  • 사용률을 기록하고 검사하기 위해 (이를테면 회사는 인터넷 이용을 파악)
  • 보안 및 통제를 뚫고 나가기 위해
  • 바이러스 전파, 악성 루머 전파, 다른 정보들을 빼낼 목적으로
  • 역으로 IP추적을 당하지 않을 목적으로
  • 전달에 앞서 악성 코드를 목적으로 전달된 콘텐츠를 검사하기 위해
  • 밖으로 나가는 콘텐츠를 검사하기 위해 (데이터 유출 보호)
  • 지역 제한을 우회하기 위해

level 3. 시저암호

|

문제출처

문제

어떤 문장의 각 알파벳을 일정한 거리만큼 밀어서 다른 알파벳으로 바꾸는 암호화 방식을 시저 암호라고 합니다. A를 3만큼 밀면 D가 되고 z를 1만큼 밀면 a가 됩니다. 공백은 수정하지 않습니다. 보낼 문자열 s와 얼마나 밀지 알려주는 n을 입력받아 암호문을 만드는 ceasar 함수를 완성해 보세요. “a B z”,4를 입력받았다면 “e F d”를 리턴합니다.

풀이

import string

def caesar(s, n):
    alphabet_lower = list(string.ascii_lowercase)
    alphabet_upper = list(string.ascii_uppercase)
    s = list(s)
    for i, char in enumerate(s):
        if char in alphabet_lower:
            position = (alphabet_lower.index(char) + n) % 26
            s[i] = alphabet_lower[position]
        elif char in alphabet_upper:
            position = (alphabet_upper.index(char) + n) % 26
            s[i] = alphabet_upper[position]
        else:
            pass
    return ''.join(s)


# 실행을 위한 테스트코드입니다.
print('s는 "a B z", n은 4인 경우: ' + caesar("a B z", 4))

풀이 과정

  • 알파벳 대문자, 소문자 리스트를 준비
  • 매개변수 문자열 s를 list로 변환
  • 해당 list의 각 요소를 순회하면서
    • 해당 문자가 대문자 혹은 소문자에 포함된다면,
      • 알파벳 리스트에서 해당 문자의 index 값을 찾아서 매개변수 n을 더하고, 알파벳의 길이인 26으로 나눈 나머지를 구한다.
      • 해당 나머지 값을 index 값으로 갖는 문자를 찾아서 리스트 s의 요소를 변경한다.
    • 해당 문자가 대문자 혹은 소문자 둘다 포함되지 않는다면 pass 한다.
  • 반복이 끝나면, 리스트 s를 문자열로 변환한다.

다른사람 풀이

# 풀이 1
import string


def caesar(s, n):
    result = ""
    base = ""
    for c in s:
        if c in string.ascii_lowercase:
            base = string.ascii_lowercase
        elif c in string.ascii_uppercase:
            base = string.ascii_uppercase
        else:
            result += c
            continue
        a = base.index(c) + n
        result += base[a % len(base)]
    return result


# 실행을 위한 테스트코드입니다.
print('s는 "a B z", n은 4인 경우: ' + caesar("a B z", 4))

# 풀이 2

def caesar(plain, no):
    result = ""
    for k in plain:
        if k.isalpha():
            if k.islower():
                result += chr((ord(k) - ord("a") + no) % 26 + ord("a"))
            else:
                result += chr((ord(k) - ord("A") + no) % 26 + ord("A"))
        else:
            result += k
    return result

# 실행을 위한 테스트코드입니다.
print('s는 "a B z", n은 4인 경우: ' + caesar("a B z", 4))

배운점

  • chr(x) 을 통해서 ascii 코드 10진수, 16진수를 문자로 바꿀 수 있다.
  • ord(x) 를 통해서 axcii 문자를 10진수로 바꿀 수 있다.

170411_TIL

|

오늘 할 일 (계획)

  • 패스트캠퍼스 컴퓨터공학 입문 수업 참여 및 강의노트 정리하기
  • 패스트캠퍼스 웹프로그래밍 스쿨 사전과제 제출하기
  • 영어공부 게시판 정렬방식 변경 (공지사항 최상단 + 최신순), 로그인하지 않은 사용자에게도 댓글 입력창 표시

오늘 한 일 (회고)

  • 영어공부 게시판 수정
    • 로그인하지 않은 사용자에게도 댓글 입력창 표시, 저장버튼 선택시 로그인 화면으로 리다이렉트
  • 컴퓨터공학 입문수업 참여
    • call-by-value
    • call-by-reference
    • call-by-assignment

내일 할 일

  • 패스트캠퍼스 스쿨 설명회 참여
  • 컴퓨터공학 입문수업 복습 및 강의노트 정리

강의노트 10. [객체지향] 파이썬 추상클래스(abstract class), 다형성(polymorphism)

|

패스트캠퍼스 컴퓨터공학 입문 수업을 듣고 중요한 내용을 정리했습니다. 개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.

객체지향개발(OOP) 4가지 특성

객체지향개발(Object Oriented Programming)의 특성은 크게 추상화, 캡슐화, 상속성, 다형성이 있다.

  1. 추상화(Abstraciton)
    • 공통의 속성이나 기능을 묶어 이름을 붙이는 것
    • OOP 에서 클래스를 정의하는 것을 추상화라고 할 수 있다.
  2. 캡슐화 (Encapsulation)
    • 변수와 함수를 하나로 묶어서 외부에서의 접근을 막고, 함수를 통해서만 접근 가능하도록 하는 것
    • 객체가 맡은 역할을 수행하기 위한 데이터를 하나로 묶는다 (은닉화)
  3. 상속성, 재사용 (Inheritance)
    • 상위 개념의 특징을 하위 개념이 물려 받는 것
  4. 다형성 (Polymorphism)
    • 부모 클래스로부터 물려받은 가상 함수를 자식 클래스 내에서 오버라이딩 하여 사용하는 것

참고자료

추상화 클래스 (Abstract Base Class)

  • 게임에 존재하는 캐릭터는 hp, 공격력 등 공통적으로 갖고 있는 공통 특성이 있다.
  • 이러한 공통 특성을 가진 부모 클래스를 만들고 해당 클래스는 객체 인스턴스를 생성할 수 없게 하는것을 추상 클래스 (abstract class)라고 한다.
  • 추상 클래스 이해에 도움이 되는 글 : ‘다시 말해 추상클래스란 해당 클래스를 통해 만들어낸 객체가 전혀 객체의 모습을 띄지 못할 때, 해당 클래스가 객체를 생성할 수 없도록 하는 것. 너는 너무 추상적이야, 그래서 객체를 만들면 안돼!’

추상클래스(abstract class) 예시

  • 게임의 모든 캐릭터가 가진 공통 속성을 정의하는 Character 클래스를 정의한다. (단, 추상 클래스로 정의한다.)
  • 추상클래스는 @abstractmethod 데코레이터를 활용한 1개 이상의 abstract method를 가져야 한다.
  • 추상크래스를 정의 할 때는 from abc import * 모듈 적용이 필요하다.

Character 클래스 (추상클래스, 부모클래스)

# character.py
from abc import * # Abstrace Base Class

class Character(metaclass = ABCMeta):
  def __init__(self):
    self.hp = 100
    self.attack_power = 20

  def attack(self, other, attack_kind):
    other.get_damage(self.attack_power, attack_kind)

  @abstractmethod # Character 클래스를 상속받는 모든 클래스는 하기 함수를 오버라이딩으로 구현해야 인스턴스 생성이 가능하다.  
  def get_damage(self, attack_power, attack_kind):
    pass

# 테스트코드    
if __name__ == '__main__':
    ch1 = Character()  # TypeError 발생 (Can't instantiate abstract class Character with abstract methods get_damage)


다형성 (polymorphism)

  • 상속과 함께 OOP에서 중요한 개념 중 하나
  • 동일한 코드이지만 동작방법, 결과가 다른 것을 의미한다.
  • 다형성의 쉬운 예제는 Java의 오버로딩이다. 오버로딩은 같은 의미지만 매개변수의 데이터타입이 무엇이냐에 따라서 다른 메소드가 호출되는 방식이다. 참고 (참고로 파이썬은 오버로딩을 허용하지 않는다.)
  • 다형성을 잘 쓰면 IF / ELSE 문을 많이 줄일 수 있다.
  • 다형성에 대한 이해하기 쉬운 글

다형성 (polymorphism) 예시

  • 추상클래스인 Character 클래스를 상속 받아서 Player, Monster 클래스를 각각 정의한다.

추상클래스의 자식 클래스 (Player)

# player.py
from character import Character

class Player(Character):

  def get_damage(self, attack_power, attack_kind):
    self.hp -= attack_power

추상클래스의 자식 클래스 (IceMonster, FireMonster : 다형성 적용)

  • 매개변수 attack_kind에 따라서 get_damage 메소드의 작동 방식을 분기처리한다.
# monster.py
from character import Character

class IceMonster(Character):
  def get_damage(self, attack_power, attack_kind):
    if attack_kind == 'ICE':
      self.hp += attack_power
    else:
      self.hp -= attack_power

  def __str__(self):
        return "Ice Monster's HP : {}".format(self.hp)

class FireMonster(Character):
  def get_damage(self, attack_power, attack_kind):
    if attack_kind == 'FIRE':
      self.hp += attack_power
    else:
      self.hp -= attack_power

  def __str__(self):
    return "Fire Monster's HP : {}".format(self.hp)

실행 파일작성 (main.py)

# main.py

from player import Player
from monster import *

play = Player()
ice = IceMonster()
fire = FireMonster()

print(ice) # Ice MOnster's HP : 100
print(fire) # Fire MOnster's HP : 100

# 확인방법 1
play.attack(ice, 'ICE')
play.attack(fire, 'ICE')

print(ice) # Ice MOnster's HP : 120
print(fire) # Fire MOnster's HP : 80

# 확인방법 2
monsters = []
monsters.append(ice)
monsters.append(fire)

for monster in monsters:
  play.attack(monster, 'ICE')
  print(monster)

# Ice Monster's HP : 140
# Fire Monster's HP : 60