본문 바로가기

Back-End/Python

[Python] 파이썬 알고리즘 - 유니코드, 클로저(closure), 데코레이터

~ 목차 ~

 

컴퓨터의 기본저장단위 : 바이트(Byte) = 8bit

* 따라서 1Byte에는 1bit는 0,1 두가지 값을 포함하므로 총 2^8(=256)개의 값 저장 가능

* 문자인코딩(Encording) : 문자나 기호의 집합을 컴퓨터에 저장하거나, 통신목적으로 사용하는 경우 부호로 변환

 

 

아스키 (ASCII)

  • 미국에서 정의한 부호체계의 표준
  • 아스키코드는 8비트를 모두 사용하는 것이 아니라 7bit(128개)의 값만 사용
  • 나머지 1비트는 통신에러 검출을위해 사용하기 때문이다 = Parity Bit
  •  이는 '영문 키보드'로 입력할 수 있는 모든 가능성을 담았지만, 다른 언어를 표현하기에는 부족
  • 따라서 8bit를 모두 사용한 ANSI코드 출현
  • 하지만 전 세계 문자를 정의하기엔 부족하여 등장한 것이 Unicode다.

 

유니코드 (Unicode)

  • 용량을 2byte로 확장한 유니코드가 등장 - 즉, 2^16(=65536)개 저장 가능
  • 버전에 따라 확장(저장크기증가)
  • 국제적으로 전세계 언어를 모두 표시할 수 있는 표준코드
  • 유니코드(Unicode)는 전 세계의 모든 문자를 표현하기 위한 표준 인코딩 체계
  • 유니코드 프로그램 : 언어, 문자, 특수기호 등의 표준화
  • 우리나라는 EUC-KR사용
UTF-8

 

  • UTF-8은 유니코드를 인코딩(encoding)하는 방식, 전 세계에서 사용하는 약속
    • 문자열을 UTF-8로 인코딩(암호화) : .encode("utf-8")
    • 문자열을 UTF-8로 디코딩(암호화 해제) : .decode("utf-8")

 

유니코드는 국제표준 문자표이고 UTF-8은 인코딩 방식이다. 

 


 

<유니코드 프로그램>

 

### 문자열 결합하기

### 문자열 결합하기
string1 = "파이썬(python)"
string2 = " 재미있어요"
string3 = "!!!"
word = string1 + string2 + string3
print(word)

 

결과

파이썬(python) 재미있어요!!!

 

 

### 문자열을 UTF-8로 인코딩

text = "안녕하세요"
encode_text = text.encode("utf-8")
encode_text

 

결과

b'\xec\x95\x88\xeb\x85\x95\xed\x95\x98\xec\x84\xb8\xec\x9a\x94'

 

 

### 문자열을 UTF-8로 디코딩하기

decode_text = encode_text.decode("utf-8")
decode_text

결과

'안녕하세요'

 

 

### "안녕하세요" 문자열을 for문을 이용해서 문자 각각 출력해주세요
# -인덱스 번호를 이용하는 방식으로 해주세요.

### for문 - 값을 이용해서 추출하는 방식
for v in decode_text:
    print(v)

### for문 - 인덱스 번호를 이용해서 추출하는 방식
for i in range(0, len(decode_text), 1):
    print(decode_text[i])

결과










 

 

### 눈 모양 유니코드 만들기 ( \u2603 )

char = "\u2603"
print(char)

# for문을 이용하지 말고, 눈사람 5개 출력해주세요
print(char*5)

결과


☃☃☃☃☃

 

### 십진수 숫자값을 문자로 변환하기( chr() )

num = 65
print(num, chr(num))

결과

65 A

 

###문자열에서 특정 문자의 위치 찾기( index(), find() )

string = "안녕하세요!!!"

# "하"라는 문자의 위치(인덱스) 찾아주세요...
ha = string.find("하")
print(ha)

print(string.index("하"))

결과

2

2

 

### 문자 치환하기 ( replace() )

string = "python 기초 문법"

# "python" 영문 문자열을 찾아서 "파이썬" 한글명으로 수정하기
result = string.replace("python", "파이썬")
print(result)

결과

파이썬 기초 문법

 


클로저 (Closure)

  • 함수 안에 함수를 만들어서 사용하는 방식
  • 함수 안에 있는 함수는 바깥쪽 함수에서 참조해서 사용하는 방식으로 접근.
  • 함수 안에 함수는 사용이 끝나면 메모리에서 해제되기 때문에 유용하게 사용하면 좋다.
  • 함수는 호출이 되어야 실행된다.
  • inner함수는 사용되고 나면 소멸된다.  outer함수는 남아있음.(종료전까지)
  • 따라서, 많은 데이터양을 처리할때 사용!!!

 

< 클로저 프로그램 >

 

 

### 클로저 함수 정의하기

def outer_function(x):
    ### 내부 함수 정의 : 실제 실행되는 함수
    def inner_function(y):
        s = x + y
        return s
    return inner_function

 

### 클로저 함수 호출하기

closure_exe = outer_function(10)
print(closure_exe)

# - clsure_exe는 inner_function 자체를 리턴받는 함수를 의미함

결과

<function outer_function.<locals>.inner_function at 0x000001FF70BCF060>

 

해석

'0x000001FF70BCF060'  = inner_function주소값은 <locals>에 들어있어

clsure_exe는 inner_function 자체를 리턴받는 함수를 의미

 

### 내부 함수 호출하기

result1 = closure_exe(5)
print(result1)

결과

15

 

총 메모리 공간은 2개 생겼을 것!! ( outer_function , inner_function  )

 

 

< closure 함수 실행 순서 알아보기>

def outer_function(x):
    print(f" #1 : x = {x}")
    ### 내부 함수 정의 : 실제 실행되는 함수
    def inner_function(y):
        print(f" #2 : y = {y}")
        s = x + y
        print(f" #3 : s = {s}")
        return s
    print(" #4 --------")
    return inner_function
closure_exe = outer_function(10)
print(closure_exe)

결과

 #1 : x = 10
 #4 --------
<function outer_function.<locals>.inner_function at 0x000001FF6FF81E40>

 

result1 = closure_exe(5)
print(result1)

결과

 #2 : y = 5
 #3 : s = 15
15

 

 

### 클로저를 이용해서 누적합 계산하기


# - 사용함수명 : outer_function2(), inner_function2(num)
# - 사용변수 : total(누적된 값을 저장할 변수)은 바깥함수에 둬야함(why? 내부함수는 실행 끝나면 소멸되므로)

# - nonlocal : 클로저 구조에서는 상위 변수를 내부 함수에서 사용못함.
                      따라서, nonlocal을 지정해서 정의하면 외부 영역의 변수 사용가능.

def outer_function2():
    total = 0
    def inner_function2(num):
        nonlocal total
        total += num
        return total
    return inner_function2
closure_exe2 = outer_function2()
print(closure_exe2)

결과

<function outer_function2.<locals>.inner_function2 at 0x000001FF700D3CE0>

 

result2 = closure_exe2(5)
print(result2)

결과

5

closure_exe2(10)

결과

15

 

 

<누적합 실행 순서 확인하기>

def outer_function2():
    total = 0
    print(f" #1 : total = {total}")
    def inner_function2(num):
        nonlocal total
        print(f" #2 : total = {total} / num = {num}")
        
        total += num
        print(f" #3 : total = {total} / num = {num}")
        return total
    print(" #4 -------------inner_function2 주소값만 가져옴 ")
    return inner_function2
closure_exe2 = outer_function2()
print(closure_exe2)

결과

 #1 : total = 0
 #4 -------------inner_function2 주소값만 가져옴 
<function outer_function2.<locals>.inner_function2 at 0x000001FF701EEF20>

 

result2 = closure_exe2(5)
print(result2)

 

결과

 #2 : total = 0 / num = 5
 #3 : total = 5 / num = 5
5

 

closure_exe2(10)

결과

 #2 : total = 5 / num = 10
 #3 : total = 15 / num = 10
15

 

 

<조건부 클로저 함수 프로그래밍>

 

def outer_function3(condition):
    def true_case():
        return "true_case 함수가 호출되었습니다."
    def false_case():
        return "false_case 함수가 호출되었습니다."

    ### condition의 값이 True이면 true_case함수 정의
    #                   False이면 false_case함수 정의
    rs = true_case if condition else false_case
    
    return rs
### 상위 함수 호출하기
rs_function = outer_function3(True)
print(rs_function)

rs_function = outer_function3(False)
print(rs_function)

결과

<function outer_function3.<locals>.true_case at 0x000001FF71A454E0>
<function outer_function3.<locals>.false_case at 0x000001FF71A471A0>

 

rs_msg = rs_function()
print(rs_msg)

결과

false_case 함수가 호출되었습니다.

 


데코레이터 (Decorator)

  • 함수의 재사용성을 확장한 개념의 방법
  • 데코레이터 사용하는 이유 : 은폐,엄폐하려고 + 많은양의 데이터를 처리할 때
  • java의 오버로드(??), 오버라이드(함수 재사용)
    • 오버로드(Overload): 같은 이름의 메서드가 매개변수의 타입, 개수, 또는 순서 등에서 다르게 정의
    • 오버라이드(Override): 상속 관계에서 부모 클래스에 정의된 메서드를 자식 클래스에서 같은 시그니처로 재정의
  • *args (가변 위치 인자): 값 인자 여러개
    • *args는 함수를 정의할 때 사용되며, 함수에 임의의 개수의 위치 인자를 전달할 수 있도록 한다.
    • *args는 관례적으로 사용되며, * 다음의 변수가 시퀀스 형태의 인자들을 모두 받음을 나타낸다.
  • **kwargs (가변 키워드 인자): 딕셔너리,리스트 인자 여러개
    • **kwargs는 함수를 정의할 때 사용되며, 함수에 임의의 개수의 키워드 인자를 전달할 수 있도록 한다.
    • **kwargs 역시 관례적으로 사용되며, 이름은 중요하지 않습니다. ** 다음의 변수가 딕셔너리 형태의 키워드 인자들을 모두 받음을 나타낸다.

 

<함수 실행 시간 확인하는 데코레이터 프로그램 작성>

● 데코레이터 함수 정의하기
   - func : 실제 처리할 함수 받아오는 매개변수

import time

### 데코레이터 함수 정의하기
# - func : 실제 처리할 함수 받아오는 매개변수(무조건??)
def timer_decorator(func):
    ### 실제 실행될 함수 정의:(함수 이름은 자유롭게)
    
    print(f"#1 : func = {func}")

    #데코레이터 함수가 리턴한 wrapper 함수는 exe_function이 된다.(재정의)
    # - func로 받은 함수를 아래 함수로 재정의하게 됨 
    def wrapper(*args, **kwargs):
        #args : argument(* : 변수)
        #kwargs : keyword argument (** : 문자열관련 리스트, 딕셔너리..)
        # - 시작시간
        start_time = time.time()
        print(f"#2 : 시작시간 = {start_time}")

        ### 실제 처리할 함수 : func
        rs = func(*args, **kwargs)
        print(f"#3 : rs = {rs}")

        # - 종료시간
        end_time = time.time()
        print(f"#4 : 종료시간 = {end_time}")

        return rs
    print("#5----------------------")
    return wrapper

 

● 데코레이터 호출 및 처리할 함수 정의하기

   - 데코레이터 호출은 '@'을 사용함

   - 데코레이터 함수 호출 시 밑에 정의된 함수(exe_function)가 매개변수로 자동으로 전달됨 

   - func는 exe_function함수의 주소값을 가짐

   - 데코레이터 함수가 리턴한 wrapper 함수는 exe_function이 된다.

   - time.sleep() : 데코레이터가 처리되는 시간을 벌어주기 위해서 쓰는 함수( 몇 초간 대기)

# - (재정의 되는 시점)
@timer_decorator
def exe_function():
    print("실제 처리할 함수")
    ### 2초간 대기 : 데코레이터가 처리되는 시간을 벌어주기 위해서 ( sleep() )
    time.sleep(2)

결과

#1 : func = <function exe_function at 0x00000292554EB9C0>
#5----------------------

 

● 실제 처리하기

exe_function()

결과

#2 : 시작시간 = 1699930632.5293994
실제 처리할 함수
#3 : rs = None
#4 : 종료시간 = 1699930634.5300581

 

설명

  •  rs = None인 이유 : exe_function()에 return값을 설정 안했으므로 print 값들만 출력
  •  exe_function함수의 주소값은 wrapper의 주소값으로 바뀜

 

<로그 데코레이터>

def log_decorator(func):
    print(f"#1 : func = {func}")
    def wrapper(*args, **kwargs):
        print(f"Logo : {func.__name__}함수 호출됨")
        rs = func(*args, **kwargs)
        print(f"Logo : {func.__name__}함수 종료됨")
        return rs
    print("#2 --------------------")
    return wrapper

 데코레이터 호출 및 처리 함수 정의

@log_decorator
def another_function():
    print("여긴 어디? 넌 누구?")

결과

#1 : func = <function another_function at 0x00000292560C3880>
#2 --------------------

 

처리 함수 호출

another_function()

결과

Logo : another_function함수 호출됨
여긴 어디? 넌 누구?
Logo : another_function함수 종료됨

 

 

<데코레이터 내부함수에서 처리된 결과를 받아보기>

def check_permission(param_id):
    print(f"#1 : param_id = {param_id}")
    
    def decorator(func):
        print(f"#2 : func = {func}")
        
        def wrapper(*args, **kwargs):
            check_id = "admin"
            print(f"#3 : check_id = {check_id}")

            if check_id == param_id:
                # *args, **kwargs는 리스트형식으로만 넣을 수 있음, 값1개면 args/ 값여러개면 kwargs
                args = ["인증성공"]
                print(f"#4 : args = {args}")

                return func(*args, **kwargs)

            else:
                print(f"#5 : raise명령으로 강제 오류 발생 시킴--------")
                raise PermissionError("접근 불가!!")

        print("#6 --------------- ")
        return wrapper
    print("#7 --------------- ")
    return decorator

 데코레이터 호출 및 처리함수 정의

### 데코레이터 호출 및 처리함수 정의
@check_permission(param_id = "admin")
def admin_function(rs_msg):
    print(f"인증 결과 : {rs_msg}")

결과

#1 : param_id = admin
#7 --------------- 
#2 : func = <function admin_function at 0x00000292554EAA20>
#6 --------------- 

 

처리 함수 호출

### 처리함수 호출
admin_function()

결과

#3 : check_id = admin
#4 : args = ['인증성공']
인증 결과 : 인증성공

 

 

<영문 소문자 2개를 매개변수로 받아서, 대문자로 변환하는 데코레이터 프로그램>

Q . 함수이름 자유롭게..생성

      - 데코레이터 함수 생성하셔서 대문자로 출력까지...

 

 방식1

def decorator1(func):
    print("#1")
    def wrapper(*args, **kwargs):
        print("#2")
        rs = func(*args, **kwargs)
        print("#3")
        return rs
    print("#4")    
    return wrapper
@decorator1
def change_upper(str1, str2):
    str1 = str1.upper()
    str2 = str2.upper()
    print("#5")
    return str1, str2

결과

#1

#4

change_upper('apple', 'banana')

 

결과

('APPLE', 'BANANA')

 

※ 여기서 wrapper의 매개변수인 *args와 **kwargs를 각각 지워보고 실행해보니 *args는 실행이 되는데 **kwargs로 하면 오류가 발생했다. *kwargs는 딕셔너리 형태의 매개변수만 가능한 것 같다.

※ 위에 순서코드를 보면 알다시피 우리가 설정한 데코레이터 함수 chage_upper()는 wrapper()함수의 func위치에 들어가는 것 같다.

 

 

방식2

def decorator_upper(func):
    ### 인자로 넘어온 2개의 소문자는 args 매개변수가 받게됨
    def wrapper(*args, **kwargs):
        ### 인자로 넘겨받은 개의 소문자를 대문자로 처리하는 영역
        args = [arg.upper() for arg in args]
        ### 대문자로 변환된 값 2개를 args에 리스트 타입으로 담아서 넘기면 됨
        return func(*args, **kwargs)
    return wrapper

###데코레이터 호출 및 처리 함수 정의

@decorator_upper
def getUpper(param1, param2):
    print(f"대문자로 변환된 {param1} / {param2}")

###처리하는 함수 호출 : 소문자 2개를 인자로 넘겨주기

getUpper("abcd","efgh")

결과

대문자로 변환된 ABCD / EFGH

 

※ 메모리 구조

실행 전 실행 후
decorator_upper공간(주소O)
getUpper 공간 (주소O)
wrapper 공간 (주소O)
func 공간 → getUpper주소값 바라봄
decorator_upper공간(주소O)
getUpper → wrapper 주소값 바라봄
wrapper 공간 (주소O)
func 공간 (주소O = 실행 전의 getUper주소를 유지)

<메모리 구조>

 

str = "안녕"

// str : 변수명 // = : 대입연산자(할당연산자) // "안녕" : 값

 

strList = [1, 2, 3]

 

def exe_func():

   str = "안녕"

 

class A:

   def __init__(sefl):

          self.str = "안녕"

   def str_msg(self):

          print("m")

물리적 구조
이름 영역 값 영역
str의 주소 '안녕'값의 주소
strList의 주소 4개 주소(전체 영역 주소 ,'1'의주소, '2'의 주소, '3'의 주소)
exe_func함수 주소 함수영역 주소
 >> str주소 '안녕' 값의 주소
wrapper함수 주소 함수영역 주소
>> 안의 값들 값의 주소
class A의 주소 클래스 A의 영역 주소
>> str 주소 "안녕" 값의 주소
>> str_msg 주소 함수영역 주소

 

* 안녕이라는 값의 주소값이 str의 주소에 대입연산자(=)를 통해 넘어가게 됨.

* 데코레이션 방식 : 주소값을 바꿔치기하는 방식(너도 이곳의 주소를 봐)

  ex) exe_func함수를 데코레이션 해서 wrapper함수의 주소값을 보게 한다.

 

 

 

  파이썬에서 반드시 알아야하는것 
리스트(튜플), 딕셔너리, if문, for문  꼭 따로 공부해보자!!

 

728x90