reverse와 reverse_lazy 차이

  1. reverse
  • reverse 메소드는 주어진 뷰에 대한 URL을 생성하는데 사용됩니다. 이 메서드는 URL 패턴의 이름과 뷰에 전달해야하는 인수를 받고 URL을 나타내는 문자열을 반환합니다.
  • 이 함수는 호출되는 즉시 평가(evaluated)됩니다. 즉, URL을 즉시 생성합니다.
  • reverse는 즉시 평가되기 때문에 아직 로드되지 않을 수 있는 URL 구성(configurations)(예: Django의 URL 구성에 의존하는 클래스 기반 뷰의 속성을 정의할 때)에는 사용해서는 안됩니다.
  1. reverse_lazy
  • 반면에 reverse_lazy는 reverse와 동일한 작업을 수행하지만 더 느립니다. 즉, 명시적으로 액세스(explicitly accessed)할 때까지 URL을 생성하지 않습니다.
  • 이는 success_url과 같은 클래스 기반 뷰와 같이 URLConf가 완전히 로드되지 않은 상태에서 URL을 사용해야할 때 유용합니다.

reverse_lazy는 일반적으로 클래스 기반 뷰에서 success_url을 정의할때 사용됩니다. success_url 속성은 클래스 수준(level)에서 값이 필요하지만, 이 시점에서 모든 URL 패턴이 로드되지 않을 수 있습니다. reverse_lazy를 사용하면 모든 패턴이 로드되고 실제로 URL이 필요할 때까지 URL을 연기합니다.

반면에 reverse는 LoginView의 post 메서드나 log_out 함수와 같이 메서드(함수) 내부에서 사용됩니다. 이러한 경우 메서드가 호출될때쯤이면 모든 URL 구성(configurations)이 로드될 것이므로 리버스 방식을 사용하는 것이 안전하다고 합니다.

쉬운 설명

이걸 쉬운 예시로 들면, reverse 메서드는 마법의 지도에게 지금 당장 보물로 가는 길을 알려달라고 하는 것과 같습니다. 그래서 그 마법지도는 즉시 어디로 가야하는지 알려줄 것입니다.

하지만 마법 지도가 아직 길을 그리고 있느라 준비되지 않는 경우도 있습니다. 이 경우 너무 일찍 길을 물어보면 마법 지도가 올바른 답을 줄 수 없을 것입니다. 이때 사용하는게 reverse_lazy입니다.

reverse_lazy 메서드는 마법 지도에게 "마법지도가 완성되면 보물로 가는 길을 알려줘!"라고 말하는 것과 같습니다. 그래서 마법 지도는 모든 경로를 그리고, 준비가 되면 보물이 있는 위치를 알려줍니다.

결국 마법 지도가 준비되었다는 것을 알면 reverse를 사용하고, 마법 지도가 아직 그리고 있을때는 reverse_lazy를 사용합니다.

예시 코드

class LoginView(View):
    """
    Handles user login.
    """
    template_name = "users/login.html"

    def get(self, request):
        form = LoginForm()
        return render(request, self.template_name, {"form": form})

    def post(self, request):
        form = LoginForm(request.POST)

        if form.is_valid():
            email = form.cleaned_data.get("email")
            password = form.cleaned_data.get("password")
            user = authenticate(request, email=email, password=password)

            if user:
                login(request, user)
                return redirect(reverse("users:logged_in_index"))

        return render(request, self.template_name, {"form": form})


@login_required
def log_out(request):
    """
    Logs out the user and redirects them to the index page.
    """
    logout(request)
    return redirect(reverse("users:index"))

요구 정책

  • 8자리 이상
  • 영문, 숫자, 특수문자 포함

구현 방식

  • DRF가 아닌 Pure Django에서 구현했습니다.
  • validators.py에 원하는 검사를 구현하고, forms.py에 validator를 추가했습니다.

구현

settings.py

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
    {
        'NAME': 'users.validators.CustomPasswordValidator', # 추가
    },
]

VALIDATORS를 추가하였습다. 구현한 validators.py에서도 기존의 validator도 사용하기 때문에 기존 validator 및에 추가하였습니다.

validators.py

from django.core.exceptions import ValidationError
from django.contrib.auth.password_validation import validate_password
import re


class CustomPasswordValidator:
    def __call__(self, value): # 1)
        try:
            validate_password(value)
        except ValidationError as e:
            raise ValidationError(str(e))

    def validate(self, password, user=None):
        """비밀번호 유효성 검사"""

        if len(password) < 8:
            raise ValidationError("비밀번호는 8자리 이상이어야 합니다.")
        if not re.search(r"[a-zA-Z]", password):
            raise ValidationError("비밀번호는 하나 이상의 영문이 포함되어야 합니다.")
        if not re.search(r"\d", password):
            raise ValidationError("비밀번호는 하나 이상의 숫자가 포함되어야 합니다.")
        if not re.search(r"[!@#$%^&*()]", password):
            raise ValidationError(
                "비밀번호는 적어도 하나 이상의 특수문자(!@#$%^&*())가 포함되어야 합니다."
            )

    def get_help_text(self): # 2)
        return "비밀번호는 8자리 이상이며 영문, 숫자, 특수문자((!@#$%^&*()))를 포함해야 합니다."

이렇게 구현했습니다.
위에처럼 정규 표현식이 아닌 아래처럼 일반 파이썬 문으로도 구현이 가능합니다.

        if not any(char.isalpha() for char in value):
            raise forms.ValidationError("비밀번호는 하나 이상의 영문이 포함되어야 합니다.")
        if not any(char.isalpha() for char in value):
            raise forms.ValidationError("비밀번호는 하나 이상의 숫자가 포함되어야 합니다.")
        if not any(char in "!@#$%^&*()" for char in value):
            raise forms.ValidationError("비밀번호는 적어도 하나 이상의 특수문자((!@#$%^&*()))가 포함되어야 합니다.")

1)

이 클래스 CustomPasswordValidator는 callable 객체를 정의합니다. 클래스의 인스턴스를 함수로 호출할 수 있는 __call__ 메서드를 구현합니다. 이 경우 'CustomPasswordValidator' 인스턴스는 암호 값을 인수로 사용하여 호출할 수 있습니다.

__call__ 메서드 내에서 validate_password 함수가 호출되어 django가 기본으로 제공하는 암호 유효성 검사를 실행합니다. 유효성 검사 오류가 발생하면 오류 메시지와 함께 ValidationError가 발생합니다.

다시 말하면, __call__이 메소드는 SignUpForm 클래스의 validators=[CustomPasswordValidator()] 행과 같이 클래스의 인스턴스가 함수로 호출될 때 호출됩니다. CustomPasswordValidator()가 호출되면 __call__ 메서드가 트리거되고 validate_password() 함수를 호출합니다.

또한 CustomPasswordValidator 클래스는 validate 메서드도 정의합니다. 이 메서드는 인스턴스가 함수로 호출될 때(__call__ 메서드와 같이) 자동으로 실행되지 않지만 validate_password()가 수행하는 것 이상의 추가 암호 유효성 검사 규칙을 제공합니다. 이러한 추가 규칙은 비밀번호의 길이와 영문, 숫자 및 특수 문자를 포함하는지 여부를 확인합니다.

따라서 SignUpForm 클래스에서 CustomPasswordValidator()를 유효성 검사기로 사용하면 validate_password() 함수와 validate() 메서드가 모두 실행되어 비밀번호를 검증합니다.

2)

get_help_text 메서드는 암호 요구 사항에 대한 정보를 제공하는 도움말 텍스트를 반환합니다. 이는 예상되는 암호 형식에 대한 추가 정보를 사용자에게 표시하는 데 유용할 수 있습니다.

forms.py

from django import forms
from .validators import CustomPasswordValidator


class SignUpForm(forms.Form):
    ... 생략 ...
    password = forms.CharField(
        widget=forms.PasswordInput,
        validators=[CustomPasswordValidator()],
    )
    ... 생략 ...

이렇게 import 해서 사용합니다.

glob.glob() 함수는 파라미터로 받은 패턴과 이름이 일치하는 모든 파일과 디렉터리를 리스트로 반환합니다.

import glob

glob.glob(pattern)

파라미터인 pattern에는 아래와 같은 특수문자가 포함될 수 있습니다.

  • * : 0개 이상의 임의 개수의 문자와 일치합니다. 그래서 glob.glob(*) 이 경우는 모든 파일과 디렉터리를 볼 수 있습니다.
  • ? : 정확히 하나의 문자와 일치합니다.
  • [] : 괄호 안의 단일 문자와 일치합니다.
  • [!] 또는 [^] : 대괄호 안에 없는 단일 문자와 일치합니다.
  • {} : 중괄호 안의 쉼표로 구분된 패턴과 일치합니다.
  • . : 리터럴 점(.)과 일치합니다.

예시

import glob

# 1) 현재 디렉터리에 있는 모든 파일과 디렉터리
files = glob.glob("*")
print(files)

# 2) 모든 python 파일들
python_files = glob.glob("*.py")
print(python_files)

# 3) 지정된 디렉터리의 모든 파일과 디렉터리
directory_files = glob.glob("/path/to/directory/*")
print(directory_files)

# 4) file0.txt, file1.txt ... file9.txt와 같은 이름을 가진 파일
pattern_files = glob.glob("file[0-9].txt")
print(pattern_files)s

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

@classmethod 와 @staticmethod  (0) 2023.07.20
@property 데코레이터  (0) 2023.07.01
Python 라이브러리 - functools  (0) 2023.05.24
Python 라이브러리 - itertools  (0) 2023.05.20
Python 라이브러리 - operator.itemgetter  (0) 2023.05.18

functools

공식 문서 링크

source code 링크

functools.cmp_to_key

functools.cmp_to_key(func)는 sorted()와 같은 정렬 함수의 key 매개변수에 함수(func)를 전달할 때 사용하는 함수입니다. 단, func() 함수는 두 개의 인수를 입력해 첫 번째 인수를 기준으로 그 둘을 비교하고 작으면 음수, 같으면 0, 크면 양수를 반환하는 비교 함수이어야 합니다.

예시

2차원 평면 위의 점 N개를 (x, y) 좌표로 구성한 리스트가 있고 이 리스트를 y 좌표가 증가하는 순으로 정렬하되 y 좌표가 같으면 x좌표가 증가하는 순으로 정렬하를 프로그램입니다.

import functools

def xy_compare(n1: int, n2: int) -> int:
    if n1[1] > n2[1]:           # y좌표가 크면
        return 1
    elif n1[1] == n2[1]:        # y좌표가 같으면
        if n1[0] > n2[0]:       # x좌표가 크면
            return 1
        elif n1[0] == n2[0]:    # x좌표가 같으면
            return 0
        else:                   # x좌표가 작으면
            return -1
    else:
        return 0                # x좌표가 작으면


src = [(0,4), (1, 2), (2, 1), (3, 4), (-1, 2)]
result = sorted(src, key=functools.cmp_to_key(xy_compare))
print(result)   # [(0, 4), (1, 2), (2, 1), (3, 4), (-1, 2)]

xy_compare()와 같이 정렬에 사용하는 함수는 반드시 아래 3가지 중 하나를 반환해야 합니다.

  • 양수
  • 음수
  • 0

functools.partial

functools.partial()은 하나 이상의 인수가 이미 채워진 새 버전의 함수를 만들 때 사용하는 함수입니다.

def add_mul(choice: str, *args: float) -> float:
    if choice == "add":
        result = 0
        for arg in args:
            result = result + arg
    elif choice == "mul":
        result = 1
        for arg in args:
            result = result * arg 
    return result

위의 함수는 입력한 인수의 합과 곱을 choice값에 따라 선택적으로 반환하는 사용자 정의 함수 add_mul()입니다.

만약 add_mul()함수를 활용해 add(), mul() 함수를 만들러면 아래처럼 작성할 수 있습니다.

def add(*args: float) -> float:
    return add_mul("add", *args)

def mul(*args):
    return add_mul("mul", *args)

print(add(1, 2, 3, 4))  # 10
print(mul(1, 2, 3, 4))  # 24

하지만, functools.partial()을 사용하면 아래처럼 더 간결하게 작성할 수 있습니다.

from functools import partial


add = partial(add_mul, "add")
mul = partial(add_mul, "mul")

print(add(1, 2, 3, 4))  # 10
print(mul(1, 2, 3, 4))  # 24
print(type(add))    # <class 'functools.partial'>

이처럼 partial은 하나 이상의 인수를 미리 채운 새로운 함수를 만드는데 유용합니다.

functools.reduce

functools.reduce(function, iterable)은 function을 반복 가능한 객체의 요소에 차례대로(왼쪽에서 오른쪽으로) 누적 적용하여 이 객체를 하나의 값으로 줄이는 함수입니다.

아래는 data의 요소를 모두 더해 반환하는 add()함수입니다.

from typing import Iterable

def add(data: Iterable[int]) -> int:
    result = 0
    for i in data:
        result += i
    return result 

data = [1, 2, 3, 4]
result = add(data)
print(result)   # 10

functools.reduce() 함수를 사용하면 아래처럼 코드를 수정할 수 있습니다.

import functools

data = [1, 2, 3, 4]
result = functools.reduce(lambda x, y: x + y, data)
print(result)   # 10

이렇게 reduce() 함수를 사용하면 reduce()에 선언한 람다 함수를 data 요소에 차례대로 누적 적용하여 다음과 같이 계산합니다.

(((1+2)+3)+4)

참고로 람다 관련 글은 아래 링크를 확인해 보면 됩니다.
람다 관련 글

추가로 functools.reduce()를 활용해 최댓값도 구할 수 있습니다.

num_list = [3, 3, 9, 1, 4]
max_num = functools.reduce(lambda x, y: x if x > y else y, num_list)
print(max_num)  # 9

 

 

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

@property 데코레이터  (0) 2023.07.01
glob.glob() 함수  (0) 2023.06.11
Python 라이브러리 - itertools  (0) 2023.05.20
Python 라이브러리 - operator.itemgetter  (0) 2023.05.18
Python 라이브러리 - statistics  (0) 2023.05.17

공식 문서 링크

source code 링크

itertools.cycle

itertools.cycle은 반복 가능한 객체(iterable)를 순서대로 무한히 반복하는 이터레이터를 생성하는 함수입니다.

예시

import itertools 

emp_pool = itertools.cycle(['kim', 'lee', 'park'])

print(next(emp_pool))       # kim
print(next(emp_pool))       # lee
print(next(emp_pool))       # park
print(next(emp_pool))       # kim

참고로 next()는 python 내장 함수로, 이터레이터의 다음 요소를 반환하는 함수입니다.

itertools.accumulate

itertools.accumulate는 반복 가능한 객체(iterable)의 누적합을 계산하여 이터레이터로 반환하는 함수입니다.

예시 - 1

1월부터 12월까지의 월별 누적 합계를 구하는 프로그램입니다.

monthly_income = [1161, 1234, 3421, 1923, 234, 2311, 1000, 1800, 1982, 2023, 2122, 2200]
result = list(itertools.accumulate(monthly_income))

print(result)   # [1161, 2395, 5816, 7739, 7973, 10284, 11284, 13084, 15066, 17089, 19211, 21411]

예시 - 2

1월부터 12월 동안 그때까지의 최대 월 수입을 표시하는 프로그램입니다.

result_2 = list(itertools.accumulate(monthly_income, max))
print(result_2) # [1161, 1234, 3421, 3421, 3421, 3421, 3421, 3421, 3421, 3421, 3421, 3421]

itertools.accumulate() 함수의 두 번째 인수로 max를 전달하면 됩니다.

itertools.groupby

itertools.groupby(iterable, key=None)은 반복 가능한 객체를 키값으로 분류하고 그 결과를 반환하는 함수입니다.

예시

itertools.groupby()함수를 사용해 혈액형별로 묶어 데이터를 분류하는 예시입니다.

import operator
import pprint
import itertools

data = [
    {'name': 'kim', 'blood': 'O'},
    {'name': 'lee', 'blood': 'B'},
    {'name': 'park', 'blood': 'B'},
    {'name': 'choi', 'blood': 'AB'},
    {'name': 'cho', 'blood': 'A'},
    {'name': 'jung', 'blood': 'B'},
    {'name': 'ki', 'blood': 'O'},
]

data = sorted(data, key=operator.itemgetter('blood'))  # groupby-1)
pprint.pprint(data)     # groupby-2)
# [{'blood': 'A', 'name': 'cho'},
#  {'blood': 'AB', 'name': 'choi'},
#  {'blood': 'B', 'name': 'lee'},
#  {'blood': 'B', 'name': 'park'},
#  {'blood': 'B', 'name': 'jung'},
#  {'blood': 'O', 'name': 'kim'},
#  {'blood': 'O', 'name': 'ki'}]

grouped_data = itertools.groupby(data, key=operator.itemgetter('blood'))    # groupby-3)
result = {}
for key, grouped_data in grouped_data:  # groupby-4)
    result[key] = list(grouped_data)

pprint.pprint(result)
# {'A': [{'blood': 'A', 'name': 'cho'}],
#  'AB': [{'blood': 'AB', 'name': 'choi'}],
#  'B': [{'blood': 'B', 'name': 'lee'},
#        {'blood': 'B', 'name': 'park'},
#        {'blood': 'B', 'name': 'jung'}],
#  'O': [{'blood': 'O', 'name': 'kim'}, {'blood': 'O', 'name': 'ki'}]}

groupby-1)

itertools.groupby() 함수를 사용하기 전에 먼저 분류 기준인 혈액형 순으로 정렬합니다. 혈액형으로 정렬하지 않고 itertools.goupby()를 사용하면 분류 기준이 바뀔 때마다 그룹이 생성되므로 원하는 결과를 얻을 수 없습니다.
참고로 중렬을 하기 위해 `operator.itemgetter('blood')를 사용했습니다.
operator.itemgetter() 함수 관련 글 링크

groupby-2)

pprint() 함수 관련 글 링크

groupby-3)

itertools.groupby() 또한 데이터를 혈액형별로 나누어야 하므로 키 항목을 key=operator.itemgetter('blood')와 같이 사용되었습니다. itertools.groupby()는 (분류 기준, 분류 기준으로 묶은 데이터)와 같은 튜플 형식으로 이터레이터를 반환합니다. 따라서 결과를 만들기 위해 groupby-4)의 for문을 통해 변환해야합니다.

itertools.zip_longest

itertools.zip_longest(*iterables, fillvalue=None)함수는 같은 개수의 자료형을 묶는 파이썬 내장 함수인 zip()과 똑같이 동작합니다. 하지만, itertools.zip_longest() 함수는 전달한 반복 가능 객체(*iterables)의 길이가 다르다면 긴 것을 기준으로 빠진 값은 fillvalue에 설정한 값으로 채웁니다.

예시

zip

students = ['jason', 'jake', 'kim', 'finn', 'sane']
teachers = ['lee', 'ki']

result = zip(students, teachers)
print(list(result)) # [('jason', 'lee'), ('jake', 'ki')]

students와 teachers의 개수가 다르면 teachers 개수만큼 zip()으로 묶습니다.

반면 itertools.zip_longest()를 사용하면 개수가 많은 것을 기준으로 묶을 수 있습니다. 이때 부족한 항목은 None으로 채우는데, 아래 코드처럼 fillvalue 값을 지정하면 지정한 값으로 채울 수 있습니다.

import itertools

result = itertools.zip_longest(students, teachers, fillvalue='park')
print(list(result))
# [('jason', 'lee'), ('jake', 'ki'), ('kim', 'park'), ('finn', 'park'), ('sane', 'park')]

itertools.permutations

itertools.permutations(iterable, r=None)은 반복 가능한 객체 중에서 r개를 선택한 순열을 반환하는 함수입니다.

이 모듈을 사용하면 순열을 간단하게 구할 수 있습니다.

예시

아래 코드는 1, 2, 3의 순열을 구하는 코드입니다.

import itertools 

print(list(itertools.permutations(['1', '2', '3'], 2)))
# [('1', '2'), ('1', '3'), ('2', '1'), ('2', '3'), ('3', '1'), ('3', '2')]

itertools.combinations

itertools.combinations(iterable, r)은 반복 가능 객체 중에서 r개를 선택한 조합을 이터레이터로 반환하는 함수입니다.

예시

아래는 10개의 숫자중 3개를 선택하는 경우의 수를 구하는 코드입니다.

import itertools

it = itertools.combinations(range(1, 11), 3)

for num in it:
    print(num)
# (1, 2, 3)
# (1, 2, 4)
# (1, 2, 5)
#     ... 
# (7, 8, 10)
# (7, 9, 10)
# (8, 9, 10)

순환하여 출력하지 않고 이터레이터의 개수만 세려면 아래 코드처럼 하면 됩니다.

print(len(list(itertools.combinations(range(1, 11), 3))))   # 120

만약 중복을 허용하는 조합은 itertools.combinations_with_replacement()를 사용하면 됩니다.

print(len(list(itertools.combinations_with_replacement(range(1, 11), 3))))  # 220

 

 

 

 

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

glob.glob() 함수  (0) 2023.06.11
Python 라이브러리 - functools  (0) 2023.05.24
Python 라이브러리 - operator.itemgetter  (0) 2023.05.18
Python 라이브러리 - statistics  (0) 2023.05.17
Python 라이브러리 - random  (0) 2023.05.17

공식 문서 링크

source code 링크

operator.itemgetter

operator.itemgetter는 주로 sorted와 같은 함수의 key 매개변수에 적용하여 다양한 기준으로 정렬할 수 있도록 하는 모듈입니다.

예시

이름, 나이, 성적이 있는 학생 데이터를 기준에 맞게 정렬하는 프로그램입니다.

tuple(나이 기준)

from operator import itemgetter


students = [
    ("kim", 23, "A"),
    ("park", 12, "C"),
    ("lee", 19, "B"),
]

result = sorted(students, key=itemgetter(1))
print(result)       # [('park', 12, 'C'), ('lee', 19, 'B'), ('kim', 23, 'A')]

itemgetter(1)은 students의 아이템인 튜플의 2번째 요소를 기준으로 정렬하겠다는 의미입니다.

dict(점수 기준)

students = [
    {"name": "kim", "age": "23", "grade": "A"},
    {"name": "park", "age": "12", "grade": "C"},
    {"name": "lee", "age": "19", "grade": "B"},
]

result = sorted(students, key=itemgetter('grade'))
print(result)
# [{'name': 'kim', 'age': '23', 'grade': 'A'}, {'name': 'lee', 'age': '19', 'grade': 'B'}, {'name': 'park', 'age': '12', 'grade': 'C'}]

딕셔너리일 때는 itemgetter('grade')처럼 딕셔너리의 키를 사용해야 합니다.

object

students 리스트의 요소가 Student 클래스의 객체라면 attrgetter()를 적용해 정렬해야 합니다.

from operator import attrgetter

class Student:
    def __init__(self, name: str, age: int, grade: str) -> None:
        self.name = name
        self.age = age
        self.grade = grade

students = [
    Student('kim', 23, 'A'),
    Student('park', 12, 'C'),
    Student('lee', 19, 'B'),
]

result = sorted(students, key=attrgetter('age'))

print(result[0].name)   # park
print(result[2].grade)  # A

 

 

 

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

Python 라이브러리 - functools  (0) 2023.05.24
Python 라이브러리 - itertools  (0) 2023.05.20
Python 라이브러리 - statistics  (0) 2023.05.17
Python 라이브러리 - random  (0) 2023.05.17
Python 라이브러리 - fractions  (0) 2023.05.17

공식 문서

source code 링크

statistics

statistics는 평균값과 중앙값을 구할 때 사용하는 모듈입니다.

평균값은 statistics.mean() 함수를 사용하면 간단하게 구할 수 있습니다.

import statistics

mark = [79, 29, 40, 69, 90, 100, 74, 69]
print(statistics.mean(mark))        # 68.75

중앙값은 statistics.median() 함수를 사용해 구하면 됩니다.

print(statistics.median(mark))      # 71.5

 

 

 

 

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

Python 라이브러리 - itertools  (0) 2023.05.20
Python 라이브러리 - operator.itemgetter  (0) 2023.05.18
Python 라이브러리 - random  (0) 2023.05.17
Python 라이브러리 - fractions  (0) 2023.05.17
Python 라이브러리 - decimal  (0) 2023.05.16

공식 문서

source code 링크

random

random은 난수를 생성할 때 사용하는 모듈입니다.

아래는 1 ~ 45 사이 숫자 중 임의의 숫자를 생성하는 예시 코드입니다.

import random 

result = []
while len(result) < 6:
    num = random.randint(1, 45)     # 1 ~ 45 사이의 정수 생성
    if num not in result:           # 중복 숫자 방지
        result.append(num)
print(result)       # [7, 20, 21, 25, 45, 43]

random.randint(a, b)는 a와 b사이의 무작위 정수를 생성하는 함수입니다.

shuffle과 choice

shuffle

리스트 요소를 무작위로 섞고 싶다면 random.shuffle() 함수를 사용합니다.

a = [1, 2, 3, 4]
random.shuffle(a)
print(a)        # [1, 4, 3, 2]

choice

리스트 요소에서 무작위로 하나를 선택하려면 random.choice()를 사용합니다.

print(random.choice(a))     # 4
print(random.choice(a))     # 2

 

 

 

 

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

Python 라이브러리 - operator.itemgetter  (0) 2023.05.18
Python 라이브러리 - statistics  (0) 2023.05.17
Python 라이브러리 - fractions  (0) 2023.05.17
Python 라이브러리 - decimal  (0) 2023.05.16
Python 라이브러리 - math  (0) 2023.05.16

공식 문서

source code 링크

fractions

fractions는 유리수를 계산할 때 사용하는 모듈입니다.

result = 1/5 + 2/5
print(result)   # 0.6000000000000001

위의 예시는 python에서 볼 수 있는 이상한 연산 결과의 예입니다. 원래 계산 결과는 0.6(3/5)이여야 하지만 이진수 기반의 python float 연산은 위처럼 미세한 오차가 발생할 수 있습니다.

python에서 유리수 연산을 정확하게 하려면 fractions.Fraction을 사용해야 합니다.

유리수는 Fraction(분자, 분모) 형태 또는 Fraction('분자/분모') 형태로 만들 수 있습니다.

from fractions import Fraction

result_1 = Fraction(1, 5)
result_2 = Fraction('1/5')
print(result_1, result_2, result_1 == result_2) # 1/5 1/5 True

분자의 값은 numerator로, 분모의 값은 denominator 알 수 있습니다.

print(result_1.numerator)   # 1
print(result_1.denominator) # 5

맨 처음 예시는 fractions를 사용해서 아래처럼 계산할 수 있습니다.

result = float(Fraction(1, 5) + Fraction(2, 5))
print(result)   # 0.6

 

 

 

 

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

Python 라이브러리 - statistics  (0) 2023.05.17
Python 라이브러리 - random  (0) 2023.05.17
Python 라이브러리 - decimal  (0) 2023.05.16
Python 라이브러리 - math  (0) 2023.05.16
Python 라이브러리 - enum  (0) 2023.05.16

공식 문서

source code

decimal

decimal.Decimal

decimal.Decimal은 숫자를 10진수로 처리해 정확한 소수점 자릿수를 표현할 때 사용하는 모듈입니다.

print(0.1 * 3)  # 0.30000000000000004
print(1.2 - 0.1)# 1.0999999999999999
print(0.1 * 0.1)# 0.010000000000000002

위의 예시는 python에서 볼 수 있는 이상한 연산 결과의 예입니다. 이진수 기반의 python float 연산은 위처럼 미세한 오차가 발생할 수 있습니다.

print(0.1 * 3 == 0.3)   # False

import math
print(math.isclose(0.1 * 3, 0.3))   # True

그래서 같은지를 비교할 때 == 연사낮 대신 두 값이 가까운지를 확인하는 math.isclose() 함수를 사용할 수 있습니다.

print(math.isclose(0.1 * 3, 0.3000000001))  # True

하지만 위의 예시처럼 math.isclose()는 가까운 값이면 True를 반환하기 때문에 완전한 해결이 될 수는 없습니다.

그러므로 decimal.Decimal을 사용해 문제를 해결해야 합니다.

from decimal import Decimal


result = Decimal('0.1') * 3
print(result)       # 0.3
print(type(result)) # <class 'decimal.Decimal'>

위의 예시처럼 인수('0.1')는 문자열이어야 합니다.
또한 결과값은 Decimal 자료형이고 float 자료형으로 형변환을 할 수 있습니다.

result = float(result)
print(result)       # 0.3
print(type(result)) # <class 'float'>

참고로 Decimal 객체는 위의 예시처럼 정수 연산은 가능하지만, 실수 연산은 불가능합니다.

# result = Decimal('1.1') * 3.0
# TypeError: unsupported operand type(s) for *: 'decimal.Decimal' and 'float'
result = float(Decimal('1.1') * Decimal(3.0))
print(result)       # 3.3

Decimal 사용하는 경우

Decimal은 정확성을 향상하고자 고정 소수를 사용하기에 메모리를 많이 차지합니다. 그래서 모든 float 연산을 Decimal로 바꾸는 것은 바람직하지 않습니다. 그래서 Decimal은 보통 금융권, 재무/회계 관련 프로그램을 작성할 때 사용하는 것이 좋습니다.

 

 

 

 

 

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

Python 라이브러리 - random  (0) 2023.05.17
Python 라이브러리 - fractions  (0) 2023.05.17
Python 라이브러리 - math  (0) 2023.05.16
Python 라이브러리 - enum  (0) 2023.05.16
Python 라이브러리 - pprint  (0) 2023.05.16