반응형

HTML 파일에서 CSS를 사용해 Font 스타일을 설정하는 방법을 2개 토픽으로 알아보자.

 

1. Google Fonts 와 같은 font 제공 서비스에서 제공하는 font에 대한 정보를 CSS 파일로 확보한 뒤 font 파일에 접근하기.

 

Google Fonts의 검색에서 "Gugi"를 선택해봤다. 우측의 "Get font"를 누른다.

Gugi 선택

 

우측의 "Get embed code"를 클릭한다.

Get embed code 선택

 

"Embed code in the <head> of your html" 밑의 코드를 복사해서 html 파일의 <head> 부분에 붙여넣는다.

코드 복사

(복사 붙여넣기할 코드)

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Gugi&display=swap" rel="stylesheet">
  • <link rel="preconnect" href="https://fonts.googleapis.com"> : 브라우저가 fonts.googleapis.com 서버에 미리 연결하도록 요청해서, 서버와의 DNS 조회 및 SSL 연결 시간을 줄여 font를 더 빨리 로드하도록 한다.
    • fonts.googleapis.com 서버는 폰트 설정(CSS 파일)을 제공하는 서버이다.
  • <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>: fonts.gstatic.com 서버에 미리 연결하고, crossorigin 속성을 설정한다. Font 파일을 빠르게 로드하면서 CORS 문제를 방지하는 역할이다.
    • fonts.gstatic.com 서버는 Google Fonts의 실제 font 파일을 제공하는 고속 서버이다.
    • Google은 전 세계에 분산된 서버(CDN - Content Delivery Network)를 사용해 font를 빠르게 제공한다. fonts.gstatic.com은 Google의 CDN에 연결된 서버로, 가까운 위치의 서버에서 font 파일을 다운로드받을 수 있도록 최적화된다.
    • Font 파일은 CORS를 통해 로드된다. 그래서 crossorigin 속성을 설정해 폰트 파일을 문제없이 가져오도록 해야 한다. Font 파일은 웹 브라우저의 보안 정책상 반드시 CORS 허용 설정이 되어야 다른 출처에서 사용할 수 있기 때문이다.
  • <link href="https://fonts.googleapis.com/css2?family=Gugi&display=swap" rel="stylesheet">: Google Fonts의 Gugi font(CSS 파일)를 불러온다.
    • 동작 과정
      •  HTML 파싱: 브라우저가 <link> 태그를 만나면 **href**에 지정된 URL인 https://fonts.googleapis.com/css2?family=Gugi&display=swap로 요청(Request)을 보낸다.
      • fonts.googleapis.com 서버는 요청된 폰트의 설정 정보를 담은 CSS 파일을 반환한다.
        • 이 CSS 파일에는 폰트 파일(실제 리소스)이 어디에 있는지 @font-face로 정의되어 있다. 
      • 브라우저는 CSS 파일을 확인한 후, 폰트 파일이 저장된 fonts.gstatic.com에 접근해 실제 폰트 파일(.ttf)을 다운로드한다.
      • 폰트 파일이 모두 로드되면, 지정된 텍스트에 폰트를 적용한다.
        • 이 과정에서 display=swap 옵션 덕분에 폰트 로딩 전에는 기본 폰트(fallback font)를 사용하다가, 폰트 로딩이 완료되면 화면에 적용된다.
      • (CSS 파일 예시)
@font-face {
  font-family: 'Gugi';
  font-style: normal;
  font-weight: 400;
  src: url(https://fonts.gstatic.com/s/gugi/v10/A2BVn5dXywshVKG1QnWJovb6aFU.ttf) format('truetype');
}

 


<style> 태그에 아래 예시와 같이 font-family를 설정한다.

html {
    font-family: "Gugi";
    font-weight: 400;
    font-style: normal;
}

 

Gugi를 적용한 HTML 예시

<!DOCTYPE html>
<html lang="ko">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Gugi&display=swap" rel="stylesheet">
</head>
<style>
    html {
        font-family: "Gugi";
        font-weight: 400;
        font-style: normal;
    }

    .normal {
        font-weight: normal;
    }

    .bold {
        font-weight: bold;
    }

    .custom {
        font-weight: 900;
    }
</style>

<body>
    <p>안녕하세요</p>
    <p class="normal">안녕하세요</p>
    <p class="bold">안녕하세요</p>
    <p class="custom">안녕하세요</p>
</body>

</html>

위 코드는 아래와 같이 화면을 렌더한다.

 

※ 참고: fonts.googleapis.com에서부터 로드된 CSS 파일의 @font-face의 역할

@font-face는 CSS 규칙 중 하나로, 웹페이지에서 custom font를 사용할 수 있게 해준다. 이 규칙을 사용하면 로컬 font가 아닌 외부 font를 가져와 웹페이지에 적용할 수 있다.

역할

  • Font 파일 선언:  웹페이지에서 사용할 font 파일을 지정한다. 다양한 형식(woff2, woff, ttf, eot 등)을 지원한다.
  • Custom font 이름 설정: font에 이름(font-family)을 부여해 나중에 CSS에서 해당 font를 사용할 수 있게 한다.
  • 다양한 font 스타일 정의: font 굵기(font-weight), 스타일(font-style), 문자 범위(unicode-rage, 선택 사항) 등을 지정할 수 있다.

기본 문법

@font-face {
    font-family: 'CustomFont'; /* 사용할 font의 이름 */
    src: url('CustomFont.woff2') format('woff2'),
         url('CustomFont.woff') format('woff'); /* font 파일 경로 및 형식 */
    font-weight: normal; /* font 굵기 */
    font-style: normal;  /* font 스타일 */
}

 

 

2. 직접 @font-face를 HTML <style> 태그에 정의해서 Custom Font를 쓰는 방법

@font-face 규칙을 직접 <style> 태그에 입력해서 사용하는 방법이다.

 

방법 1. 외부 서버에서 제공하는 font file을 사용할 수 있도록 @font-face를 직접 명시하기

 

눈누에서 제공하는 "쿠키런 글꼴" font를 사용해보자.

https://noonnu.cc/font_page/322

 

눈누

쿠키런 - 데브시스터즈(주)

noonnu.cc

 

우측의 "웹폰트로 사용"의 @font-face 코드를 복사한다.

 

복사한 코드를 아래처럼 HTML 파일에 붙여넣는다.

<!DOCTYPE html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        @font-face {
            font-family: "GmarketSansMedium";
            src: url("https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_2001@1.1/GmarketSansMedium.woff") format("woff");
            font-weight: normal;
            font-style: normal;
        }

        p {
            font-family: "GmarketSansMedium";
        }

        .normal {
            font-weight: normal;
        }

        .bold {
            font-weight: bold;
        }

        .custom {
            font-weight: 900;
        }
    </style>
</head>

<body>
    <p>안녕하세요</p>
    <p class="normal">안녕하세요</p>
    <p class="bold">안녕하세요</p>
    <p class="custom">안녕하세요</p>

</body>

</html>

font-family는 "GmarketSansMedium"로 정의되있고, p에 적용하였다.

위 코드는 아래와 같이 화면을 렌더한다.

 

 

방법 2. 로컬에 저장되있는 font 파일을 사용하도록 @font-face에서 직접 명시하기

로컬(웹 페이지의 디렉터리 기준)에 font 파일이 있다면, 이를 사용할 수 있다.

 

아래는 인터넷에서 "온글잎 박다현체" font 파일을 다운로드해서 로컬에서 사용한 예시이다.

<!DOCTYPE html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        @font-face {
            font-family: "Ownglyph_ParkDaHyun";
            src: url("./온글잎박다현체.ttf") format("woff");
            font-weight: normal;
            font-style: normal;
        }

        p {
            font-family: "Ownglyph_ParkDaHyun";
        }

        .normal {
            font-weight: normal;
        }

        .bold {
            font-weight: bold;
        }

        .custom {
            font-weight: 900;
        }
    </style>
</head>

<body>
    <p>안녕하세요</p>
    <p class="normal">안녕하세요</p>
    <p class="bold">안녕하세요</p>
    <p class="custom">안녕하세요</p>

</body>

</html>

위 코드는 아래와 같이 화면을 렌더한다.

 

 

요약

HTML 파일에서 CSS를 사용해 Font 스타일을 설정하는 방법을 아래와 같이 나눠서 알아보았다.

  1. Font 제공 서비스에서 제공하는 font 관련 정보(CSS 파일)를 먼저 확보한 다음, 해당 CSS 파일에 명시된 정보를 통해 외부 서버에 위치한 font 파일에 접근하기
  2. @font-face 직접 명시하기
    1. 외부 서버에서 제공하는 font file을 사용할 수 있도록 @font-face에 직접 명시하기
    2. 로컬에 저장되있는 font 파일을 사용하도록 @font-face에 직접 명시하기
반응형

'HTML&CSS' 카테고리의 다른 글

[HTML&CSS] CSS Box Model  (0) 2024.12.18
[HTML&CSS] HTML Boilerplate & HTML 기본 구조  (2) 2024.12.16
반응형

HTML Boilerplate

HTML Boilerplate란 HTML 문서를 생성하기 위한 기본 구조 또는 템플릿이다. HTML 문서를 적절히 포매팅하고 다양한 브라우저에서 작동하도록 해주는 필수 element와 tag들이 포함되어 있다.

HTML Boilerplate는 웹 개발 시작 초기에 사용되는 템플릿이다.

아래와 같은 boilerplate 코드를 모든 HTML 파일에 추가해야 한다.

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>
</html>

 

HTML 기본 구조

HTML의 기본 구조를 이해하기 위해, boilerplate에서 내용이 좀 더 추가된 아래 코드를 분석해보자.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="A brief description of the webpage.">
    <meta name="keywords" content="HTML, CSS, JavaScript, web development">
    <meta name="author" content="Your Name">
    <title>Document</title>
    <link rel="stylesheet" href="styles.css">
    <link rel="icon" href="favicon.ico" type="image/x-icon">
    <link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
    <link rel="preload" href="main.js" as="script">
    <script src="script.js" defer></script>
</head>
<body>
    <header>
        <h1>Welcome to My Website</h1>
    </header>
    
    <main>
        <p>This is a basic HTML boilerplate template.</p>
    </main>
    
    <footer>
        <p>&copy; 2024 Your Name</p>
    </footer>

    <script src="script.js"></script>
</body>
</html>

 

위 코드의 컴포넌트들을 하나씩 설명한다.:

  • <!DOCTYPE html>: HTML Living Standard 문서임을 명시한다. HTML Living Standard는 점진적으로 진화 중인, 현재 최신의 HTML 표준을 가라킨다.
  • <html>: HTML 문서의 루트(최상단 element)이며, 문서의 시작과 끝을 나타낸다.
    • lang="en" lang 속성은 HTML 문서의 주 언어를 선언한다. 이 경우엔 영어이며, 한국어로 지정하고 싶다면 "en" 대신 "ko"를 명시해야 한다.
  • <head>: 문서의 메타데이터를 정의하는 부분이다. 웹페이지의 설정, 정보, 스타일 및 스크립트 등을 포함한다.
    • <meta>: 웹페이지의 메타 정보를 담고 있다. 문서의 가장 윗부분에 적시되며, 웹페이지 자체에 디스플레이되지는 않지만, 웹페이지가 어떻게 동작할지, 인덱스될지, 스타일링될지 등의 핵심적인 정보를 제공한다.
      • <meta charset="UTF-8">: charset 속성은 캐릭터 인코딩을 UTF-8로 세팅한다. UTF-8로 하면 세계 각종 언어의 거의 대부분을 커버할 수 있다.
      • <meta name="viewport" content="width=device-width, initial-scale=1.0">: 이 웹페이지가 모바일 장치의 디스플레이에서도 정상적으로 표시되도록 하는 반응형 디자인을 갖추도록 제어한다.
      • <meta name="description" content="A brief description of the webpage.">: 검색엔진에 의해 검색되는 부분이며, 검색결과의 미리보기 텍스트(snippet)로 활용된다.
      • <meta name="keywords" content="HTML, CSS, JavaScript, web development">: 과거엔 중요했지만, 현대의 검색엔진에선 무시된다.
      • <meta name="author" content="Your Name">: 작성자 정보를 나타낸다.
      • <title>: 웹페이지의 제목을 나타낸다. 브라우저의 tab이나 검색엔진의 검색결과에 나타난다.
    • <link>: 외부리소스와 현재 HTML 문서를 연결하는 역할을 한다. 빈 태그와 속성만을 포함하고, <head> 안에만 위치할 수 있다. rel 속성은 대상 파일의 속성을 나타내며, href 속성은 연결 시 참조할 파일의 위치를 나타낸다.
      • <link rel="stylesheet" href="styles.css">: 스타일링을 위해 외부의 CSS 파일을 링크한다.
      • <link rel="icon" href="favicon.ico" type="image/x-icon">: 브라우저 탭에 표시될 작은 아이콘(파비콘)을 설정한다.
      • <link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
        </head>: 외부 custom font를 로드한다.
      • <link rel="preload" href="main.js" as="script">: 리소스를 미리 로드하여 성능 최적화
      • <link rel="prefetch" href="future-page.html">: 리소스를 예비로 로드하여 성능 최적화
    • <script>: JavaScript 코드를 HTML 문서에 포함하거나 외부 JavaScript 파일을 연결함으로써, 웹 페이지의 동적 기능을 추가하는 데 사용된다. 
      • <head> 내에 <script>를 삽입하는 경우, 주로 문서 로딩 시 필요한 라이브러리나 설정 정보를 포함한다. 다만, <head>에 넣으면 페이지 로딩이 완료되기 전에 JavaScript가 실행되므로 페이지 로딩 성능에 영향을 줄 수 있다. 일반적으로 <body> 끝쪽에 넣는 것이 더 효율적이다.
      • <script src="script.js" defer></script>: 외부 Javascript 파일인 script.js을 연결한다. defer 속성은 HTML의 로딩이 끝난 뒤에 script를 동작시키도록 한다. defer가 아닌 async가 사용된 경우, JavaScript 파일을 비동기적으로 로드하여 페이지 렌더링에 영향을 미치지 않도록 한다.
  • <body>: 웹페이지의 본문을 나타내며, 실제로 사용자에게 보여지는 콘텐츠가 포함된다. 즉 페이지의 시각적인 부분 및 상호작용하는 요소들이 들어가는 영역이다. 웹페이지의 모든 내용은 <body> 안에 작성되어야 한다. 브라우저는 <body> 내부의 내용을 해석해서 렌더한다.
    • <header>: 웹페이지를 소개하는 내용 또는 navigational link를 담는다. 보통 웹사이트의 로고, 제목, 또는 메인 navigation 메뉴를 포함한다.
      • <h1>Welcome to My Website</h1>: 웹사이트의 title을 디스플레이 하고 있다.
    • <main>: 웹페이지의 주 콘텐츠(text, image 등)가 위치한다. 
      • <p>This is a basic HTML boilerplate template.</p>: 웹페이지를 묘사하는 단순한 문단이다.
    • <footer>: 웹페이지의 footer 콘텐츠(저작권, contact 정보, 링크 등)가 위치한다.
      • <p>&copy; 2024 Your Name</p>: 저작권 및 이름 정보가 담긴 문단이다.
    • <script>: 외부 JavaScript 파일을 연결하여 웹페이지의 동적 기능을 추가한다.
      • <head><script> 부분에서 말했듯이, <head> 안에 배치될 수 있지만, 성능 최적화를 위해 <body> 끝에 배치하는 것이 일반적이다. 또한  asyncdefer 속성을 사용하여 JavaScript 파일의 로딩 방식을 제어할 수 있다.
      • <script src="script.js"></script>: 외부 Javascript 파일인 script.js을 연결한다.

 

(브라우저 렌더 결과)

 

 

참고 링크)

https://www.freecodecamp.org/news/basic-html5-template-boilerplate-code-example/

 

Basic HTML5 Template: Use This HTML Boilerplate as a Starter for Any Web Dev Project

When you are building a new website, it is important to have a good starting foundation. In this article, I will explain what an HTML 5 boilerplate is and how to create a basic template to use in your projects. What is an HTML 5 boilerplate? Accordi...

www.freecodecamp.org

 

반응형

'HTML&CSS' 카테고리의 다른 글

[HTML&CSS] CSS Box Model  (0) 2024.12.18
[HTML&CSS] Font 설정하기  (0) 2024.12.17
반응형

https://www.acmicpc.net/problem/4613

 

파이썬에서 아스키 코드를 숫자로 변환하는 ord() 함수를 사용하면 알파벳을 매핑되는 숫자로 쉽게 변환할 수 있는 점을 이용했다.

그리고 문자열의 각 문자별 index를 쉽게 구하기 위해, enumerate()도 사용했다.

이번 문제부터 본격적으로 input()print()를 대신해서, 성능 개선을 위해 앞으로 쭉 sys.stdinsys.stdout 모듈 함수를 사용하려 한다.

(정답 코드)

import sys

lines = sys.stdin.read().splitlines()[:-1]

ans = []
for line in lines:
    sum = 0
    for i, c in enumerate(line, 1):
        if c != " ":
            sum += i * (ord(c) - ord("A") + 1)
    ans.append(str(sum))

sys.stdout.write("\n".join(ans))

반응형
반응형

https://www.acmicpc.net/problem/1620

 

처음에 문제를 읽었을 때는 단순히 리스트를 사용해서 쉽게 풀 수 있겠다 싶었다.

그래서 문제에서 주어지는 모든 input 정보를 하나의 리스트에 단순하게 append해버렸다.

그러나 이렇게 하면 무조건 시간 초과가 발생한다.

(시간 초과 발생 코드 1)

n, m = map(int, input().split())
pokemon = [""]
for _ in range(n):
    pokemon.append(input())
for _ in range(m):
    q = input()
    if q.isdigit():
        print(pokemon[int(q)])
    else:
        print(pokemon.index(q))

 

그래서 리스트가 아닌, 해싱이 적용된 딕셔너리를 사용하는 방법으로 수정했다. 

그러나 이번에도 (분명 내부적으로 시간 개선은 되었겠지만) 시간 초과가 발생했다.

(시간 초과 발생 코드 2)

n, m = map(int, input().split())
pokedex = {}
for i in range(1, n + 1):
    pokemon_name = input()
    pokedex[pokemon_name] = i
    pokedex[str(i)] = pokemon_name
for _ in range(m):
    q = input()
    print(pokedex[q])

 

여기까진 input을 받을 때 오직 input()만 사용한 naive한 코드이다.

나는 그동안 파이썬에서 문자열 input을 받을 때 사용하기 쉽다는 이유로 input() 함수를 써왔다.

그러나 이번 문제를 통해, 그랬다가는 시간 초과가 뜰 수 있는 문제가 있다는 것을 제대로 체감하게 되었다.

다른 블로그 글들을 참고하여, sys.stdinsys.stdout 모듈을 사용하는 방법을 택하였다.

(정답 코드)

import sys

n, m = map(int, sys.stdin.readline().split())
pokedex = {}
for i in range(1, n + 1):
    pokemon_name = sys.stdin.readline().rstrip()
    pokedex[pokemon_name] = str(i)
    pokedex[str(i)] = pokemon_name
for _ in range(m):
    q = sys.stdin.readline().rstrip()
    sys.stdout.write(pokedex[q] + "\n")

 

추가적으로 시도하진 않았지만, 더 최적화할 수 있는 여지가 충분히 있다는 것도 깨달았다.

현재 위 코드에서는 sys.stdin.readline()의 호출을 input을 한 줄 읽을 때마다 여러번 반복하고 있다. 그러나 문제에서 모든 input을 한번에 입력받을 수 있기 때문에, 굳이 한 줄씩 읽지 않아도 된다.

그리고 현재는 정답 출력도 한 줄씩만 수행하고 있다.

Input을 전부 단 한번의 read로 읽고, 정답을 단 한번의 write로 수행하면 성능이 아주 많이 개선될 것이다.

추후에 추가 최적화 작업을 해보겠다.

반응형
반응형

 

https://comgu.tistory.com/entry/Python-Iteration의-원리Iterator-Protocol

 

[Python] Iteration의 원리(Iterator Protocol)

Python에서 iteration(반복)은 iterator 프로토콜을 기반으로 동작한다.이 원리를 이해하려면 아래 개념들을 알아야 한다. 1. Iterable (Iterate 가능한 객체)Iterable은 __iter__() 메서드를 구현한 객체이다. for

comgu.tistory.com

 

앞서 파이썬에서 iteration이 어떻게 동작하는지, iterator protocol을 중심으로 살펴보았다.

본 글에서는 파이썬의 이터러블(iterable)이면서 동시에 이터레이터(iterator)인 제너레이터에 대해 알아본다.

 

제너레이터(Generator)

제너레이터는 이터레이터를 생성하는 함수로, 데이터를 한 번에 하나씩 생성하여 메모리를 효율적으로 사용할 수 있다.

제너레이터는 일반 함수와 비슷하지만, 데이터를 반환할 때 return 대신 yield 키워드를 사용한다.

아래의 기본적인 사용 방법을 보자.

def my_generator():
    yield 1
    yield 2
    yield 3

gen = my_generator()  # 제너레이터 객체 생성

print(next(gen))  # 1
print(next(gen))  # 2
print(next(gen))  # 3
print(next(gen))  # StopIteration 익셉션 발생
1
2
3
StopIteration 익셉션 발생

동작 과정

  1. yield는 값을 반환하면서 함수의 실행을 멈춘다.
  2. 다음 호출 시 함수는 이전에 멈췄던 위치부터 실행을 재개한다.
  3. 더 이상 yield 값이 없으면 StopIteration 예외가 발생한다.

 

제너레이터의 특징

  1.  효율적인 메모리 사용: 한 번에 모든 데이터를 메모리에 올리지 않고 필요할 때마다 하나씩 생성한다.
  2. 이터러블(Iterable)하다: for 루프와 같은 이터레이션 구조에서 사용할 수 있다.
  3. 상태 유지: 호출될 때마다 마지막으로 멈췄던 위치를 기억한다.

 

제너레이터의 활용 예시

1. 대규모 데이터 처리

제너레이터를 사용하면 대규모 데이터를 메모리 사용 낭비 없이 처리할 수 있다.

def read_large_file(file_name):
    with open(file_name) as file:
        for line in file:
            yield line.strip()

for line in read_large_file("large_file.txt"):
    print(line)

read_large_file() 함수는 제너레이터 함수이다. yield를 사용해서 한 번에 하나의 라인을 반환한다.

yield를 통해 한 줄을 처리하고 나서 함수의 상태를 보존한 채 대기하고, 다음 호출이 오면 그 상태에서 재개한다.

  • 참고로 file에 대해서 for loop이 동작할 수 있는 이유는, file 객체 자체가 iterable이기 때문이다. 파이썬의 파일 객체는 내부적으로 iterator로 동작하도록 설계되어 있기 때문이다. 즉 __iter__와 __next__를 구현하고 있다:
    • __iter__(): 파일 객체 자체를 반환한다(즉 file.__iter__() == file).
    • __next__(): 파일에서 다음 라인을 읽어 반환한다. 파일의 끝에 도달하면 StopIteration 익셉션을 발생시킨다.

for line in read_large_file("large_file.txt")는 제너레이터를 통해 파일의 각 라인을 한번에 하나씩 가져온다. 이 방식은 한 번에 파일 전체를 메모리에 로드하지 않기 때문에 메모리 사용량이 최소화된다.

즉 1개 라인씩 파일에서 읽고 처리하므로 메모리 사용량은 1개 라인씩만 증가한다. 따라서 파일 크기와 무관하게 일정한 메모리만 사용한다.

 

이를 일반적인 파일 읽기 방식과 비교해보자.

with open("large_file.txt") as file:
    lines = file.readlines()  # 파일의 모든 내용을 메모리에 로드

위 방식은 파일의 모든 라인을 메모리에 로드하므로, 큰 파일의 경우 메모리 부족 문제가 발생할 수 있다.

 

2. 무한 제너레이터

무한 루프를 제너레이터로 구현하여 메모리 소모 없이 값을 끝없이 생성할 수 있다.

무한루프가 사용되므로, 제너레이터를 사용하는 쪽에서는 특정 조건을 달성하는 경우 적절히 break문을 사용해서 반복을 중단시켜줘야 한다.

def infinite_sequence():
    num = 0
    while True:
        yield num
        num += 1


gen = infinite_sequence()
for i in gen:
    if i > 10:
        break
    print(i)
0
1 
2 
3 
4 
5 
6 
7 
8 
9 
10

 

3. 제너레이터 컴프리헨션(Generator Comprehension)

제너레이터 컴프리헨션(Generator Comprehension)은 리스트 컴프리헨션이나 딕셔너리/셋 컴프리헨션과 비슷하지만, 결과를 한 번에 메모리에 저장하는 대신 필요할 때마다 요소를 생성하는 제너레이터 객체를 반환하는 표현식이다. 메모리 사용량을 줄이고 효율적으로 데이터 처리를 할 수 있는 장점을 가진다.

문법

(expression for item in iterable if condition)

 

  • expression: 각 요소에 적용할 연산
  • item: 반복 가능한 객체(iterable)에서 가져온 각 요소
  • iterable: 반복 가능한 객체
  • condition: 선택적으로 요소를 필터링하는 조건

자칫 "튜플 컴프리헨션"으로 오해할 수 있으니 조심하자.

특징

  • ( )를 사용하여 정의하며, lazy evaluation(지연평가: 필요할 때 계산한다는 뜻)을 수행한다.
  • 리스트 컴프리헨션 [ ]과 비슷하지만 결과는 리스트 대신 제너레이터 객체로 반환된다.

예시

 

gen = (x for x in range(1000) if x % 3 == 0 or x % 5 == 0)
print(gen)

result = sum(gen)
print(result)  # 0부터 999까지의 3 또는 5의 배수 합
<generator object <genexpr> at 0x0000022AA625F1D0>
233168

gen은 제너레이터 컴프리헨션을 통해 도출된 제너레이터이다. sum과 같은 소비 함수에 제너레이터 gen을 사용할 수 있다.

 

 

리스트 컴프리헤션 vs 제너레이터 컴프리헨션

numbers = [x ** 2 for x in range(10**6)]  # 모든 데이터를 메모리에 저장

 

numbers = (x ** 2 for x in range(10**6))  # 필요한 데이터만 생성

 

다양한 상황에서의 제너레이터 컴프리헨션 사용

제너레이터 컴프리헨션은 대용량 파일 처리, 스트리밍 데이터 처리, 파일 스트림 처리, 데이터베이 쿼리 결과 처리, 메모리 사용량 최적화 등 메모리를 절약해서 사용해야 하는 상황에서 유용하게 사용될 수 있다. 

Lazy evaluation(지연 평가) 덕분에, 데이터를 필요한 시점에만 요청할 때마다 하나씩 생성하며, 남은 값들은 여전히 계산되지 않은 상태로 요청 전까지 대기시킬 수 있는 것이다.

 

 

(Deep Dive) 제너레이터는 곧 이터레이터!

파이썬에서 이터레이터는 __iter__()__next__() 메소드를 구현한 객체이다.

제너레이터는 특별한 형태의 이터레이터로, yield를 사용해 직접 구현한 함수를 실행할 때 자동으로 이터레이터를 생성한다.

제너레이터 함수는 실행시 제너레이터 객체를 반환하며, 이 객체는 이터레이터의 성질(즉 __iter__와 __next__ 메소드)를 가지고 있다.

def my_generator():
    yield 1
    yield 2
    yield 3

gen = my_generator()

print("__iter__" in dir(gen))  # True (반복 가능한 객체)
print("__next__" in dir(gen))  # True (이터레이터)
True
True

위에서 볼 수 있듯, 제너레이터 함수 안에 __iter__와 __next__의 구현부가 보이지 않지만, 제너레이터는 자동으로 __iter__와 __next__ 메서드를 구현하고 있음을 알 수 있다.

※ 제너레이터 객체는 __iter____next__가 이미 구현된 상태로 만들어진다는 의미는, 제너레이터 내부적으로 C로 구현된 메커니즘을 통해 __iter____next__를 자동으로 제공한다는 뜻이다. 이 덕분에 제너레이터 함수는 간단하게 yield를 사용하면서도 이터레이터로 동작할 수 있는 것이다. (generator의 C 구현은 CPython에서 PyGenObject라는 C 구조체와 관련 함수 등을 분석해야 한다.)

 

def my_generator():
    yield 1
    yield 2
    yield 3


gen = my_generator()

print(gen.__iter__() is gen)  # True: 제너레이터 객체는 자기 자신을 반환
print(gen.__next__())  # 1: 첫 번째 `yield` 값
print(gen.__next__())  # 2: 두 번째 `yield` 값
print(gen.__next__())  # 3: 세 번째 `yield` 값

위 코드를 통해 다음을 확인할 수 있다:

  • 제너레이터의 __iter__() : 제너레이터 객체 자신을 반환한다. 따라서 제너레이터는 반복 가능한 객체이다.
  • 제너레이터의 __next__(): yield로 값을 반환하고, 다음 yield 문에서 멈추는 기능을 수행한다.

실제로 제너레이터에 대한 반복을 실행할 때, __iter__()와 __next__()가 under the hood에서 동작하고 있음을 알 수 있다!

반응형
반응형

https://comgu.tistory.com/entry/Python-데코레이터Decorator-1-데코레이터-중첩-데코레이터

https://comgu.tistory.com/entry/Python-데코레이터Decorator-2-동적-데코레이터

데코레이터에 대한 마지막 주제로, 클래스형 데코레이터에 대해 다뤄보겠다.

 

클래스형 데코레이터

클래스형 데코레이터는 데코레이터를 클래스 형태로 구현하는 방식이다.

함수형 데코레이터와 마찬가지로, 클래스형 데코레이터도 함수나 메서드에 특정 기능을 추가하거나 수정할 때 사용된다.

클래스형 데코레이터의 핵심은 클래스의 __call__ 메소드를 정의하여 인스턴스가 호출 가능하도록 만드는 것이다.

실제로 함수도 "function" 클래스의 인스턴스이고 __call__ 메소드를 갖기 때문에 함수명()의 형태로 호출이 가능한 것이다.

class MyDecorator:
    def __init__(self, func):
        self.func = func  # 데코레이트할 함수 저장

    def __call__(self, *args, **kwargs):
        print("함수 실행 이전")
        result = self.func(*args, **kwargs)
        print("함수 실행 이후")
        return result


@MyDecorator
def add(x, y):
    print(f"{x} + {y} = {x + y}")


add(3, 4)
함수 실행 이전
3 + 4 = 7     
함수 실행 이후

실행순서

  1. @MyDecorator가 데코레이트된 함수(즉 add)에 적용될 때, MyDecorator 클래스의 __init__ 메서드가 호출되어 add가 func으로 전달된다.
  2. 데코레이트된 함수(add)가 호출될 때, __call__ 메서드가 실행된다.
  3. __call__ 메서드 안에서 추가 작업을 수행한 후 원래의 함수(self.func)를 호출한다.

 

만약 add 함수가 데코레이터를 사용하지 않았다면, 클래스형  데코레이터를 사용하기 위해 아래와 같은 방식을 사용한 것과 동일하다.

def add(x, y):
    print(f"{x} + {y} = {x + y}")


MyDecorator(add)(3, 4)
함수 실행 이전
3 + 4 = 7     
함수 실행 이후

 

클래스형 데코레이터의 장점

  • 상태 유지: 클래스 내부에 상태를 저장할 수 있어, 함수 호출 횟수 등을 추적할 수 있다.
  • 구조화된 코드: 복잡한 데코레이터 로직을 객체 지향적으로 관리할 수 있다.

 

 

상태를 저장하는 클래스형 데코레이터

클래스형 데코레이터를 쓰면, 클래스 내부에 상태를 저장하여 관리할 수 있다.

class CallCounter:
    def __init__(self, func):
        self.func = func
        self.count = 0  # 호출 횟수 추적

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"{self.func.__name__} 함수 실행 카운트: {self.count}")
        return self.func(*args, **kwargs)


@CallCounter
def greet(name):
    print(f"Hello, {name}!")


greet("AAA")
greet("BBB")
greet("CCC")
greet 함수 실행 카운트: 1
Hello, AAA!
greet 함수 실행 카운트: 2
Hello, BBB!
greet 함수 실행 카운트: 3
Hello, CCC!

 

만약 greet 함수가 데코레이터를 사용하지 않았다면, 클래스형 데코레이터를 사용하기 위해 아래와 같은 방식을 사용한 것과 동일하다.

def greet(name):
    print(f"Hello, {name}!")


CallCounter(greet)("AAA")
CallCounter(greet)("BBB")
CallCounter(greet)("CCC")
greet 함수 실행 카운트: 1
Hello, AAA!
greet 함수 실행 카운트: 1
Hello, BBB!
greet 함수 실행 카운트: 1
Hello, CCC!

 

 

 

(고급) 파라미터를 받는 클래스형 데코레이터

클래스형 데코레이터에 인자를 전달하려면, 데코레이터의 역할을 하는 클래스의 인스턴스가 내부의 wrapper 함수를 한번 더 리턴할 수 있도록 해주면 된다.

class Repeat:
    def __init__(self, times):
        self.times = times

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            for _ in range(self.times):
                func(*args, **kwargs)

        return wrapper


@Repeat(3)
def hello():
    print("Hello!")


hello()
Hello!
Hello!
Hello!

 

만약 hello 함수가 데코레이터를 사용하지 않았다면, 클래스형 데코레이터를 사용하기 위해 아래와 같은 방식을 사용한 것과 동일하다.

def hello():
    print("Hello!")


Repeat(3)(hello)()
Hello!
Hello!
Hello!

 

 

이것으로  데코레이터 시리즈의 마지막 글을 마친다.

(나중에 더 다룰 주제가 있으면 4탄 이상으로 추가할 생각이다)

반응형
반응형

https://comgu.tistory.com/entry/Python-데코레이터Decorator-1-데코레이터-중첩-데코레이터

위 글에 이어서 데코레이터에 대한 개념을 더 정리하고자, 동적 데코레이터에 대해 다뤄보겠다.

 

동적 데코레이터

동적 데코레이터는 실행 중에 동적으로 조건에 따라 동작하도록 설정할 수 있는 데코레이터를 의미한다.

여러 가지 사용 예시를 통해 동적 데코레이터의 원리에 대해 살펴보자.

 

1. 조건에 따라 데코레이터 함수의 동작을 동적으로 변경할 수 있는 예시

def conditional_decorator(condition):
    def decorator(func):
        if condition:

            def wrapper1(*args, **kwargs):
                print(f"[1] condition is {condition}")
                return func(*args, **kwargs)

            return wrapper1

        else:

            def wrapper2(*args, **kwargs):
                print(f"[2] condition is {condition}")
                return func(*args, **kwargs)

            return wrapper2

    return decorator

conditional_decorator는 내부의 decorator를 정의하며, decorator 함수를 반환한다.

내부의 decorator는 바로 데코레이터 함수 본체이며, 데코레이터가 적용될 함수 func를 파라미터로 받는다. conditional_decorator의 조건인 condition 변수를 참조하며 해당 condition에 따라 다른 wrapper 함수를 반환한다.

wrapper1wrapper2condition에 따라 실행되는 함수이며, 데코레이터를 사용한 함수 func를 호출한다.

만약 True를 아규먼트로 넘긴 conditional_decorator를 데코레이터로 쓰는 아래 함수가 있다면,

@conditional_decorator(True)
def say_hello():
    print("Hello, World!")
    
say_hello()
[1] condition is True
Hello, World!

이는 만약 say_hello()에 데코레이터가 적용이 되지 않았을 경우의에 아래 코드와 동일하다.

conditional_decorator(True)(say_hello)()

 

만약 False를 아규먼트로 넘긴 conditional_decorator를 데코레이터로 쓰는 아래 함수가 있다면,

@conditional_decorator(False)
def say_hello():
    print("Hello, World!")
    
say_hello()
condition is False
Hello, World!

이는 만약 say_hello()에 데코레이터가 적용이 되지 않았을 경우의 아래 코드와 동일하다.

conditional_decorator(False)(say_hello)()

 

conditional_decorator의 파라미터를 더 일반적인 함수 파라미터 형태로 바꿔도 잘 동작한다.

아래처럼 conditional_decorator에서 argskwargs 파라미터로 받게 한뒤, decorator에서 해당 조건에 따라 서로 다른 wrapper 함수를 리턴하도록 해봤다.

def conditional_decorator(*args, **kwargs):
    def decorator(func):
        def wrapper1(*args, **kwargs):
            print("wrapper1 start")
            result = func(*args, **kwargs)
            print("wrapper1 end")
            return result

        def wrapper2(*args, **kwargs):
            print("wrapper2 start")
            result = func(*args, **kwargs)
            print("wrapper2 end")
            return result

        if sum(args) > 10 and kwargs.get("key1", None) == "value1":
            return wrapper1
        else:
            return wrapper2

    return decorator


@conditional_decorator(1, 2, 3, 4, 5, key="value1")
def hello55value1(*args, **kwargs):
    print(sum(args), kwargs.get("k1", None))


@conditional_decorator(0)
def helloNone(*args, **kwargs):
    print(args[0] * args[1] * args[2], kwargs.get("k2", None))


hello55value1(1, 2, 3, k1="!!")
helloNone(5, 7, 9, k2="??")
wrapper1 start
6 !!
wrapper1 end  
wrapper2 start
315 ??        
wrapper2 end

 

만약 hello55value1 함수와 helloNone 함수에 데코레이터 적용이 없었다면, 각각 아래와 같이 함수를 호출한 것과 동일하다.

def hello55value1(*args, **kwargs):
    print(sum(args), kwargs.get("k1", None))


def helloNone(*args, **kwargs):
    print(args[0] * args[1] * args[2], kwargs.get("k2", None))


conditional_decorator(1, 2, 3, 4, 5, key1="value1")(hello55value1)(1, 2, 3, k1="!!")
conditional_decorator(0)(helloNone)(5, 7, 9, k2="??")
wrapper1 start
6 !!
wrapper1 end  
wrapper2 start
315 ??        
wrapper2 end

 

 

2. 조건에 따라 wrapper 함수의 동작을 동적으로 변경할 수 있는 예시

def repeat(n):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(n):
                func(*args, **kwargs)

        return wrapper

    return decorator


# 동적 데코레이터 사용
@repeat(n=3)
def greet(name):
    print(f"Hello, {name}!")


greet("John")
Hello, John!
Hello, John!
Hello, John!

repeat 함수는 파라미터 n을 받아 데코레이터를 생성하고 반환한다.

decorator 함수는 데코레이터 함수 본체이며, 데코레이터가 적용될 함수 func를 파라미터로 받는다. wrapper 함수를 반환함으로써 원래 함수(func)에 반복 실행 기능을 추가한다.

wrapper 함수는 데코레이터가 추가 동작을 수행하도록 하는 기능을 외부에서 추가해주는 기능을 추가해준다. 최상위 repeat 함수의 n 변수를 참조하고 있으며, n의 값에 따라 다르게 동작한다(func 함수의 실행 횟수가 바뀐다).

"1. 조건에 따라 데코레이터 함수의 동작을 동적으로 변경할 수 있는 예시" 에서는 decorator 함수가 상위의 조건 변수를 참조했다면, 이 예시에서는 wrapper 함수가 조건 변수(n)을 참조하고 있다는 점에서 다르다.

repeat(n) 데코레이터는 repeat의 정의 시점이 아닌, 실행 시점에 반복 횟수(n)을 동적으로 설정한다. 이를 통해 하나의 데코레이터를 다양한 방식으로 재사용할 수 있다.

 

3. 클래스 메서드에 동적 데코레이터를 적용한 예시

def dynamic_decorator(method_type):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(f"{method_type} method called")
            return func(*args, **kwargs)

        return wrapper

    return decorator


class MyClass:
    @dynamic_decorator("Instance")
    def instance_method(self, methodtype):
        print(f"{methodtype} method running")

    @staticmethod
    @dynamic_decorator("Static")
    def static_method(methodtype):
        print(f"{methodtype} method running")


obj = MyClass()
obj.instance_method("INSTANCE")
MyClass.static_method("STATIC")
Instance method called
INSTANCE method running
Static method called   
STATIC method running

클래스의 메서드에 동적 데코레이터를 적용하는 방법을 보여주는 예시이다.

dynamic_decoratormethod_type라는 문자열 파라미터를 받고, 데코레이터를 정의 및 리턴한다.

decorator가 리턴하는 wrapper 함수는 method_type를 출력하고 데코레이터를 사용하는 func 함수를 내부에서 실행한다.

MyClass 클래스는 dynamic_decorator 를 사용하는 2가지 메소드를 정의한다.

  • instance_method: 인스턴스를 통해 호출되는 인스턴스 메소드.
  • static_method: 클래스에 바인딩되어 인스턴스와 무관하게 호출되는 메소드. @staticmethod를 사용한다.

 

만약 MyClass 클래스의 두 메소드가 아래와 같이 데코레이터를 사용하지 않았다면,

class MyClass:
    def instance_method(self, methodtype):
        print(f"{methodtype} method running")

    @staticmethod
    def static_method(methodtype):
        print(f"{methodtype} method running")

데코레이터 함수를 사용하기 위해 각각 아래와 같은 함수 호출을 사용한 것과 동일하다.

obj = MyClass()
dynamic_decorator("Instance")(obj.instance_method)("INSTANCE")
dynamic_decorator("Static")(MyClass.static_method)("STATIC")
Instance method called
INSTANCE method running
Static method called   
STATIC method running

 

다음 글에서는 데코레이터의 마지막 주제로, 클래스형 데코레이터에 대해 다뤄보고자 한다.

https://comgu.tistory.com/entry/Python-데코레이터Decorator-3-클래스형-데코레이터

 

[Python] 데코레이터(Decorator) 3 - 클래스형 데코레이터

https://comgu.tistory.com/entry/Python-데코레이터Decorator-1-데코레이터-중첩-데코레이터https://comgu.tistory.com/entry/Python-데코레이터Decorator-2-동적-데코레이터데코레이터에 대한 마지막 주제로, 클래스형 데

comgu.tistory.com

반응형
반응형

파이썬에서 데코레이터란 무엇인지, 그 동작에 대해 알아본다. 

먼저 그에 앞서 파이썬의 클로저(Closure)의 개념을 이해하면 더 수월하게 이해할 수 있다. 관련해서는 클로저를 다룬 글을 참고하면 된다

https://comgu.tistory.com/entry/Python-클로저Closure-함수

 

[Python] 클로저(Closure) 함수

파이선에서 클로저 함수란 무엇인지, 그 동작에 대해 알아본다. 또한 클로저와 데코레이터의 관계도 파악한다. 클로저 (Closure)클로저란, 함수 안에서 정의된 내부 함수가 외부 함수의 지역 변수

comgu.tistory.com

 

 

데코레이터(Decorator)

데코레이터는 함수나 메서드의 동작을 동적으로 확장하거나 수정할 수 있는 강력한 도구이다. 주로 코드 재사용성, 가독성, 유지보수를 개선하기 위해 사용된다.

데코레이터는 다른 함수를 인수로 받아 새로운 함수를 반환하는 함수이며, @ 기호를 사용해서 함수의 정의 위에 적용된다.

def decorator(func):
    def wrapper(*args, **kwargs):
        print("함수 호출 이전")
        result = func(*args, **kwargs)
        print("함수 호출 이후")
        return result
    return wrapper

@decorator
def hello():
    print("Hello, world!")

hello()
함수 호출 이전
Hello, world! 
함수 호출 이후

데코레이터를 사용하지 않은 경우, hello 함수의 호출은 다음과 같이 나타내야 한다. (즉 데코레이터를 쓴다는 것은 실제로는 아래의 방식대로 함수 호출이 된다는 의미이다.)

def hello():
    print("Hello, world!")
decorator(hello)()

decorator(hello)decorator 내부의 wrapper 함수를 의미한다. wrapper 함수는 상위의 decorator 함수의 scope에 있는 func 함수(즉 hello)를 참조하고 있는 상태이다. 그래서 wrapper 함수가 실행될 때, wrapper가 감싸고 있는 func(즉 hello) 함수가 호출될 수 있는 것이다.

 

데코레이터 내부의 wrapper 함수는 원 함수와 동일한 형식의 파라미터를 받아야 한다. 그렇지 않으면 데코레이터가 원래 함수에 제대로 적용되지 않을 수 있다.

보통 데코레이터의 wrapper 함수는 *args**kwargs를 사용하여 원 함수의 파라미터를 모두 받아 처리한다. 이렇게 하면 원 함수가 어떤 파라미터를 받든지 유연하게 대응할 수 있다.

def my_decorator(func):
    def wrapper(*args, **kwargs):  # 원 함수의 파라미터를 그대로 받음
        print("함수 호출 이전")
        result = func(*args, **kwargs)
        print("함수 호출 이후")
        return result

    return wrapper


@my_decorator
def say_hello(name):
    print(f"Hello, {name}!")


say_hello("world")
함수 호출 이전
Hello, world!
함수 호출 이후

 

위 코드에서 wrapper 함수는 *args, **kwargs를 사용하여 say_hello 함수의 인자 name을 그대로 받을 수 있다.이 방식으로 데코레이터는 원 함수의 시그니처에 맞춰 유연하게 작동할 수 있다.

 

 

중첩(다중) 데코레이터

여러 데코레이터를 동시에 적용할 수도 있다.

아래는 3중 데코레이터를 적용한 예시이다.

def decorator1(func):
    def wrapper(*args, **kwargs):
        print("Decorator 1")
        return func(*args, **kwargs)

    return wrapper


def decorator2(func):
    def wrapper(*args, **kwargs):
        print("Decorator 2")
        return func(*args, **kwargs)

    return wrapper


def decorator3(func):
    def wrapper(*args, **kwargs):
        print("Decorator 3")
        return func(*args, **kwargs)

    return wrapper


@decorator1
@decorator2
@decorator3
def my_function():
    print("Original function")


my_function()
Decorator 1
Decorator 2
Decorator 3
Original function

함수 정의에 가까운 데코레이터부터의 순서대로 적용된다. 첫번째 데코레이터는 원 함수의 호출을 감싸게 되고, 그 다음부터의 데코레이터는 이전 데코레이터에 의해 반환된 결과를 감싸게(wrap) 된다.

즉 데코레이터를 사용하지 않은 경우, my_function의 호출은 다음과 같이 나타내야 한다:

decorator1(decorator2(decorator3(my_function)))()

 

다음 글에서는 동적 데코레이터와 클래스 데코레이터에 대해 다뤄보고자 한다.

https://comgu.tistory.com/entry/Python-데코레이터Decorator-2-동적-데코레이터

 

[Python] 데코레이터(Decorator) 2 - 동적 데코레이터

https://comgu.tistory.com/entry/Python-데코레이터Decorator-1-데코레이터-중첩-데코레이터위 글에 이어서 데코레이터에 대한 개념을 더 정리하고자, 동적 데코레이터에 대해 다뤄보겠다. 동적 데코레이터

comgu.tistory.com

반응형
반응형

파이선에서 클로저 함수란 무엇인지, 그 동작에 대해 알아본다. 또한 클로저와 데코레이터의 관계도 파악한다.

 

클로저 (Closure)

  • 클로저란, 함수 안에서 정의된 내부 함수가 외부 함수의 지역 변수를 참조하고, 외부 함수의 실행이 끝난 뒤에도 그 변수의 메모리를 계속 참조하는 함수이다. 외부 함수가 종료된 뒤에도 변수가 소멸하지 않는다.
  • 외부 함수의 변수를 캡처하여 내부 함수에서 계속 사용할 수 있기 때문에, 클로저를 사용하면 특정 메모리 공간에 계속 접근 가능하면서 동작 가능한 함수를 생성할 수 있다.

 

클로저의 구성 요건

  1. 중첩 함수: 함수 안에 또 다른 함수가 정의되어 있어야 한다.
  2. 외부 함수 변수 참조: 내부 함수가 외부 함수의 변수를 참조해야 한다.
  3. 외부 함수의 리턴값: 외부 함수는 내부 함수를 리턴해야 한다.

 

아래 간단한 클로저의 예시를 보자.

def outer_function(x):
    # 외부 함수의 지역 변수
    def inner_function(y):
        print(f"x = {x}")
        return x + y  # 외부 변수 x 참조

    return inner_function  # 내부 함수를 반환


# 외부 함수를 호출하고 결과로 내부 함수를 가져옴
closure1 = outer_function(10)
closure2 = outer_function(20)
print(f"hex(id(closure1)) = {hex(id(closure1))}, hex(id(closure2)) = {hex(id(closure2))}")

# 내부 함수 호출
print(f"closure1(5) = {closure1(5)}")  # 10 + 5 = 15
print(f"closure2(5) = {closure2(5)}")  # 20 + 5 = 25
hex(id(closure1)) = 0x231de68d1c0
hex(id(closure2)) = 0x231de68d260
x = 10
closure1(5) = 15
x = 20
closure2(5) = 25

inner_fuction은 외부 함수인 outer_function의 변수 x를 참조하고 있다.

closure1 = outer_function(10)를 통해 closure1 변수가 클로저 함수를 가리킨다.

closure2 = outer_function(10)를 통해 closure2 변수도 클로저 함수를 가리킨다.

주소값을 통해 확인할 수 있듯, closure1closure2는 각기 다른 클로저 함수를 가리키고 있음을 알 수 있다.

중요한 점은 closure1closure2가 단순히 inner_function이라는 함수를 가리킨다는 것만이 아니라, 그것을 감싸는  outer_function의 실행 컨텍스트(즉 외부 변수 x의 값)을 함께 캡처(capture)하고 있다는 점이다.

 

클로저의 활용(장점)

1. 데이터의 은닉: 외부 변수에 직접 접근하지 못하도록 보호한다. 

위 예시에서 외부 변수 x의 값은 외부로부터 직접 접근할 수 없다(=은닉되고 있다).

xouter_function의 지역 변수이며, outer_function의 호출 이후에는 이 외부 변수는 클로저 함수의 호출을 통해서만 간접적으로 접근 가능하고 외부에서는 직접 접근할 수 없다. 즉 외부에서는 x라는 이름으로 값을 확인하거나 어떤 다른 방법으로 해당 값을 수정할 수 있는 방법이 없다. 

즉 아래와 같은 코드로 외부 변수 x를 수정할 수 없다.

closer1.x = 30

 

2. 상태 유지: 클로저는 함수 호출 사이에 상태를 유지할 수 있는 방법을 제공한다.

외부 변수의 값을 수정하는 클로저의 예시 코드를 보자.

def counter():
    count = 0
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

counter_instance = counter()
print(counter_instance())  # 1
print(counter_instance())  # 2
print(counter_instance())  # 3

nonlocal 키워드는 클로저 내부에서 외부 함수의 변수(즉 count)를 수정할 때 사용된다.

총 3번의 함수 호출 동안 count의 값은 계속해서 클로저에 의해 접근 및 참조되고 있음을 확인할 수 있다.

클로저 함수인 counter_instance는 계속해서 외부 변수인 count에 대한 reference를 유지하고 있기 때문이다.

클로저의 이런 특성은, OOP(객체지향 프로그래밍)와 유사한 기능을 제공한다. 파이썬 OOP에서는 인스턴스의 변수와 이를 조작하는 메소드를 통해 "상태"를 관리하는데, 클로저를 사용하면 클래스와 인스턴스 없이도 비슷한 방식으로 상태를 관리할 수 있다는 의미이다.

특징 객체지향 프로그래밍(OOP) 클로저
구조 클래스와 객체 사용 함수와 변수 사용
상태 저장 위치 객체의 인스턴스 변수 클로저의 외부 함수 변수
상태 변경 방식 메서드를 통해 변경 클로저의 내부 함수로 변경
사용 사례 복잡한 상태 관리, 재사용성 높은 설계 간단한 상태 관리, 함수형 프로그래밍 스타일

 

 

클로저 사용 시 주의할 점(단점)

1. 클로저 함수를 지나치게 많이 사용할 경우, 메모리에 오래 유지될 수 있는 변수들로 인해 메모리 누수 가능성이 있다.

클로저 내부에서 외부 함수의 지역 변수를 참조하면, 클로저가 삭제되지 않는 한 그 변수도 메모리에서 제거되지 않는다.

따라서 코드 흐름 상 해당 변수가 더 이상 필요하지 않더라도, 클로저가 살아 있는 한, 파이썬 가비지 컬렉터에 의해 해당 객체의 메모리를 해제할 수가 없게 된다.

이 변수들의 크기가 크거나 갯수가 많다면, 불필요한 메모리 점유가 발생할 수 있다.

 

2. 클로저의 사용은 코드의 가독성과 유지보수성을 저해할 수 있다.

클로저는 함수 안에 또 다른 함수를 정의하고 반환하므로, 코드가 중첩되면서 가독성이 떨어지게 된다. 특히 함수의 실행 컨텍스트가 여러 단계로 나뉘어서, 변수의 흐름을 이해하기가 어려워진다.

def outer_function(a):
    def middle_function(b):
        def inner_function(c):
            return a + b + c  # 여러 레벨의 변수 참조
        return inner_function
    return middle_function

result = outer_function(1)(2)(3)
print(result)  # 6

위 코드의 경우, abc 변수가 어떤 변수인지 추적하기 어렵다. 그리고 중첩된 함수가 많으므로 흐름을 읽기가 어렵다.

 

 

클로저의 상세한 작동원리 영상

유튜브에 파이썬의 클로저의 작동 원리를 매우 잘 설명한 영상이 있다: https://youtu.be/tNSOaA1z6Uo

 

 

클로저와 데코레이터의 관계

  • 데코레이터는 다른 함수를 감싸는 함수로, 원래 함수에 기능을 추가하거나 동작을 변경한다.
    • 데코레이터는 함수를 입력으로 받고, 새로운 함수를 반환한다.
    • (데코레이터에 대한 상세 글은 추후 작성할 예정)
  • 데코레이터는 클로저를 기반으로 동작한다.
  • 데코레이터 안에서 내부 함수(wrapper 함수)는 외부 함수(데코레이터)의 변수(데코레이터의 인자 함수)를 참조해야 하기 때문에, 클로저를 자연스럽게 사용하게 된다.

클로저와 데코레이터의 관계를 볼 수 있는 아래 예시 코드를 보자.

def my_decorator(func):
    def wrapper(*args, **kwargs):  # 내부 함수 (클로저)
        print("함수 호출 전")
        result = func(*args, **kwargs)
        print("함수 호출 후")
        return result
    return wrapper

@my_decorator
def add(a, b):
    return a + b

print(add(2, 3))
함수 호출 전
함수 호출 후
5

 

@my_decorator 를 사용한 add(2, 3) 호출은 아래의 코드와 완전히 동일하다.

my_decorator(add)(2, 3)

my_decorator(add)my_decorator의 내부 함수인 wrapper를 리턴받는데, 이것이 바로 클로저 함수이다. 

해당 클로저 함수는 wrapper의 외부 변수인 func에 대한 참조를 유지하고 있다. 이 덕분에 클로저 함수인 wrapper를 호출했을 때, wrapper 내부에서 func를 호출할 수 있는 것이다.

참고로 add의 함수 파라미터 목록과 wrapper의 함수 파라미터 목록은 동일해야 한다. 그래야 wrapper에서 func(즉 add)를 호출할 때 올바른 아규먼트를 전달할 수 있다.

반응형
반응형

파이썬의 sorted() 함수를 사용하면 쉽게 풀 수 있는 문제다.

key를 지정할 때 2가지 기준(나이와 입력 순서)를 사용하도록 지정했다. 

그러나 파이썬은 stable sort를 지원하므로, 사실 1가지 기준(나이만) key로 지정해도 된다는 것은 뒤늦게 알았다.

Stable sort란, 정렬의 기준이 같은 요소들 간의 상대적인 순서를 유지하는 정렬 방식이다. 이 문제의 경우 입력 순서대로 list를 처음부터 채울 수 있기 때문에, 그냥 나이 순으로만 정렬해도 문제가 없다.

 

(정답 코드)

n = int(input())
people = []
for i in range(n):
    age, name = input().split(" ")
    people.append((int(age), name, i))

people_sorted = sorted(people, key=lambda x: (x[0], x[2]))
for i in people_sorted:
    print(i[0], i[1])

 

https://www.acmicpc.net/problem/10814

 

반응형

+ Recent posts