반응형

Django 프로젝트를 개발시 CSS, JavaScript, 이미지와 같은 정적 파일들을 관리해야 할 필요가 있다. Django는 이러한 정적 파일들을 효율적으로 관리할 수 있는 시스템을 제공한다. Django의 정적 파일 관리 방법에 대해 정리해봤다.

자세한 내용은 공식문서를 확인하자 : https://docs.djangoproject.com/en/5.1/ref/settings/#std-setting-STATIC_URL

 

Settings | Django documentation

The web framework for perfectionists with deadlines.

docs.djangoproject.com

 

 

기본 설정하기

먼저 프로젝트 설정(config) 디렉토리의 settings.py에서 정적 파일 관련 설정을 해야 한다:

STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"]
  • STATIC_URL: 웹 URL에서 정적 파일을 참조할 때 사용할 기본 URL prefix이다.
    • STATIC_URL의 목적은 템플릿에서 "정적 파일 URL의 접두어 문자열 설정", 즉 정적파일의 URL 생성이다. 
    • 예를 들어 http://example.com/static/css/style.css와 같이 "static/"이 URL의 일부가 된다.
    • 배포 환경에서는 웹서버(nginx, apache 등)이 /static/에 대한 요청이 올 때 어떤 디렉터리에서 파일을 서빙할지 웹서버의 설정파일에 개발자가 명시해야 한다.
  • STATICFILES_DIRS: Django가 정적 파일을 찾아볼 추가 디렉토리 목록을 지정한다.
    • 위 설정은 "프로젝트 루트의 static 폴더에서도 정적 파일을 찾아보라"는 의미이다.
    • STATICFILES_DIRS는 주로 프로젝트 단위 공통 static 파일을 관리하려고 지정한다.
    • 배포시에 collectstatic이 STATICFILES_DIRS까지 참고해서 정적 파일을 모으나, 실제 서비스에서는 웹서버(Nginx 등)들이 STATICFILES_DIRS가 아닌 STATIC_ROOT만 바라본다.

 

템플릿에서 정적 파일 사용하기

템플릿에서 정적 파일을 사용하려면 두 가지 단계가 필요하다:

1. 템플릿 최상단에 static 템플릿 태그를 로드한다:

{% load static %}

{% load static %} 탬플릿 태그는,  static 템플릿 태그를 사용 가능하게 함으로써 CSS, JavaScript, 이미지 등의 정적 파일을 템플릿에서 사용할 수 있게 해준다.

2. static 템플릿 태그를 사용하여 정적 파일을 참조한다:

<!DOCTYPE html>
<html>
<head>
    <title>My Site</title>
    <link rel="stylesheet" href="{% static 'css/main.css' %}">
</head>
<body>
    <img src="{% static 'images/logo.jpg' %}" alt="Logo">
    <script src="{% static 'js/app.js' %}"></script>
</body>
</html>
{% static ... %} 는 정적 파일의 실제 URL을 (STATIC_URL 설정에 따라) 자동으로 생성한다.
현재 STATIC_URL'static/'으로 설정되있으므로, 아래와 같이 변환된다.
  • {% static 'css/main.css' %}  '/static/css/main.css'
  • {% static 'images/logo.jpg' %}  '/static/images/logo.jpg'
  • {% static 'js/app.js' %}  '/static/js/app.js'

참고로, STATIC_URL이 상대경로일 때, SCRIPT_NAME이라는 값이 설정되있지 않다면, '/'가 prefix된다. 그래서 STATIC_URL'static/'이어도 '/static/...'으로 변환되는 것이다.

# 일반적인 경우
STATIC_URL = 'static/'
결과: '/static/css/style.css'  # SCRIPT_NAME 없을 때 '/'가 붙음

# 서브 경로에서 실행되는 경우
SCRIPT_NAME = '/myapp'
STATIC_URL = 'static/'
결과: '/myapp/static/css/style.css'

 

{% static ... %} 태그가 해석되는 시점은, Django가 템플릿을 렌더링할 때이다. 구체적인 과정을 살펴보면, 

1. 브라우저가 페이지 요청
2. Django 뷰 함수 실행
3. 템플릿 렌더링 시작
4. {% static 'css/main.css' %} 발견
5. STATIC_URL + 파일경로로 변환 
   예: 'static/' + 'css/main.css' -> 'static/css/main.css'
6. 변환된 URL이 포함된 HTML을 브라우저에 전송

 

프로젝트 구조

일반적인 Django 프로젝트의 정적 파일 구조는 다음과 같다:

my_project/
    ├── manage.py
    ├── my_project/
    │   ├── settings.py
    │   └── urls.py
    ├── static/           # 프로젝트 레벨 정적 파일
    │   ├── css/
    │   ├── js/
    │   └── images/
    └── my_app/
        └── static/      # 앱 레벨 정적 파일

 

정적 파일 탐색

{% static 'css/style.css' %}

위와 같은 탬플릿 파일의  {% static ... %} 태그는 URL을 생성하는 역할을 수행한다.

브라우저는 해당 URL이 적힌 html 파일을 수신한다. 그리고 이 URL로 요청을 보낸다.

요청을 받을 때, Django 서버가 현재 개발 환경인지, 아니면 운영 환경인지에 따라 대응 로직이 다르다:

 

1. 개발 환경(DEBUG=True) 일 때

파일 탐색 시점

  • 브라우저가 정적 파일 URL을 요청할 때마다 실시간으로 탐색.
  • Django 개발 서버가 요청을 받은 시점에 탐색 시작.

탐색 프로세스

  1. 브라우저가 정적파일의 URL로 정적 파일을 요청.
  2. Django 개발 서버가 요청 수신.
  3. staticfiles 앱이 다음 순서로 실시간 탐색:
    • STATICFILES_DIRS에 지정된 경로들 확인.
    • INSTALLED_APPS의 각 앱의 static 폴더 확인.
  4. 파일을 찾으면 해당 파일 제공, 못 찾으면 404 반환.

(URL 경로의 파일이 양쪽 위치에 모두 있다면, STATICFILES_DIRS에 있는 파일이 우선적으로 사용된다.)

위에 언급했듯, 정적 파일을 탐색하는 주체는 staticfiles 앱(django.contrib.staticfiles)이다. 

정리하자면, Django의 개발 서버는 직접 정적 파일 요청이 들어올 때 때마다 실시간으로 파일을 찾아서 제공한다.

 

2. 운영 환경(DEBUG=False) 일 때

파일 탐색 시점

  • 배포 전 python manage.py collectstatic 실행 시 한 번만 수행.
  • 실제 서비스 중에는 탐색 과정 없음.
python manage.py collectstatic
  • 배포를 위해 모든 정적 파일을 한 곳으로 수집할 때 실행하는 명령이다.
  • STATIC_ROOT 디렉토리로 파일들을 복사한다.
    • STATIC_ROOT는 Django가 실행 중인 서버의 로컬 디렉터리이다.
    • 즉 운영 환경(배포)에서는 STATIC_ROOT를 settings.py에 미리 설정해둬야 한다.
STATIC_ROOT = BASE_DIR / 'staticfiles'

탐색 프로세스

  1. collectstatic 실행 시:
    • STATICFILES_DIRS 경로들 탐색.
    • INSTALLED_APPS의 각 앱의 static 폴더 탐색.
    • 발견된 모든 정적 파일을 STATIC_ROOT 디렉토리로 복사.
  2. 서비스 운영 중:
    • 브라우저가 정적 파일을 요청.
    • .웹 서버(Nginx, Apache 등)가 STATIC_ROOT 디렉토리에서 직접 파일 제공
      • 웹 서버가 /static/으로 시작하는  URL을 감지하고, 웹서버의 설정에 따라 특정 디렉토리(Django에서 STATIC_ROOT로 명시한 곳과 같은 디렉토리) 내부에서 파일을 찾고, 직접 클라이언트에 전송.
    • Django는 이 과정에 관여하지 않음
      • 따라서 Django 서버의 부하를 줄일 수 있음.
      • 웹서버는 정적 파일 서빙에 더 최적화 되어있으므로 서빙이 Django보다 더 빠름.
      • 필요하다면, 웹서버에서 정적파일을 캐싱할 수도 있음.

만약 정적 파일을 외부 서버에 두고 싶으면?

만약 정적 파일을 AWS S3, CloudFront, CDN, 혹은 다른 외부 서버에 올리고 싶다면:

  • STATIC_ROOT는 일단 로컬 경로로 지정해두고, 
  • collectstatic 명령어를 이용해서 로컬에 모은 다음,
  • 그걸 외부 서버로 업로드하는 추가 작업을 해야한다.
    • 예: django-storages 라이브러리를 사용해서 S3에 바로 업로드하도록 설정할 수 있다.

 

정적 파일 관리의 장점

  1. 개발/배포 환경 분리 (동일한 코드로 동작)
    • 개발 환경에서는 Django의 개발 서버가 정적 파일을 직접 제공
    • 배포 환경에서는 웹 서버나 CDN이 정적 파일을 제공
  2. 유연한 파일 경로 관리
    • STATIC_URL 설정만 변경하면 모든 정적 파일의 경로가 자동으로 업데이트
    • 파일 위치가 변경되어도 템플릿 코드를 수정할 필요가 없음
  3. 성능 최적화
    • 캐싱과 버전 관리를 위한 파일 핑거프린팅 지원
    • collectstatic 명령어를 통한 효율적인 정적 파일 관리

 

마무리

Django의 정적 파일 관리 시스템은 개발자가 효율적으로 정적 리소스를 관리할 수 있게 해준다.특히 {% static %} 템플릿 태그를 통해 유연하고 유지보수가 쉬운 방식으로 정적 파일을 참조할 수 있다. 이는 프로젝트가 커지고 복잡해져도 정적 파일 관리를 일관되게 유지할 수 있게 해주는 큰 장점이다.

반응형
반응형

파이썬에서 import 문을 쓸 때, 다양한 형태의 import 문을 마주치게 된다.

경험상, import 문의 동작 방식을 제대로 이해하지 못하면 예상치 못한 문제들을 마주할 수 있다.

 

먼저, import 대상이 점(.)으로 시작하는 경우와 그렇지 않은 경우가 있는데, 언제 점(.)을 쓰고 안 써야 하는지 정리해보려고 한다.

다음으로 import문의 충돌 문제에 대해서도 다뤄보겠다.

 

1. 절대 경로 import (점 없는 import)

점(.) 없는 import 문이 모듈을 찾는 경로는 Python의 sys.path에 등록된 모든 경로이다.

sys.path란 Python이 모듈을 검색할 때 참조하는 경로 목록으로 Python Path라고 부르며, 다음 경로들을 포함한다:

  1. 현재 실행중인 스크립트가 있는 디렉토리(sys.path[0])
  2. PYTHONPATH 환경변수에 등록된 디렉토리들
  3. Python이 설치될 때 기본으로 등록된 디렉토리들 (표준 라이브러리가 위치한 lib 디렉토리와 외부 라이브러리들이 설치되는 site-packages 디렉토리)

이런 방식의 import는 크게 세 가지 경우에서 볼 수 있다:

1.1 파이썬 기본 모듈

파이썬 설치 경로 밑의 (표준 라이브러리가 위치한 lib 디렉토리)에서 찾는다.

from datetime import datetime
import os
import sys

1.2 외부 라이브러리

lib 디렉토리 밑의 site-packages 폴더에서 찾는다.

import pandas
import numpy as np
from sklearn.model_selection import train_test_split

1.3 Python Path에 등록된 로컬 패키지

크게 3가지 경우이다:

  1. 현재 (스크립트의) 실행 위치(sys.path[0])
  2. PYTHONPATH 환경변수에 등록된 경로들
  3. 프레임워크나 도구가 자동으로 sys.path에 추가하는 경로들.

로컬 패키지가 Python Path에 등록되는 것은, Django 같은 웹 프레임워크에서 자주 볼 수 있는 케이스이다.

Django의 경우 프로젝트의 루트 디렉토리가 Python Path에 자동으로 추가되어, 앱 단위의 모듈을 절대 경로로 import할 수 있다.

# Django 프로젝트의 urls.py
from main.views import index  # main은 로컬 앱 이름
from blog.models import Post  # blog는 로컬 앱 이름

이것이 가능한 이유는, 파이썬의 기본동작과 manage.py 스크립트 실행 방식 때문에 프로젝트 루트 디렉토리가 Python Path에 추가되기 때문이다.

  • 파이썬은, 파이썬으로 스크립트를 직접 실행할 때(ex: python manage.py 실행) 스크립트가 위치한 디렉토리를 sys.path의 첫번째 항목(sys.path[0])에 자동 추가한다.
    • 참고로, 스크립트가 타 프로그램에 의해 간접 실행되는 경우, sys.path[0]에는 그 프로그램이 실행된 디렉토리(CWD)가 설정된다. 이는 아래의 "상대 경로 import(점 있는 import)" 절에서 더 다룬다.
  • manage.py는 Django 프로젝트의 루트 디렉토리에 위치하므로, python manage.py를 실행하면 루트 디렉토리가 sys.path[0]에 자동으로 추가된다.

아래처럼 manage.py를 수정해보면, manage.py 파일이 위치한 디렉토리(프로젝트 루트 디렉토리)가 출력되는 것을 확인 가능하다.

import sys
import os

# ... 코드 생략 ...

if __name__ == '__main__':
    print(sys.path[0])  # 현재 디렉토리인 프로젝트 최상위 디렉토리가 출력됨
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
    execute_from_command_line(sys.argv)

이렇게 해서 프로젝트 내의 모든 앱을 최상위 네임스페이스에서 접근 가능하게 된다(달리 말하면, sys.path에 프로젝트 루트 디렉토리가 포함되므로, 코드 작성시 프로젝트 내의 앱 디렉토리를 최상위 네임스페이스처럼 사용할 수 있게 된다).

 

1.1~1.3의 예시들을 정리하자면, 

  1. Python 기본 모듈 (예: datetime)은 Python 설치 경로의 기본 라이브러리 lib에서 찾는다.
  2. 외부 라이브러리 (예: pandas)는 Python 설치 경로의 site-packages에서 찾는다.
  3. Django의 앱 같은 로컬 패키지는 프로젝트 루트 디렉토리(Djangomanage.py 실행시 sys.path에 추가됨)에서 찾는다.

 

 

2. 상대 경로 import (점 있는 import)

현재 파일의 위치를 기준으로 다른 모듈의 위치를 지정하는 방식이다.

from .utils import helper          # 현재 디렉토리의 utils
from ..models import User          # 상위 디렉토리의 models
from ...core import settings       # 두 단계 상위 디렉토리의 core
 
프로젝트 실행 위치에 따라 import 문의 동작이 달라질 수 있다.
아래 구조의 FastAPI 프로젝트를 예로 들어보자:
my_fastapi_project/
    ├── src/
    │   ├── main.py
    │   ├── api/
    │   │   └── v1/
    │   │       └── endpoints.py
    │   └── utils/
    │       └── helpers.py

src 디렉토리에서 uvicorn으로 FastAPI 서버를 실행할 경우:

  1. 해당 명령어를 실행한 디렉토리 srcsys.path[0]에 추가된다.
  2. 이는  src에서 python main.py를 실행한 것과 동일한 효과를 가진다.
    • main.py가 위치한 src를 직접 sys.path[0]으로 넣는 효과를 의미한다.
# src/ 디렉토리에서
uvicorn main:app --reload
# src/main.py
from api.v1.endpoints import router  # ✅ 동작함
from utils.helpers import format_response  # ✅ 동작함

 

반면, 프로젝트 루트(my_fastapi_project)에서 uvicorn으로 FastAPI 서버를 실행할 경우:

  1. my_fastapi_projectsys.path[0]이 된다.
  2. uvicornmain.py를 직접 실행한 게 아니라, 자신의 CWD(현재 작업 디렉토리)에서 스크립트를 실행한 것이기 때문이다.
    • 스크립트를 간접 실행하는 프로그램의 경우, sys.path[0]에는 그 프로그램이 실행된 디렉토리(CWD)가 설정된다.
    • 이는 파이썬의 기본 동작이므로, uvicorn, pytest, 또는 다른 파이썬 기반 도구에서도 동일한 원칙이 적용된다.
  3. 따라서 위 예시의 import 문들은 동작하지 않는다.
# my_fastapi_project/ 디렉토리에서
uvicorn src.main:app --reload
# src/main.py
from api.v1.endpoints import router  # ❌ 실패
from src.api.v1.endpoints import router  # ✅ 동작함

# 또는 상대 경로 사용
from .api.v1.endpoints import router  # ✅ 동작함
 

 

3. 실제 프로젝트 예시

실제 프로젝트에서 import문을 어떻게 사용하는지 살펴보기 위해, 다음과 같은 구조의 프로젝트가 있다고 가정해보자:

myproject/
    └── src/
        ├── utils/
        │   ├── __init__.py
        │   └── helper.py
        └── models/
            ├── __init__.py
            ├── user.py
            └── services/
                └── auth.py

auth.py 파일에서 다른 모듈을 import할 때, 절대 경로 방식과 상대 경로 방식이 어떻게 사용되는지 비교해보자:

(myproject 폴더가 sys.path[0]에 등록된 상태임을 가정)

# 절대 경로 방식
from src.utils.helper import some_function
from src.models.user import User

# 상대 경로 방식
from ...utils.helper import some_function
from ..user import User

 

 

4. 언제 어떤 방식을 사용해야 할까?

상대 경로 import가 좋은 경우

  • 같은 프로젝트 내의 모듈을 import 할 때
  • 프로젝트 구조가 자주 변경될 수 있을 때
  • 코드의 재사용성을 높이고 싶을 때

절대 경로 import가 좋은 경우

  • 외부 라이브러리를 사용할 때
  • 프로젝트 구조가 안정적일 때
  • 코드의 명확성이 더 중요할 때

 

 

5. import 충돌 문제

동일한 import 문이 여러 모듈을 가리킬 수 있어 충돌이 발생할 수 있다.

주요 시나리오들을 살펴보자:

5.1 sys.path 충돌

# 프로젝트 구조:
my_project/
    utils/
        helper.py
    venv/
        lib/
            site-packages/
                utils/
                    helper.py  # pip로 설치된 외부 라이브러리

# my_project/main.py
from utils.helper import something  # 어떤 helper.py를 import 할까?

이 경우, 파이썬은 sys.path의 첫 번째로 매칭되는 모듈을 import한다.

5.2 이름 충돌

# 여러 utils.py가 있는 경우
from auth.utils import helper
from billing.utils import helper  # 이전 helper를 덮어씀

5.3 순환 import

# a.py
from .b import b_function

def a_function():
    print("This is a_function")
    b_function()

# b.py
from .a import a_function

def b_function():
    print("This is b_function")
    a_function()

이런 순환 import 문제는 특히 Django나 FastAPI 같은 웹 프레임워크에서 Model과 Serializer, 또는 Model과 View 사이에서 자주 발생한다. 가장 좋은 해결책은 애초에 순환 참조가 발생하지 않도록 코드를 구조화하는 것이지만, 불가피한 경우 위와 같이 지역 import를 사용하여 해결할 수 있다.

5.5 import 충돌 방지를 위한 Best Practices

1. 명확한 alias 사용하기

❌ alis 미사용:

# 여러 utils.py가 있는 경우
from auth.utils import helper
from billing.utils import helper  # 이전 helper를 덮어씀

✅ 명확한 alias 사용:

from auth.utils import helper as auth_helper
from billing.utils import helper as billing_helper

2. 패키지 이름을 구체적으로 짓기

❌ 안 좋은 예:

my_project/
    utils/
        date.py
        string.py    # Python 기본 모듈인 string과 이름이 같음
        json.py      # Python 기본 모듈인 json과 이름이 같음

✅ 좋은 예:

my_project/
    custom_utils/               # 더 구체적인 이름
        date_formatter.py      # 기능을 더 명확히 표현
        string_validator.py    # 기능을 더 명확히 표현
        json_handler.py       # 기능을 더 명확히 표현

3. 가능하면 상대경로보다 절대경로를 사용하기

❌ 복잡한 상대 경로:

# my_project/apps/users/views/auth/login.py
from ....utils.validators import validate_email  # 점이 너무 많아 혼란스러움
from ...models.user import User                 # 경로 파악이 어려움

✅ 명확한 절대 경로:

# my_project/apps/users/views/auth/login.py
from my_project.utils.validators import validate_email  # 명확한 경로
from my_project.apps.users.models import User          # 어디서 import하는지 바로 알 수 있음

4. 로컬 모듈과 외부 라이브러리 이름이 겹치지 않도록 주의하기

❌ 이름 충돌 가능성 있는 구조:

my_project/
    requests/            # requests는 유명한 HTTP 라이브러리 이름
        api.py
    json/               # json은 Python 기본 모듈 이름
        parser.py

✅ 충돌을 피한 구조:

my_project/
    http_client/        # requests 대신 더 구체적인 이름
        api.py
    json_processing/    # json 대신 더 구체적인 이름
        parser.py

이렇게 구조화하면, import문의 의미가 명확해진다.

# 혼란스러울 수 있는 import
import requests  # 외부 라이브러리? 로컬 모듈?
from json import loads  # Python 기본 모듈? 로컬 모듈?

# 명확한 import
import requests  # 확실히 외부 라이브러리
from http_client import api  # 확실히 로컬 모듈
from json_processing import parser  # 확실히 로컬 모듈

 

 

마무리

import 문은 단순해 보이나, 실제로는 복잡한 규칙과 주의사항들이 있다. 특히 프로젝트가 커질수록 import 충돌 문제는 디버깅이 어려운 버그의 원인이 될 수 있다. import에 대한 규칙들을 잘 이해하면, 더 견고한 파이썬 프로젝트를 만들 수 있다.

반응형

'Python > 문법' 카테고리의 다른 글

[Python] dataclass  (0) 2025.01.27
[Python] Module과 Package  (0) 2025.01.18
[Python] Module import로 이해하는 Namespace와 Scope  (1) 2025.01.09
[Python] Unpacking의 다양한 예시  (1) 2024.12.27
[Python] Iterable과 Sequence 자료형  (0) 2024.12.27
반응형

아래는 FastAPI에서 정적 파일을 제공하는 간단한 예시 코드이다.

# main.py

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse

app = FastAPI()

# 정적 파일을 위한 디렉토리 마운트
app.mount("/static", StaticFiles(directory="static"), name="static")

@app.get("/", response_class=HTMLResponse)
async def read_root():
    return """
    <html>
        <head>
            <title>FastAPI Static Files Example</title>
        </head>
        <body>
            <h1>FastAPI Static Files 예시</h1>
            <img src="/static/example.jpg" alt="예시 이미지">
            <link rel="stylesheet" href="/static/styles.css">
        </body>
    </html>
    """

위 코드를 실행하려면 아래와  같은 디렉토리 구조가 필요하다.

your_project/
├── main.py (위 파일)
└── static/
    ├── example.jpg
    └── styles.css

 

정적 파일을 위한 디렉토리 마운트를 수행하는 코드를 각 파라미터 별로 살펴보자.

app.mount("/static", StaticFiles(directory="static"), name="static")

 

  1. "/static" (첫 번째 파라미터):
    • URL 경로를 지정한다.
    • 웹 애플리케이션에서 정적 파일에 접근할 때 사용할 기본 URL 경로이다.
      • ex: "/static/images/photo.jpg"처럼 접근
    • 다른 경로로도 변경 가능하다 (예: "/assets", "/public" 등)
  2. StaticFiles(directory="static") (두 번째 파라미터):
    • 실제 파일 시스템의 디렉토리를 지정한다
    • directory="static": 프로젝트 폴더 내의 'static' 폴더를 의미
    • 이 디렉토리 안에 있는 모든 파일들이 첫 번째 파라미터에서 지정한 URL 경로를 통해 접근 가능해진다.
    • 절대 경로도 사용 가능하다 (예: directory="/path/to/files")
  3. name="static" (세 번째 파라미터):
    • 마운트 지점의 이름을 지정한다.
    • FastAPI의 URL 생성 기능에서 이 이름을 참조할 때 사용 가능하다.
    • 템플릿에서 URL을 동적으로 생성할 때 이 이름을 사용할 수 있다(특히 Jinja2 템플릿에서 많이 사용됨).
    • 선택적 파라미터이다. (생략 가능)

 

정적 파일  마운팅의 여러 예시

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

app = FastAPI()

# 기본적인 사용
app.mount("/static", StaticFiles(directory="static"), name="static")

# 다른 경로와 디렉토리 사용
app.mount("/assets", StaticFiles(directory="public_files"), name="assets")

# 절대 경로 사용
app.mount("/files", StaticFiles(directory="/var/www/files"), name="files")

 

 

실제 파일 시스템과 URL의 관계 예시

파일 시스템:        URL 접근:
static/             
├── images/         /static/images/
│   └── logo.png    /static/images/logo.png
├── css/            /static/css/
│   └── style.css   /static/css/style.css
└── js/             /static/js/
    └── script.js   /static/js/script.js

 

반응형
반응형

파이썬을 처음 배우다 보면 다음과 같은 의문이 생길 수 있다:

"서로 다른 파일에서 함수와 변수가 어떻게 서로를 찾아가는 걸까?"

 간단한 예제를 통해 이 개념을 명확하게 이해해보자.

 

아래와 같이 file_a.py file_b.py가 있다.

# file_a.py
some_variable = "Hello"

def some_function():
    print(some_variable)  # 이 some_variable은 어디서 오는가?
# file_b.py
from file_a import some_function

some_function()  # 실행하면 "Hello"가 출력됨

단순한 예제이지만, 파이썬의 중요한 동작원리가 숨어있다.

 

실행 순서 이해하기

python file_b.py 명령을 실행하면 다음과 같은 순서로 코드가 실행된다:

  1. file_b.py 실행 시작.
  2. from file_a import some_function 구문을 만남.
  3. 파이썬은 file_a.py를 찾아서 전체를 실행함.
    • some_variable = "Hello" 가 실행됨.
    • some_function() 함수가 정의됨.
  4. some_functionfile_b.py의 namespace로 가져옴.
    • file_a의 namespace에 있는 객체 중 오로지 some_function만 가져온 것.
    • 만약 모든 것을 가져오려면, from file_a import *를 사용해야 함.
  5. file_b.py에서 some_function() 호출.
    • "Hello"가 출력됨.

 

Namespace와 Scope

여기서 핵심적인 개념이 바로 namespacescope이다.

Namespace

정의: 코드가 실행되는 어떤 특정 시점에 사용 가능한 모든 이름과 그에 매핑된 객체들의 집합. 변수나 함수의 정의, import, 삭제 등의 작업이 발생할 때마다 namespace는 계속 변화한다.

# 시점 1: module이 처음 로드될 때
x = 1
# namespace: {'x': 1}

# 시점 2: 함수가 정의된 후
def add_number(y):
    return x + y
# namespace: {'x': 1, 'add_number': <function>}

# 시점 3: 새로운 변수가 추가된 후
z = 2
# namespace: {'x': 1, 'add_number': <function>, 'z': 2}

# 시점 4: 기존 변수가 삭제된 후
del x
# namespace: {'add_number': <function>, 'z': 2}
# 시점 1: 스크립트 시작
# namespace: {}

# 시점 2: math module의 import 후
import math
# namespace: {'math': <module>}

# 시점 3: math에서 특정 함수만 import 후
from math import sin
# namespace: {'math': <module>, 'sin': <function>}
  • 각 파이썬 module은 자신만의 namespace를 가진다.
    • file_a의 namespace는 some_variablesome_function의 집합으로 구성된다.
    • some_variablefile_a의 namespace에 존재하는 객체이다.
  • 한 module에서 다른 module의 객체를 직접 import해야, namespace에 해당 객체가 비로소 추가된다.
    • file_b에서 file_asome_variable을 import하므로, file_b의 namespace에 some_variable이 추가된다.

Scope

정의: "변수의 유효 범위" 혹은 "이름이 유효한 범위"를 의미한다. 즉, namespace의 유효 범위를 의미한다.

Scope는 코드 블록에서 변수가 접근 가능한 범위를 정의한다.

LEGB 규칙(Scope의 4단계)

  1. Local(L): 가장 안쪽의 함수나 클래스 메서드 내부
  2. Enclosing(E): 중첩 함수에서 바깥쪽 함수의 범위
  3. Global(G): module 수준의 전역 범위
  4. Built-in(B): 파이썬 내장 함수와 예약어가 있는 범위

Scope 검색 순서

파이썬에서 변수를 찾을 때는 다음 순서로 검색한다:

 

  1. local scope (함수 내부)
  2. enclosing scope (중첩 함수에서 바깥 함수 범위)
  3. global scope (Module 레벨)
  4. built-in scope (파이썬 내장 함수/변수)

따라서 some_functionsome_variable을 찾는 과정은 다음과 같다:

  1. local scope 검색: 함수 내부에 없음.
  2. enclosing scope 검색: 중첩 함수 구조가 아니므로 미해당.
  3. global scope 검색: file_a의 module 레벨에서 찾아냄.
  4. built-in scope 검색: global scope에서 찾았으므로 여기까지 오지 않음.

 

Module import의 특징

파이썬의 module 시스템에는 다음과 같은 중요한 특징이 있다:

  1. 한 번만 실행:
    • Module은 처음 import될 때만 실행된다.
    • 같은 module을 여러 번 import해도 한 번만 실행된다.
  2. 클로저(Closure):
    • 함수는 자신이 정의된 환경의 변수들을 기억한다.

 

(심화) 함수는 "자신이 정의된 환경"의 어디까지 기억하는가?

 

  • 함수는 자신의 enclosing scope의 변수들 중 자신이 사용하는 변수들만 기억한다.
  • global scope의 변수는 클로저에 저장되지 않고, 필요할 때 module에서 찾는다.
  • built-in scope의 함수/변수들도 저장되지 않고, 필요할 때 찾는다.
# global scope
global_var = "global"

def outer():
    outer_var = "outer"  # enclosing scope
    
    def inner():
        local_var = "local"  # local scope
        print(local_var)    # 1. 당연히 접근 가능
        print(outer_var)    # 2. outer 함수의 변수 기억
        print(global_var)   # 3. global 변수도 접근 가능
        print(len("test"))  # 4. built-in 함수도 사용 가능
    
    return inner  # inner 함수를 반환

# inner 함수를 할당
func = outer()

# 나중에 실행해도 outer_var를 기억
func()  # 모든 출력이 정상적으로 작동

 

 

1. 클로저의 범위

def outer():
    outer_var = "remembered"  # 이 변수는 클로저에 저장됨
    
    def inner():
        print(outer_var)
    
    return inner

func = outer()
print(func.__closure__)  # 클로저 정보 확인 가능

2. 실제로 기억하는 것

def outer():
    outer_var = "outer"
    other_var = "not used"  # inner에서 사용하지 않음
    
    def inner():
        print(outer_var)
    
    return inner

func = outer()
# outer_var만 클로저에 저장되고
# other_var는 저장되지 않음

3. 기억하지 않는 것

global_var = "global"

def simple_func():
    print(global_var)

# global_var는 클로저에 저장되지 않음
# 왜냐하면 module 레벨(global scope)의 변수이기 때문

 

따라서 앞선 예제의 some_functionsome_variable의 값을 기억하는게 아니라, some_variable이 global scope의 변수이므로 자신이 정의된 module인 file_a.py에서 찾아보는 것이다.

# file_a.py
some_variable = "Hello"

def some_function():
    print(some_variable)

# 클로저 확인
print(some_function.__closure__)  # None이 출력됨
# 클로저가 None이라는 것은 저장된 변수가 없다는 의미

 

 

결론

파이썬의 namespace와 scope는 변수와 함수의 가시성과 생명주기를 결정하는 중요한 개념이다. 이들을 제대로 이해하고 활용하면, 변수 이름 충돌을 방지하고, 코드의 module성을 높이는 데 유용하다.

반응형

'Python > 문법' 카테고리의 다른 글

[Python] Module과 Package  (0) 2025.01.18
[Python] import 문 사용 팁  (0) 2025.01.13
[Python] Unpacking의 다양한 예시  (1) 2024.12.27
[Python] Iterable과 Sequence 자료형  (0) 2024.12.27
[Python] 제너레이터(Generator)  (1) 2024.12.14
반응형

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

여러 숫자 카드들의 목록이 주어지고(최대 50만개) 또 숫자들의 목록이 주어졌을 때(역시 최대 50만개), 해당 숫자들을 숫자 카드로 가지고 있는지 체크하는 문제이다.

숫자들이 숫자 카드 목록 중에 있는지 확인하기 위해 tuple 목록에 대해 in 연산자를 사용하는 방식을 사용했더니, 시간 초과가 발생했다.

시간 초과 발생 코드

import sys

lines = sys.stdin.readlines()
cards = tuple(lines[1].split())
questions = lines[3].split()

sys.stdout.write(" ".join(("1" if q in cards else "0" for q in questions)))

in 연산자를 사용하는 것의 문제는, list 및 tuple의  in 연산자에 의한 list 탐색은 선형탐색이라 O(N)의 시간복잡도를 가지므로, 카드 수가 많을 경우 비효율적이게 된다.

 

set 자료형을 활용하면 검색 속도를 O(1)로 줄일 수 있다는 것을 활용해보자. 이는 set은 해시 테이블 기반으로 구현되어, 검색(in 연산)의 시간복잡도가 평균 O(1)이기 때문이다(해시 충돌이 많이 발생하면 O(N)이 될 수 있지만, 해시 함수 설계가 잘 되어 있다면 드문 경우이다).

정답 코드

import sys

lines = sys.stdin.readlines()
cards = set(lines[1].split())
questions = lines[3].split()

sys.stdout.write(" ".join(("1" if q in cards else "0" for q in questions)))

결과적으로, 위 시간 초과가 발생한 코드에서 달라진 점은 tuple이 아닌 set으로 숫자 카드를 저장한다는 것이다.

이렇게 자료구조만 바뀌었는데도, 같은 in 연산을 사용할 때 성능 차이가 발생한다는 것을 확인할 수 있는 좋은 문제라고 생각한다.

 

Set의 시간 복잡도 정리

연산 평균 시간 복잡도 최악 시간 복잡도
삽입 (add) O(1) O(N)
삭제 (remove, discard) O(1) O(N)
조회 (in) O(1) O(N)
순회 O(N) O(N)
반응형
반응형

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

최대 1,000,000개 숫자의 1차원 array가 주어졌을 때, 각 숫자에 대해 array 중 해당 숫자보다 작은 숫자들에 갯수를 출력하는 문제이다.

 

각 숫자가 unique하게 발생되는 목록을 sort한 것에서(unique_ordered), 숫자의 index를 찾으면 그 값이 곧 그 숫자보다 작은 숫자들의 갯수가 될 것이라고 생각해 풀이해봤으나 시간초과가 발생했다. 

시간 초과 발생 코드

import sys

x = tuple(map(int, sys.stdin.readlines()[1].rstrip().split()))
unique_ordered = sorted(set(x))
sys.stdout.write(" ".join(str(unique_ordered.index(i)) for i in x))

unique_ordered.index(i)는 list에서 특정 값의 index를 찾기 위해 list의 길이만큼 순차 검색을 수행한다. 이는 O(N) 시간이 걸리며, 이를 x의 모든 요소에 대해 반복하기 때문에 전체 복잡도가 O(N^2)가 된다. 1,000,000 ^ 2는 1조에 해당되므로 시간초과가 발생할 수밖에 없게 된다.

 

x의 각 숫자에 대해  unique_ordered를 매번 순회해서 index값을 찾는 대신에, unique_ordered를 단 1회 순회하면서 unique_ordered의 각 값과 index를 매핑하는 dict(아래의 index_map)를 만드는 방법이 있다.

정답 코드

import sys

x = tuple(map(int, sys.stdin.readlines()[1].rstrip().split()))
unique_ordered = sorted(set(x))
index_map = {v: i for i, v in enumerate(unique_ordered)}
sys.stdout.write(" ".join(str(index_map[i]) for i in x))

이렇게 코드를 수정하면, x의 모든 요소에 대해 index값 탐색이 dict 해싱 덕분에 O(1)로 해결된다. 즉 전체 x의 index 값 탐색은 O(N)에 해당된다.

 

정답 코드의 시간 복잡도

  • set과 sorted (unique_ordered 만들기) → O(N log N)
  • dict 생성 및 결과 조회 (index_map 만들기 및 전체 index값 조회 → O(N)

=> 전체 복잡도: O(N log N) 이 산출된다.

반응형
반응형

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

 

N개의 수가 주어졌을 때, 오름차순으로 정렬하는 프로그램을 작성해야 한다.

(조건: 1 ≤ N ≤ 10,000,000 이며,  각 숫자는 10,000보다 작거나 같은 자연수)

메모리 제한이 단 8MB에 불과해, 메모리 최적화가 중요한 문제이다.

 

단순히 list를 정렬하는 방식으로는 해결할 수 없다.

숫자의 갯수가 1천만개까지 가능하기 때문에, 이를 모두 담기 위한 list의 메모리 크기를 대략 계산해보자.

  • 10,000 이하의 자연수가 각각 int로 저장되는데, 파이썬의 int 형은 64bit 시스템에서 28 byte 정도가 소요된다고 한다.
  • List 자체는 각 자연수에 대한 포인터를 저장하는데, 그리고 각 포인터는 8 byte를 차지한다.

결론적으로, 단순히 모든 숫자를 list에 개별 저장하려면, 대략 (8 byte + 28 byte) * 10,000,000 = 360MB의 메모리가 필요하므로, 문제의 제한 사항을 아득히 뛰어넘게 된다.

 

대신 Counting Sort(계수 정렬)을 사용해야 한다. 

Counting Sort는 주어진 데이터에서 각 값의 빈도를 세어 해당 값들이 몇 번 나타나는지 기록한 후, 빈도 정보에 따라 데이터를 정렬하는 알고리즘이다. 이 알고리즘은 데이터의 값의 범위가 적고, 빈도가 많은 경우에 매우 효율적이다. 시간 복잡도는 O(N + K)로, N은 데이터의 개수, K는 데이터의 값 범위이다. 다만, 값의 범위가 너무 넓으면 메모리 사용량이 급격히 증가할 수 있다

 

Couting Sort를 사용해 간단히 구현해봤다. 작은 숫자부터 시작해서, 각 숫자의 발생 갯수만큼 출력하면된다.

그러나 아래처럼 메모리 초과가 발생하게 되었다.

메모리 초과 발생 코드

import sys

n = int(next(sys.stdin))
l = [0] * 10_001
for i in sys.stdin:
    l[int(i)] += 1

for i in range(1, 10_001):
    sys.stdout.write(f"{i}\n" * l[i]) # 메모리 초과 원인

메모리 초과가 발생한 이유는, 최종 결과 출력시 문자열 곱셈을 너무 naive하게 수행했기 때문이다. l[i]의 값이 최대 10,000,000이 될 수 있기 때문에, 수천만 byte짜리 string이 생길 수도 있는 코드 작성이다.

 

위 오류를 수정해서 통과하는 코드를 작성했다.

정답 코드 -  메모리 사용 

import sys

n = int(next(sys.stdin))
l = [0] * 10_001
for i in sys.stdin:
    l[int(i)] += 1

for i in range(1, 10_001):
    for _ in range(l[i]):
        sys.stdout.write(f"{i}\n")

개별 숫자에 대해 sys.stdout.write()를 일일이 호출하는 방식이다. 이 방식은 여러 문자를 합친 문자열을 출력하지 않으므로, 메모리 공간은 그만큼 사용하지 않을 수는 있으나, sys.stdout.write() 의 호출이 그만큼 증가하므로 성능에 페널티가 있을 수 있다.

 

이어서, 개별 숫자에 대해 일일이 sys.stdout.write() 를 호출하지 않고, 그룹화시킨 것에 대해 호출하는 최적화를 시도했다.

성능 개선된 정답 코드 -  write 함수 호출 횟수 줄이기(성능과 메모리 사용 간의 최적화)

import sys

n = int(next(sys.stdin))
l = [0] * 10_001
for i in sys.stdin:
    l[int(i)] += 1

BUFFER_SIZE = 1_000
for i in range(1, 10_001):
    for _ in range(l[i] // BUFFER_SIZE):
        sys.stdout.write(f"{i}\n" * BUFFER_SIZE)
    for _ in range(l[i] % BUFFER_SIZE):
        sys.stdout.write(f"{i}\n")

개별 숫자를 1,000개씩 buffer에 모아서 출력하도록 변경했다.

개별 숫자의 문자열("1"~"10000")의 크기는 5 byte이내이므로, 각 buffer의 메모리 크기는 5,000 byte에 해당된다.

이러면 8 MB의 메모리 제약 조건을 안전하게 지키면서, sys.stdout.write()의 호출 횟수를 크게 줄일 수 있다.

예를 들어, 숫자 1이 1,000번 등장했다고 가정하자.

이전 코드에서는 1에 대해  sys.stdout.write("1\n") 를 1,000번 호출했어야 했으나, 개선된 코드에서는 sys.stdout.write("1\n" * 1000) 를 한번만 호출하면 된다.

 

 

추가 개선 방법?

  • input, output 함수를 다른 것을 사용해서 입출력에서 성능 개선을 볼 수 있을 것 같다.
  • 그렇지만 현재 작성된 코드가 간결하기 때문에 불필요할 수도 있다.
반응형
반응형

CORS란 무엇인가?

CORS(Cross-Origin Resource Sharing)는 "교차 출처 리소스 공유"라는 의미로, 브라우저에서 다른 도메인, 포트 또는 프로토콜로부터 리소스를 요청할 때, 해당 출처(도메인, 프로토콜, 포트)의 리소스를 공유할 수 있게 해주는 보안 메커니즘이다.

쉽게 말해, 브라우저가 A 웹사이트의 웹페이지를 동작시키고 있을 때, 해당 웹페이지가 외부의 B 웹사이트의 데이터를 가져오려고 할 때 필요한 보안 정책이다.

공식 문서 설명

사례로 이해하기

내가 mywebsite.com이라는 도메인에서 운영하는 블로그를 가지고 있다고 가정해보자. 블로그에 날씨 위젯을 추가하기 위해 weatherapi.com의 API를 사용하려고 한다.

// mywebsite.com의 프론트엔드 코드
fetch('https://api.weatherapi.com/v1/current.json')
  .then(response => response.json())
  .then(data => console.log(data));

이때 브라우저 콘솔에 아래의 에러가 발생할 수 있다. 이것이 바로 CORS 에러이다.

Access to fetch at 'https://api.weatherapi.com/v1/current.json' from origin 'https://mywebsite.com' has been blocked by CORS policy

 

CORS가 필요한 이유

CORS는 웹 보안을 위해 매우 중요하다.악의적인 웹사이트가 사용자의 민감한 정보에 마음대로 접근하는 것을 방지하기 때문이다.

📌 CORS가 없다면?

  • 악성 웹사이트가 사용자의 은행 웹사이트 데이터를 무단으로 가져갈 수 있다.
  • 다른 사이트의 API를 무제한으로 호출하여 서버에 부하를 줄 수 있다.
  • XSS(Cross-Site Scripting) 공격이 더 쉬워질 수 있다.

 

CORS는 어떻게 동작하는가?

Preflight 요청

브라우저는 실제 요청을 보내기 전에 'preflight' 요청이라는 사전 검증을 먼저 수행한다. 이는 OPTIONS 메소드를 사용하는 HTTP 요청이다.

예를 들어, 나의 웹사이트에서 다음과 같은 API 요청을 한다고 가정해보자:

fetch('https://api.weatherapi.com/v1/current.json', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ city: 'Seoul' })
});

이때 브라우저와 서버는 다음과 같은 순서로 요청을 처리한다:

1. 브라우저가 Preflight 요청 전송

OPTIONS /v1/current.json HTTP/1.1
Host: api.weatherapi.com
Origin: https://mywebsite.com
Access-Control-Request-Method: POST      // 실제로 보내고자 하는 HTTP 메서드
Access-Control-Request-Headers: Content-Type  // 실제로 사용할 헤더들

여기서 각 헤더의 의미는 다음과 같다:

  • Origin: 요청을 보내는 출처(도메인, 프로토콜, 포트).
  • Access-Control-Request-Method: 이후에 실제로 보내고자 하는 HTTP 메서드를 서버에 알려준다.
  • Access-Control-Request-Headers: 이후에 실제로 사용할 헤더들을 서버에 알려준다.

2. 서버의 Preflight 응답

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://mywebsite.com     // 허용된 출처
Access-Control-Allow-Methods: GET, POST, PUT, DELETE   // 허용된 HTTP 메서드들
Access-Control-Allow-Headers: Content-Type             // 허용된 헤더들
Access-Control-Max-Age: 86400                         // Preflight 응답 캐시 시간(초)

서버는 각각의 요청 헤더에 대응하는 허용 범위를 응답 헤더로 알려준다:

  • Access-Control-Allow-Origin: 이 리소스에 접근이 허용된 출처
  • Access-Control-Allow-Methods: 허용된 HTTP 메서드들의 목록
  • Access-Control-Allow-Headers: 허용된 헤더들의 목록
  • Access-Control-Max-Age: Preflight 요청의 결과를 캐시할 시간(초)

3. 브라우저가 실제 요청 전송

  • Preflight 응답이 성공적이면, 그제서야 실제 POST 요청을 보낸다.
  • 만약 Preflight 응답이 실패하면, 브라우저는 실제 요청을 보내지 않고 CORS 에러를 발생시킨다.

 

언제 Preflight가 필요한가?

브라우저는 CORS 요청을 '단순 요청'과 'Preflight가 필요한 요청' 두 가지로 구분한다. 하지만 중요한 점은, 모든 요청에 CORS 정책이 적용된다는 것이다.

1. Simple Request(단순 요청)

다음 조건을 모두 만족하는 경우에는 Preflight 요청이 생략된다(자세한 건 공식문서의 여기 참고):

  1. HTTP 메소드가 다음 중 하나:
    • GET
    • HEAD
    • POST
  2. 헤더가 다음만 포함:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (아래 값만 허용)
      • application/x-www-form-urlencoded
      • multipart/form-data
      • text/plain
  3. 요청에 ReadableStream 객체가 사용되지 않음
  4. XMLHttpRequest.upload에 이벤트 리스너가 등록되지 않음

주의: 단순 요청이라도 서버가 적절한 CORS 헤더(Access-Control-Allow-Origin)를 응답하지 않으면 브라우저는 여전히 CORS 에러를 발생시킨다!

2. Preflight가 필요한 요청

좀 더 복잡한 요청의 경우, 브라우저는 본 요청 전에 Preflight 요청을 먼저 보낸다. 다음 중 하나라도 해당되면 Preflight가 필요하다:

1. 특별한 메서드 사용 (PUT, DELETE 등)

// PUT 메서드 사용 - Preflight 필요
fetch('https://api.example.com/data', {
  method: 'PUT',
  body: JSON.stringify({ name: 'John' })
});

2. application/json 사용 (웹 API에서 가장 일반적)

// Content-Type: application/json 사용 - Preflight 필요
fetch('https://api.example.com/data', {
  method: 'POST',  // POST라도 JSON을 사용하면 Preflight 필요
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ name: 'John' })
});

3. 커스텀 헤더 추가

// 커스텀 헤더 'Authorization' 사용 - Preflight 필요
fetch('https://api.example.com/data', {
  method: 'GET',  // GET이라도 커스텀 헤더가 있으면 Preflight 필요
  headers: {
    'Authorization': 'Bearer token123'
  }
});

현대 웹 개발에서는 대부분의 API 요청이 다음 특징을 가진다:

  • JSON 데이터 사용
  • Authorization 헤더를 통한 인증
  • RESTful API (PUT, DELETE 등 사용)

따라서 실제로는 대부분의 API 요청이 Preflight를 필요로 하며, 서버는 반드시 적절한 CORS 설정을 해야 한다. '단순 요청'은 오히려 드문 케이스라고 볼 수 있다.

 

CORS 해결 방법

1. 서버 측 설정

가장 일반적인 해결 방법은 서버에서 적절한 CORS 헤더를 설정하는 것이다.

FastAPI 예제:

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://mywebsite.com"],
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["*"],
    allow_credentials=True,
)

 

2. 프록시 서버 사용

자체 프록시 서버를 통해 API 요청을 우회하는 방법도 있다.

// 프론트엔드에서의 요청
fetch('/api/weather')  // 자체 서버로 요청
  .then(response => response.json())
  .then(data => console.log(data));
# FastAPI를 사용한 백엔드 프록시 서버
from fastapi import FastAPI
import httpx

app = FastAPI()

@app.get("/api/weather")
async def get_weather():
    async with httpx.AsyncClient() as client:
        response = await client.get("https://api.weatherapi.com/v1/current.json")
        return response.json()

이 방식은 프론트엔드에서 직접 외부 API를 호출하지 않고, 자체 백엔드 서버를 통해 우회하는 방법이다.

이 방식이 작동하는 이유는 서버 간의 통신에는 CORS 정책이 적용되지 않기 때문이다. 브라우저에서 직접 외부 API를 호출할 때는 CORS 제한이 있지만, 백엔드 서버에서 외부 API를 호출할 때는 이러한 제한이 없다.

CORS 설정이 불가능한 외부 API를 사용해야 할 때 유용한 해결책이 될 수 있다.

 

자주 발생하는 CORS 에러와 해결 방법

1. "No 'Access-Control-Allow-Origin' header is present"

이 에러는 서버에서 CORS 헤더를 설정하지 않았거나 Preflight 요청에 대한 응답이 올바르지 않은 경우 발생한다. 서버에서 OPTIONS 요청에 대한 처리와 함께 적절한 CORS 헤더를 추가해야 한다.

2. "Method not allowed"

허용되지 않은 HTTP 메서드를 사용할 때 발생한다. 서버의 CORS 설정에서 Access-Control-Allow-Methods 헤더에 해당 메서드를 추가해야 한다. 예를 들어 PUT 요청이 차단된다면, 서버에서 PUT 메서드를 명시적으로 허용해야 한다.

3. "Request header field Content-Type is not allowed"

허용되지 않은 헤더를 사용할 때 발생하는 에러이다. 특히 application/json과 같은 Content-Type을 사용할 때 자주 발생한다. 서버의 CORS 설정에서 Access-Control-Allow-Headers에 필요한 헤더를 추가해야 한다.

 

 

핵심 포인트 정리

  1. 모든 CORS 요청은 기본적으로 브라우저의 보안 정책을 따른다.
  2. 서버 측에서 적절한 헤더 설정으로 대부분의 문제를 해결할 수 있다.
  3. 개발 환경과 프로덕션 환경의 CORS 설정을 구분하여 관리하는 것이 좋다.
반응형

'Web 일반' 카테고리의 다른 글

[Web 일반] URL Path Parameter vs. Query Parameter  (0) 2024.12.30
반응형

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

 

요약하자면, 2차원 행렬의 공간의 모든 값을 더하는 문제이다.

 

정답 코드

import sys

l = [[0 for _ in range(100)] for _ in range(100)]

next(sys.stdin)
for line in sys.stdin:
    x, y = map(int, line.split())
    for j in range(x, x + 10):
        for i in range(y, y + 10):
            l[i][j] = 1

sys.stdout.write(f"{(sum(sum(l, [])))}")

 

2차원 행렬에 값을 입력받는 것은 수월하게 할 수 있다.

2차원 행렬을 row, column 양방향으로 iterate를 돌면서 합하면 간단하지만, 대신에 2차원 list를 1차원 리스트로 변환해서 한번에 더하는 코드를 사용했다.

sum(sum(2차원 list, []))

sum의 두번째 인자에 []를 주면, 2차원 list의 각 item인 1차원 list를 이 []에 하나씩 뒤에 더하게 된다.

반응형
반응형

Path Parameter(경로 매개변수)Query Parameter(쿼리 매개변수)는 web API나 URL에서 데이터를 전달하기 위한 방법으로, 각각의 용도와 목적이 다르다.

둘다 주로 GET 요청에서 많이 사용된다. GET 요청의 주요 목적이 서버에서 데이터를 읽어오고 리소스를 검색하는 것이기 때문인데, GET 요청은 요청 데이터를 URL에 포함하여 전달하므로 Path Parameter와 Query Parameter를 사용하는 것이 자연스럽다. 주의할 점은, GET 요청은 URL에 모든 데이터가 노출되기 때문에 민감한 데이터를 전달하는 데 적합하지 않다(이런 경우 POST 요청이나 본문(body)을 사용하는 것이 권장된다).

 

1. Path Parameter(경로 매개변수)

  • 위치: URL의 특정 경로 부분에 포함된다.
    예: https://example.com/users/{user_id}/orders/{order_id}
  • 용도:
    • Resource 식별: 특정 resource를 명확히 식별하기 위해 사용된다.
    • API 구조를 계층적으로 만들고 의미를 전달하는 데 적합하다.
  • 특징:
    • 필수적으로 사용되는 경우가 많다.
    • 일반적으로 고정된 값으로 나타나며 RESTful 설계에서 흔히 사용된다.
  • 예제:
    • 12345products라는 resource의 특정 제품 ID를 나타낸다.

 

2. Query Parameter(쿼리 매개변수)

  • 위치: URL의 끝에 ? 뒤에 key-value 쌍 형태로 나타난다. 여러 query parameter는 &로 구분된다.
    예: https://example.com/search?query=shoes&sort=price_asc
  • 용도:
    • Resource를 filtering하거나 sorting, paging, 또는 검색 조건을 전달할 때 사용한다.
    • 선택적 데이터를 추가하기에 적합하다.
  • 특징:
    • 선택적으로 사용할 수 있다.
    • 가변적인 데이터를 표현하기에 적합하다.
  • 예제:
    • category=electronics는 전자제품 카테고리를 필터링하고, limit=10은 한 페이지당 10개, page=2는 두 번째 페이지를 요청한다.

 

차이점 요약

항목 Path Parameter (경로 매개변수) Query Parameter (쿼리 매개변수)
위치 URL 경로의 일부 URL 끝의 ? 뒤
용도 Resource 식별 Filtering, Sorting, 검색 조건 전달
필수 여부 일반적으로 필수 선택적
가독성 명확하고 계층적 구조 표현에 적합 복잡한 조건 표현에 유용

 

결합 사용 예제

Path Paramter와 Query Paramter를 함께 사용할 수도 있다.

  • Path Paramter : 12345는 특정 사용자를 식별.
  • Query Paramter : status=pending은 대기 중인 주문만, limit=5는 결과를 5개로 제한.
반응형

'Web 일반' 카테고리의 다른 글

[Web 일반] CORS 에러 발생과 해결 방법  (0) 2025.01.02

+ Recent posts