keyword : scope,global,nonlocal,locals,globals,closure
변수의 유효 범위(scope)는 변수가 어디에 읽고,쓸 수 있는지를 결정하는 개념이다. 파이썬에서는 전역변수(global variable) 와 지역변수(local variable)로 나뉜다.
전역 변수 (global) | 지역 변수 (local) | |
---|---|---|
함수 내 읽기 | 가능 | 가능 |
함수 내 쓰기 | (global 선언 필요) | 가능 |
함수 외 읽기 | 가능 | 불가능 |
함수 외 쓰기 | 가능 | 불가능 |
a = 10 # 전역 변수
def foo():
print('Ex1 >', a) # 전역 변수 읽기
foo()
print('Ex1 >', a) # 전역 변수 읽기
전역 변수는 함수 내에서도 읽을 수 있음.
b = 20 # 전역 변수
def bar():
b = 30 # 지역 변수 선언 (전역 변수 b와 다름)
print('Ex2 >', b) # 지역 변수 출력
bar()
print('Ex2 >', b) # 전역 변수 출력 (변하지 않음)
Ex2 > 30
Ex2 > 20
함수 내부의 지역 변수는 함수가 종료되면 사라지며, 전역 변수에는 영향을 주지 않음.
c = 40 # 전역 변수
def foobar():
c += 10 # 전역 변수 c를 수정하려 하면 오류 발생
print('Ex3 >', c)
foobar()
UnboundLocalError 발생
c += 10
을 실행하면 파이썬이 c
를 지역 변수로 간주하기 때문에 오류 발생!
해결 방법: global
키워드 사용
global
키워드를 사용하여 전역 변수 수정d = 50 # 전역 변수
def barfoo():
global d # 전역 변수 수정 가능하도록 선언
d = 60
print('Ex4 >', d)
barfoo()
print('Ex4 >', d) # 60 (전역 변수 d가 변경됨)
global
키워드를 사용하면 함수 내부에서 전역 변수 수정 가능.
하지만 전역 변수 수정은 가급적 피하는 것이 좋음
클로저(Closure)는 함수 안에서 또 다른 함수를 정의하고, 그 내부 함수를 반환하는 구조를 의미한다.
외부 함수(outer
)와 내부 함수(inner
)가 서로 공유하는 변수를 함께 사용하며 실행되는 방식이다.
nonlocal
키워드를 사용한 클로저def outer():
e = 70 # 외부 함수의 지역 변수
def inner():
nonlocal e # 외부 함수의 변수 수정 가능
e += 10
print('Ex5 >', e)
return inner
in_test = outer() # 클로저 생성
in_test() # Ex5 > 80
in_test() # Ex5 > 90
nonlocal
을 사용하면 외부 함수의 변수를 내부 함수에서 수정 가능
클로저(Closure) 패턴을 활용하여 특정 값 유지 가능
locals()
와 globals()
변수의 범위(scope)를 확인하는 함수.
locals()
(지역 변수 확인)def func(var):
x = 10
def printer():
print('Printer Func Inner')
print('Func Inner', locals()) # 지역 변수 출력
func("Hi")
locals()
는 현재 함수 내의 지역 변수를 딕셔너리 형태로 출력.
globals()
(전역 변수 확인)print('Ex7 >', globals()) # 전역 변수 전체 출력
globals()['test_variable'] = 100 # 전역 변수 동적 추가
print('Ex7 >', globals()) # test_variable 추가됨
globals()
를 사용하면 전역 변수 목록을 확인하고 동적으로 수정 가능.
globals()
를 활용한 지역 변수 → 전역 변수 변환for i in range(1, 10):
for k in range(1, 10):
globals()['plus_{}_{}'.format(i, k)] = i + k
print(plus_3_5) # 8
print(plus_9_9) # 18
globals()
를 사용하면 반복문에서 생성된 변수들을 전역 변수로 저장 가능.
전역 변수(global): 함수 외부에서 선언, 프로그램 전체에서 접근 가능
지역 변수(local): 함수 내부에서 선언, 함수 실행이 끝나면 사라짐
전역 변수 수정 시 오류 발생, 해결 방법:
global
키워드 사용 → 전역 변수 수정 가능 (⚠ 지양하는 것이 좋음)nonlocal
키워드 사용 → 클로저 내부에서 외부 변수 수정 가능locals()
→ 현재 함수 내의 변수 확인globals()
→ 전역 변수 목록 확인 및 수정 가능 lambda
, map
, filter
, reduce
Keyword:
lambda
,map
,filter
,reduce
lambda
함수란?익명 함수(Anonymous Function) 로, 한 줄로 간결하게 작성할 수 있는 함수.
일반 def
함수는 메모리에 저장되지만, lambda
함수는 힙(Heap) 영역에서 즉시 소멸됨.
lambda
사용 예시cul = lambda a, b, c: a * b + c
print('Ex1 >', cul(10, 15, 20)) # (10 * 15) + 20 = 170
def
를 사용할 필요 없이 간단한 연산을 수행할 수 있음.
map()
함수란?map()
함수는 리스트의 각 요소에 특정 함수를 적용하여 새로운 리스트를 생성하는 함수.
map(함수, 반복 가능한 자료형)
map()
기본 사용법numbers = [1, 2, 3, 4, 5]
# 일반 함수 사용
def square(x):
return x ** 2
result = list(map(square, numbers))
print(result) # [1, 4, 9, 16, 25]
# lambda 사용
result = list(map(lambda x: x ** 2, numbers))
print(result) # [1, 4, 9, 16, 25]
lambda
를 사용하면 코드가 더 간결해짐.
map()
을 반환할 때 list()
를 꼭 써야 하는 이유?def also_square(nums):
def double(x):
return x ** 2
return map(double, nums)
print('Ex2 >', also_square([10, 20, 30])) # <map object at 0x...>
print('Ex2 >', list(also_square([10, 20, 30]))) # [100, 400, 900]
map()
은 제너레이터(generator) 이므로, 리스트로 변환해야 값이 출력됨.
제너레이터는 메모리를 절약하기 위해 한 번에 하나씩 처리되기 때문.
filter()
함수란?조건을 만족하는 요소만 필터링하여 새로운 리스트를 생성하는 함수.
filter()
기본 사용법numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Lambda 사용
result = list(filter(lambda x: x % 2 == 0, numbers))
print(result) # [2, 4, 6, 8, 10]
filter()
의 함수는 True
또는 False
값을 반환해야 하며, True
인 요소만 남음.
filter()
을 반환할 때 list()
를 꼭 써야 하는 이유?def also_evens(nums):
def is_even(x):
return x % 2 == 0
return filter(is_even, nums)
print('Ex3 >', also_evens([1, 2, 3, 4])) # <filter object at 0x...>
print('Ex3 >', list(also_evens([1, 2, 3, 4]))) # [2, 4]
filter()
도 map()
과 동일하게 제너레이터(generator) 형태로 저장되므로 list()
로 변환해야 결과가 출력됨.
reduce()
함수란?reduce()
함수는 리스트의 모든 요소를 하나의 값으로 축약(누적 연산) 하는 함수.
reduce()
를 사용하려면 functools
모듈을 import해야 함.
reduce()
기본 사용법from functools import reduce
numbers = [1, 2, 3, 4, 5]
# 모든 요소를 더하기
result = reduce(lambda x, y: x + y, numbers)
print(result) # 15 (1 + 2 + 3 + 4 + 5)
리스트의 모든 요소를 하나로 합칠 때 유용함.
map()
, filter()
, reduce()
비교함수 | 동작 방식 | 반환 값 |
---|---|---|
map() | 각 요소를 변환하여 새로운 리스트 생성 | [결과1, 결과2, 결과3, ...] |
filter() | 조건을 만족하는 요소만 선택하여 새로운 리스트 생성 | [True인 요소들] |
reduce() | 모든 요소를 하나의 값으로 축약(누적 연산) | 단일 값 |
from functools import reduce
numbers = [1, 2, 3, 4, 5]
# map 사용 (각 요소를 제곱)
squared = list(map(lambda x: x ** 2, numbers))
print(squared) # [1, 4, 9, 16, 25]
# filter 사용 (짝수만 선택)
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4]
# reduce 사용 (모든 요소 합산)
total = reduce(lambda x, y: x + y, numbers)
print(total) # 15
map()
은 각 요소 변환, filter()
는 조건 필터링, reduce()
는 누적 연산에 사용됨.
lambda
→ 익명 함수, 한 줄로 간단하게 표현 가능 map()
→ 리스트의 각 요소를 변환하여 새로운 리스트 반환 filter()
→ 조건을 만족하는 요소만 필터링하여 새로운 리스트 반환 reduce()
→ 리스트의 모든 요소를 하나의 값으로 축약(누적 연산) map()
, filter()
는 제너레이터 객체를 반환하므로 list()
로 변환해야 값이 출력됨.Keyword:
mutable
,immutable
,list
,tuple
,copy
파이썬의 객체는 변경 가능(Mutable) 한 객체와 변경 불가능(Immutable) 한 객체로 나뉜다.
class | 설명 | 구분 |
---|---|---|
list | mutable 한 순서가 있는 객체 집합 | mutable |
set | mutable 한 순서가 없는 고유한 객체 집합 | mutable |
dict | key와 value가 매핑된 객체, 순서 없음 | mutable |
bool | 참, 거짓 | immutable |
int | 정수 | immutable |
float | 실수 | immutable |
tuple | immutable 한 순서가 있는 객체 집합 | immutable |
str | 문자열 | immutable |
frozenset | immutable한 set | immutable |
파이썬에서 일반적으로 사용자가 작성하는 클래스는 대부분 mutable 객체이며,
immutable한 클래스를 만들려면 특별한 방법이 필요하다.
a = [1, 2, 3] # 리스트 생성
b = a # 같은 객체를 참조
b[0] = 100
print(a) # [100, 2, 3]
print(b) # [100, 2, 3]
b[0]
을 변경하면 a[0]
도 함께 변경된다. a = (1, 2, 3) # 튜플 생성
b = a
b = (100, 2, 3) # 새로운 튜플 할당
print(a) # (1, 2, 3)
print(b) # (100, 2, 3)
a_list = [1, 2, 3, [4, 5, 6], [7, 8, 9]]
b_list = a_list # 같은 객체를 참조
b_list[2] = 100 # 변경
print(a_list) # [1, 2, 100, [4, 5, 6], [7, 8, 9]]
print(b_list) # [1, 2, 100, [4, 5, 6], [7, 8, 9]]
b_list
를 변경하면 a_list
도 함께 변경됨. import copy
c_list = [1, 2, 3, [4, 5, 6], [7, 8, 9]]
d_list = copy.copy(c_list) # 얕은 복사
d_list[4][2] = 10000
d_list[1] = 100
d_list[3].append(1000) # 내부 리스트 변경
d_list[4][2] = 10000
print(c_list) # 원본 리스트도 변경됨
print(d_list) # 변경된 값 포함
Ex2 > [1, 2, 3, [4, 5, 6, 1000], [7, 8, 10000]]
Ex2 > [1, 100, 3, [4, 5, 6, 1000], [7, 8, 10000]]
e_list = [1, 2, 3, [4, 5, 6], [7, 8, 9]]
f_list = copy.deepcopy(e_list) # 깊은 복사
f_list[3].append(1000) # 내부 리스트 변경
f_list[4][1] =10000
print(e_list) # 원본 리스트 영향 없음
print(f_list) # 복사본만 변경됨
Ex3 > [1, 2, 3, [4, 5, 6], [7, 8, 9]]
Ex3 > [1, 100, 3, [4, 5, 6], [7, 8, 9]]
복사 방법 | 새 객체 생성 여부 | 내부 객체(리스트 등) 복사 여부 | 원본과 독립성 |
---|---|---|---|
단순 복사 | ❌ (같은 객체 참조) | ❌ (같은 내부 객체 공유) | 없음 (완전히 동일) |
얕은 복사 (copy.copy() ) | ⭕ (새 객체 생성) | ❌ (내부 리스트는 공유) | 부분적 독립 (내부 객체는 공유됨) |
깊은 복사 (copy.deepcopy() ) | ⭕ (새 객체 생성) | ⭕ (내부 리스트까지 새로운 주소로 복사) | 완전 독립 |
list
, set
, dict
(내부 값 변경 가능) int
, float
, str
, tuple
, frozenset
(내부 값 변경 불가능) 객체의 특성을 잘 이해하고 상황에 맞는 복사 방법을 선택하는 것이 중요하다.