๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ Python

์ดํŽ™ํ‹ฐ๋ธŒ ํŒŒ์ด์ฌ 8์žฅ - ๊ฐ•๊ฑด์„ฑ๊ณผ ์„ฑ๋Šฅ

by dev.py 2025. 4. 9.

Better way 65 try/except/else/finally์˜ ๊ฐ ๋ธ”๋ก์„ ์ž˜ ํ™œ์šฉํ•˜๋ผ

๋ธ”๋ก ์šฉ๋„
try ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ์†Œํ•œ์˜ ์ฝ”๋“œ๋งŒ ํฌํ•จ
except ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
else ์˜ˆ์™ธ ์—†์ด ์„ฑ๊ณตํ•œ ๊ฒฝ์šฐ์˜ ์ฒ˜๋ฆฌ (except์œผ๋กœ ๋น ์ง„ ๊ฒฝ์šฐ ์‹คํ–‰ X)
finally ์˜ˆ์™ธ ์—ฌ๋ถ€์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ๋ฐ˜๋“œ์‹œ ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ๋ถ€๋ถ„ (ํŒŒ์ผ ๋‹ซ๋Š” ๋“ฑ)

 

 

์•ˆ ์ข‹์€ ์˜ˆ์‹œ - try๋ฌธ ์•ˆ์— ์˜ˆ์™ธ ๋ฐœ์ƒ ์ฝ”๋“œ & ์•ˆํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์„ž์—ฌ ์žˆ์Œ

import threading

lock = threading.Lock()

def critical_section_basic():
    lock.acquire()
    try:
        result = 10 / 0  # โ— ์˜ˆ์™ธ ๋ฐœ์ƒ ๊ฐ€๋Šฅ
        print("โœ… ์ž‘์—… ์„ฑ๊ณต:", result)
    except ZeroDivisionError:
        print("โŒ 0์œผ๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
    finally:
        lock.release()
        print("๐Ÿ”“ Lock ํ•ด์ œ ์™„๋ฃŒ")

critical_section_basic()

 

 

๊ฐœ์„  - else๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์˜ˆ์™ธ ๋ฐœ์ƒ ๊ฐ€๋Šฅ ์ฝ”๋“œ๋งŒ try์— ๋ฐฐ์น˜ํ•˜์—ฌ ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ

import threading

lock = threading.Lock()

def critical_section_better():
    lock.acquire()
    try:
        result = 10 / 2  # โ— ์˜ˆ์™ธ ๋ฐœ์ƒ ๊ฐ€๋Šฅ
    except ZeroDivisionError:
        print("โŒ 0์œผ๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
    else:
        print("โœ… ์ž‘์—… ์„ฑ๊ณต:", result)  # ์˜ˆ์™ธ๊ฐ€ ์—†์„ ๋•Œ๋งŒ ์‹คํ–‰
    finally:
        lock.release()
        print("๐Ÿ”“ Lock ํ•ด์ œ ์™„๋ฃŒ")

critical_section_better()

 

 

Better way 66 ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ try/finally ๋™์ž‘์„ ์›ํ•œ๋‹ค๋ฉด contextlib๊ณผ with ๋ฌธ์„ ์‚ฌ์šฉํ•˜๋ผ

@contextmanager 

  • ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ € ์ƒ์„ฑ๊ธฐ
  • yield
    • with ๋ธ”๋ก์— ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌ
  • finally
    • with ๋ธ”๋ก์ด ๋๋‚˜๋ฉด ํ•ญ์ƒ ์‹คํ–‰๋จ

 

์ž„์‹œ ํŒŒ์ผ ์—ด๊ณ , ์ข…๋ฃŒ ์‹œ ์ž๋™ ์ •๋ฆฌํ•˜๋Š” ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ € ์ƒ์„ฑ๊ธฐ

from contextlib import contextmanager

@contextmanager
def managed_file(path):
    print("๐Ÿ“‚ ํŒŒ์ผ ์—ด๊ธฐ")
    f = open(path, 'w')
    try:
        yield f  # ํŒŒ์ผ์„ with ๋ธ”๋ก ์•ˆ์œผ๋กœ ๋„˜๊น€
    finally:
        print("โŽ ํŒŒ์ผ ๋‹ซ๊ธฐ")
        f.close()

# with ๋ธ”๋ก์œผ๋กœ ์‚ฌ์šฉ
with managed_file("sample.txt") as f:
    f.write("Hello, contextlib!")

 

Better way 67 ์ง€์—ญ ์‹œ๊ฐ„์—๋Š” time๋ณด๋‹ค๋Š” datetime์„ ์‚ฌ์šฉํ•˜๋ผ

ํ•ญ๋ชฉ time ๋ชจ๋“ˆ datetime ๋ชจ๋“ˆ
ํƒ€์ž„์กด ์ง€์› OS ์˜์กด (๋ถˆ์•ˆ์ •) ๋ช…์‹œ์  ์„ค์ • ๊ฐ€๋Šฅ (์ผ๊ด€๋จ)
์‹œ๊ฐ„ ๊ณ„์‚ฐ  ์ง์ ‘ timestamp ๊ณ„์‚ฐ ํ•„์š” timedelta๋กœ ์ง๊ด€์  ๊ณ„์‚ฐ ๊ฐ€๋Šฅ
ํ”Œ๋žซํผ ํ˜ธํ™˜์„ฑ  Windows/Linux ๊ฒฐ๊ณผ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Œ ํ”Œ๋žซํผ ๋…๋ฆฝ์ 
์ถ”์ฒœ ์šฉ๋„ ํƒ€์ด๋จธ, ๋ฒค์น˜๋งˆํฌ ๋“ฑ ์‹œ์Šคํ…œ ์‹œ๊ฐ„ ๋กœ๊น…, ์‚ฌ์šฉ์ž ์‹œ๊ฐ„ ํ‘œ์‹œ, ์‹œ๊ฐ„๋Œ€ ์ฒ˜๋ฆฌ ๋“ฑ

 

time ๋ชจ๋“ˆ์€ C ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ time.h๋ฅผ ๋ž˜ํ•‘ํ•œ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ์šด์˜์ฒด์ œ์— ๋”ฐ๋ผ ์‹œ๊ฐ„/ํƒ€์ž„์กด ์„ค์ •์ด ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋ ‡๊ธฐ์— datetime ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•˜์ž

 

import time
import os

# ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ํƒ€์ž„์กด์„ ์ž„์˜๋กœ ๋ฐ”๊ฟˆ (Linux/macOS์—์„œ๋งŒ ๋™์ž‘)
os.environ['TZ'] = 'Asia/Seoul'
time.tzset()  # ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ์ด ํ•จ์ˆ˜ ์ž์ฒด๊ฐ€ ์—†์Œ (Windows ๋ฏธ์ง€์›)

print("๐Ÿ•“ time.localtime():", time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))

# ํƒ€์ž„์กด์„ UTC๋กœ ๋ณ€๊ฒฝ
os.environ['TZ'] = 'UTC'
time.tzset()

print("๐ŸŒ time.localtime() in UTC:", time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))

์œ„์˜ ์ฝ”๋“œ๋Š” Windows ์—์„œ๋Š” ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค.

 

 

from datetime import datetime, timezone, timedelta

# ๋ช…์‹œ์ ์œผ๋กœ UTC ๊ธฐ์ค€
utc_now = datetime.now(timezone.utc)
print("๐ŸŒ UTC ์‹œ๊ฐ„:", utc_now.strftime('%Y-%m-%d %H:%M:%S %Z'))

# ํ•œ๊ตญ ์‹œ๊ฐ„ (UTC+9)
kst = timezone(timedelta(hours=9))
local_now = utc_now.astimezone(kst)
print("๐Ÿ‡ฐ๐Ÿ‡ท KST ์‹œ๊ฐ„:", local_now.strftime('%Y-%m-%d %H:%M:%S %Z'))

datetime์€ ํ”Œ๋žซํผ ๋…๋ฆฝ์ ์ด๊ธฐ์— OS์— ์ƒ๊ด€์—†์ด ์ž‘๋™ํ•œ๋‹ค.

 

 

Better way 69 ์ •ํ™•๋„๊ฐ€ ๋งค์šฐ ์ค‘์š”ํ•œ ๊ฒฝ์šฐ์—๋Š” decimal์„ ์‚ฌ์šฉํ•˜๋ผ

์„ธ๊ธˆ, ์š”๊ธˆ ๋“ฑ์˜ ์†Œ์ˆซ์  ๋‹จ์œ„์˜ ์ •ํ™•ํ•œ ๊ฐ’์ด ์ค‘์š”ํ•œ ๊ฒฝ์šฐ decimal์„ ์‚ฌ์šฉํ•˜์ž.

ํ†ตํ™”๋ฃŒ๋ฅผ ๊ณ„์‚ฐํ•ด๋ณด์ž

 

  • ํ†ตํ™”๋ฃŒ
    • 1.45 ๋‹ฌ๋Ÿฌ/๋ถ„
  • ์‹œ๊ฐ„
    • 3๋ถ„ 42์ดˆ

 

rate = 1.45
seconds = 3*60 + 42
cost = rate * seconds / 60
print(cost) # ์‹ค์ œ ๊ฐ’์€ 5.365
>>> 5.364999999999999

์‹ค์ œ๊ฐ’ ๋ณด๋‹ค 0.0.0000000000000001 ๋ณด๋‹ค ์ž‘๋‹ค.

 

 

from decimal import Decimal


rate = Decimal('1.45') # Decimal(1.45)์™€ ๊ฒฐ๊ณผ๊ฐ€ ๋‹ค๋ฅด๋‹ค
seconds = Decimal(3*60 + 42)
cost = rate * seconds / Decimal(60)
print(cost) # ์‹ค์ œ ๊ฐ’์€ 5.365
>>> 5.365

 

์ •ํ™•ํ•œ ๊ฐ’์ด ๊ณ„์‚ฐ๋œ๋‹ค.

 

 

 

Better way 70 ์ตœ์ ํ™”ํ•˜๊ธฐ ์ „์— ํ”„๋กœํŒŒ์ผ๋ง์„ ํ•˜๋ผ

ํ•ญ๋ชฉ cProfile profile
๊ตฌํ˜„ ๋ฐฉ์‹ C๋กœ ๊ตฌํ˜„๋œ ํ”„๋กœํŒŒ์ผ๋Ÿฌ ์ˆœ์ˆ˜ ํŒŒ์ด์ฌ์œผ๋กœ ๊ตฌํ˜„๋œ ํ”„๋กœํŒŒ์ผ๋Ÿฌ
์„ฑ๋Šฅ ์˜ค๋ฒ„ํ—ค๋“œ ๋งค์šฐ ์ ์Œ (๊ณ ์†) ๋А๋ฆผ (ํ•ด์„ ๋‹จ๊ณ„์—์„œ ๋А๋ฆผ)
์ •ํ™•๋„ ์ •ํ™•ํ•˜๊ณ  ์‹ค์ œ ์‹คํ–‰ ํ™˜๊ฒฝ ๋ฐ˜์˜ ์ž˜๋จ ์ •ํ™•ํ•˜์ง€๋งŒ ๋А๋ ค์„œ ์ธก์ • ๋Œ€์ƒ์— ์˜ํ–ฅ ์ค„ ์ˆ˜ ์žˆ์Œ
์ถ”์ฒœ ์šฉ๋„ ์ผ๋ฐ˜์ ์ธ ์„ฑ๋Šฅ ๋ถ„์„, ์‹ค์ „ ์ฝ”๋“œ์— ์ ํ•ฉ ๊ต์œก, ๋ถ„์„ ๋ชฉ์ , ๋””๋ฒ„๊น… ์ •๋„์— ์ ํ•ฉ
ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ import cProfile import profile

 

profile์˜ ๊ฒฝ์šฐ ์ˆœ์ˆ˜ ํŒŒ์ด์ฌ์ด๊ธฐ์— ์ธก์ • ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ์–ด cProfile์„ ์‚ฌ์šฉํ•˜์ž

import cProfile

def slow_sum():
    total = 0
    for i in range(1000000):
        total += i
    return total

def fast_sum():
    return sum(range(1000000))

def run():
    slow_sum()
    fast_sum()

cProfile.run('run()')

 

์ถœ๋ ฅ ๊ฒฐ๊ณผ

         6 function calls in 0.135 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.102    0.102    0.123    0.123 profile_demo.py:4(slow_sum)
        1    0.000    0.000    0.011    0.011 profile_demo.py:10(fast_sum)
        1    0.000    0.000    0.135    0.135 profile_demo.py:13(run)
        1    0.000    0.000    0.135    0.135 {built-in method builtins.exec}
        1    0.000    0.000    0.135    0.135 <string>:1(<module>)

 

์ปฌ๋Ÿผ ์ด๋ฆ„ ์„ค๋ช…
ncalls ํ˜ธ์ถœ ํšŸ์ˆ˜
tottime ํ•ด๋‹น ํ•จ์ˆ˜ ์ž์ฒด์—์„œ ์‚ฌ์šฉํ•œ ์‹œ๊ฐ„ (ํ•˜์œ„ ํ•จ์ˆ˜ ์ œ์™ธ)
percall ํ˜ธ์ถœ๋‹น ํ‰๊ท  ์‹œ๊ฐ„ (tottime / ncalls)
cumtime ํ•ด๋‹น ํ•จ์ˆ˜ ํฌํ•จ ์ „์ฒด ์‹คํ–‰ ์‹œ๊ฐ„ (ํ•˜์œ„ ํ•จ์ˆ˜ ํฌํ•จ)
filename:lineno(function) ํ•จ์ˆ˜ ์œ„์น˜

 

 

Better way 71 ์ƒ์‚ฐ์ž-์†Œ๋น„์ž ํ๋กœ deque๋ฅผ ์‚ฌ์šฉํ•˜๋ผ

์—ฐ์‚ฐ list ์‹œ๊ฐ„ ๋ณต์žก๋„ deque ์‹œ๊ฐ„ ๋ณต์žก๋„
append(x) O(1) O(1)
pop() O(1) O(1)
pop(0) โŒ O(n) (์•ž ์š”์†Œ๋“ค ์ด๋™) โœ… O(1)
popleft() โŒ ์—†์Œ โœ… O(1)

 

import time
from collections import deque

N = 100000

# list ์‚ฌ์šฉ
lst = list(range(N))
start = time.time()
while lst:
    lst.pop(0)  # โŒ O(n) ์—ฐ์‚ฐ
end = time.time()
print(f"list pop(0) ์ฒ˜๋ฆฌ ์‹œ๊ฐ„: {end - start:.4f}์ดˆ")

# deque ์‚ฌ์šฉ
dq = deque(range(N))
start = time.time()
while dq:
    dq.popleft()  # โœ… O(1) ์—ฐ์‚ฐ
end = time.time()
print(f"deque popleft() ์ฒ˜๋ฆฌ ์‹œ๊ฐ„: {end - start:.4f}์ดˆ")

 

list pop(0) ์ฒ˜๋ฆฌ ์‹œ๊ฐ„: 5.4321์ดˆ
deque popleft() ์ฒ˜๋ฆฌ ์‹œ๊ฐ„: 0.0043์ดˆ

๋ฆฌ์ŠคํŠธ๋Š” ๋™์  ๋ฐฐ์—ด(array) ๊ตฌ์กฐ๋กœ ๊ตฌํ˜„๋ผ ์žˆ์Œ

pop(0)์„ ํ•˜๋ฉด

  1. ์ฒซ ๋ฒˆ์งธ ์š”์†Œ๋ฅผ ์ œ๊ฑฐํ•œ ํ›„,
  2. ๋‚จ์€ ๋ชจ๋“  ์š”์†Œ๋“ค์„ ์•ž์œผ๋กœ ํ•œ ์นธ์”ฉ ๋ฐ€์–ด์•ผ ํ•จ โžœ O(n) ์‹œ๊ฐ„

์ด ์ž‘์—…์ด ๋งŽ์•„์ง€๋ฉด ์„ฑ๋Šฅ์ด ์„ ํ˜•์ ์œผ๋กœ ๋А๋ ค์ง

 

 

 

 

Better way 72 ์ •๋ ฌ๋œ ์‹œํ€€์Šค๋ฅผ ๊ฒ€์ƒ‰ํ•  ๋•Œ๋Š” bisect๋ฅผ ์‚ฌ์šฉํ•˜๋ผ

bisect ์ฃผ์š” ํ•จ์ˆ˜

ํ•จ์ˆ˜ ์„ค๋ช…
bisect.bisect_left(a, x) x๋ฅผ ๋„ฃ์„ ์ˆ˜ ์žˆ๋Š” ๊ฐ€์žฅ ์™ผ์ชฝ ์ธ๋ฑ์Šค ๋ฐ˜ํ™˜
bisect.bisect_right(a, x) x๋ฅผ ๋„ฃ์„ ์ˆ˜ ์žˆ๋Š” ๊ฐ€์žฅ ์˜ค๋ฅธ์ชฝ ์ธ๋ฑ์Šค ๋ฐ˜ํ™˜
bisect.insort(a, x) ์ •๋ ฌ๋œ ๋ฆฌ์ŠคํŠธ a์— x๋ฅผ ์‚ฝ์ž… (์ •๋ ฌ ์œ ์ง€)
import time
import bisect
import random

N = 1000000
sorted_list = list(range(N))  # 0 ~ 999,999 ๊นŒ์ง€ ์ •๋ ฌ๋œ ๋ฆฌ์ŠคํŠธ
target = random.randint(0, N)

# ์„ ํ˜• ํƒ์ƒ‰
start = time.time()
for i, x in enumerate(sorted_list):
    if x >= target:
        linear_result = i
        break
linear_time = time.time() - start
print(f"๐Ÿ” ์„ ํ˜• ํƒ์ƒ‰ ์œ„์น˜: {linear_result}, ์‹œ๊ฐ„: {linear_time:.6f}์ดˆ")

# bisect ์ด์ง„ ํƒ์ƒ‰
start = time.time()
bisect_result = bisect.bisect_left(sorted_list, target)
bisect_time = time.time() - start
print(f"โšก bisect ํƒ์ƒ‰ ์œ„์น˜: {bisect_result}, ์‹œ๊ฐ„: {bisect_time:.6f}์ดˆ")

 

๐Ÿ” ์„ ํ˜• ํƒ์ƒ‰ ์œ„์น˜: 725394, ์‹œ๊ฐ„: 0.261245์ดˆ
โšก bisect ํƒ์ƒ‰ ์œ„์น˜: 725394, ์‹œ๊ฐ„: 0.000015์ดˆ

 

  • ์ •๋ ฌ๋œ ๋ฆฌ์ŠคํŠธ์—์„œ๋Š” for ๋ฃจํ”„ ๋Œ€์‹  bisect ์‚ฌ์šฉ
  • bisect๋Š” ์ด์ง„ ํƒ์ƒ‰ ๊ธฐ๋ฐ˜ -> O(log n)
  • ๋ฆฌ์ŠคํŠธ๊ฐ€ ํด์ˆ˜๋ก ์„ฑ๋Šฅ ์ฐจ์ด๋Š” ์••๋„์ 

 

Better way 73 ์šฐ์„ ์ˆœ์œ„ ํ๋กœ heapq๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋‘๋ผ

import time
import heapq
import random

N = 10000
data = [random.randint(1, 100000) for _ in range(N)]

# ๋ฆฌ์ŠคํŠธ + sort ๋ฐฉ์‹
lst = []
start = time.time()
for num in data:
    lst.append(num)
    lst.sort()  # โŒ ๋งค๋ฒˆ ์ „์ฒด ์ •๋ ฌ
    _ = lst[0]  # ์ตœ์†Œ๊ฐ’ ์กฐํšŒ
end = time.time()
print(f"๐Ÿ“‰ list + sort() ๋ฐฉ์‹: {end - start:.4f}์ดˆ")

# heapq ๋ฐฉ์‹
heap = []
start = time.time()
for num in data:
    heapq.heappush(heap, num)   # โœ… ์ •๋ ฌ ์—†์ด push
    _ = heap[0]  # ์ตœ์†Œ๊ฐ’ ์กฐํšŒ๋Š” ํ•ญ์ƒ heap[0]
end = time.time()
print(f"โšก heapq ๋ฐฉ์‹:         {end - start:.4f}์ดˆ")

 

๐Ÿ“‰ list + sort() ๋ฐฉ์‹: 1.3826์ดˆ
โšก heapq ๋ฐฉ์‹:         0.0194์ดˆ

 

 

  • ์šฐ์„ ์ˆœ์œ„๊ฐ€ ์žˆ๋Š” ์ž‘์—… ์ฒ˜๋ฆฌ์—๋Š” ๋ฆฌ์ŠคํŠธ + sort ๋Œ€์‹  heapq๋ฅผ ์“ฐ์ž
  • ์‚ฝ์ž…๊ณผ ์ตœ์†Œ๊ฐ’ ์กฐํšŒ๊ฐ€ O(log n) ๋˜๋Š” **O(1)**๋กœ ๋น ๋ฅด๋‹ค
  • ๋ฐ˜๋ณต ์‚ฝ์ž…/์กฐํšŒ๊ฐ€ ๋งŽ์€ ๊ฒฝ์šฐ ์„ฑ๋Šฅ์ด ์ˆ˜์‹ญ ๋ฐฐ ์ด์ƒ ๋นจ๋ผ์ง„๋‹ค