Journey to CS/머신러닝

[ML 스터디 1주차] 잡다한 질문

Cordilog 2026. 7. 25. 16:00

스터디 교재: <개발자를 위한 필수 수학>

1. 내장 함수 이름을 변수로 쓰면 안 되는 이유

본문: 31p. 예제 1-11 네 배열의 합계 구하기

문제

주피터 노트북에서 아래 파이썬 코드를 실행했더니 에러가 났다.

x = [1, 5, 3, 4]
n = len(x)
summation = sum(10*x[i] for i in range(0, n))
print(summation)
 

코드 자체는 아무 문제가 없어 보인다. 

원인

원인은 그 전 셀에 있었다.

여기서 오른쪽의 sum(...)은 내장 함수로 정상 동작해서 30을 계산했다.

문제는 그 결과를 sum이라는 이름에 그대로 대입했다는 것.

그 순간부터 sum은 더 이상 함수가 아니라 정수 30이 된다.

다음 셀에서 sum(...)을 호출하면 파이썬 입장에선 30(...)을 호출하는 셈이니, "int 객체는 호출할 수 없다"는 에러가 난 것이다.

 

주피터는 커널이 살아있는 동안 변수를 계속 들고 있다.

문제의 셀을 수정하거나 삭제해도, 이미 메모리에 올라간 sum = 30은 그대로 남는다.

 

확인 방법:

print(type(sum))

 

객체가 정수 클래스에 속한다고 나온다.

해결

del sum

 

덮어쓴 변수를 지우면 내장 함수 sum으로 돌아온다.

 

또는 커널 재시작(Restart Kernel) 후 처음부터 다시 실행해도 된다.

교훈

내장 함수 이름을 변수로 쓰지 말자. 한 번 덮어쓰면 커널이 끝날 때까지 함수로 돌아오지 않는다. 

자주 사용하는 내장함수: 

sum, list, dict, set, str, int, max, min, len, id, type, input, filter, map, next, object

 

2. 게으른 연산(Lazy evaluation)

본문: 31p. '심파이의 합계'

질문

게으른 연산은 어떤 언어 패러다임에서 사용되는 개념이지? 파이썬, 자바 같은 객체지향 언어에서 쓰는 개념인가? 

답변

객체 지향과는 아무 상관 없는 개념입니다. 오히려 게으른 연산의 본고장은 함수형 언어 쪽이에요.

Lazy evaluation은 "패러다임"이 아니라 평가 전략(evaluation strategy) 의 한 종류입니다.

즉 "인자나 표현식을 언제 계산할 것인가"에 대한 언어 설계 선택이고, OOP/FP/절차형 분류와는 축이 다릅니다.

계열별로 보면

함수형 언어 — 언어 차원의 기본값

  • Haskell: 대표적. 기본 평가 전략이 lazy라서 take 10 [1..] 처럼 무한 리스트를 자연스럽게 다룹니다.
  • Miranda, Clean도 마찬가지. Scheme은 delay/force로 명시적으로 씁니다.
  • Scala는 lazy val, 이름에 의한 호출(=> T)로 선택적 지원.

절차형 언어 — 부분적으로 이미 쓰고 있음

  • C의 &&, || 단축 평가(short-circuit)가 사실상 lazy evaluation입니다. C는 객체 지향이 아니죠.
  • 매크로, #ifdef 같은 것도 넓게 보면 평가 지연입니다.

Python / Java — 라이브러리·문법 수준의 지원

  • Python: 제너레이터(yield), range(), itertools, 딕셔너리 .get()이 아닌 defaultdict... 이건 Python이 객체 지향이라서가 아니라 이터레이터 프로토콜을 언어가 지원해서입니다.
  • Java: Stream API의 중간 연산(map, filter)은 터미널 연산이 호출될 때까지 실행되지 않습니다. Supplier<T>도 지연 계산용이고요.

언어 밖의 영역

  • SQL 뷰, ORM의 lazy loading (Hibernate FetchType.LAZY)
  • Spark의 RDD/DataFrame 변환 — collect() 같은 액션 전까지 계산 안 함
  • 빌드 시스템, CDN 캐시 워밍 등

반대편 개념

Eager(strict) 표현식을 만나는 즉시 계산. 대부분 언어의 기본값
Call-by-name 필요할 때 계산하되, 쓸 때마다 다시 계산
Call-by-need 필요할 때 계산하고 결과를 메모이제이션. 엄밀한 의미의 lazy evaluation

왜 쓰나

무한 자료구조 표현, 불필요한 연산 회피(성능), 메모리 절약이 주 목적입니다.

대신 평가 시점을 예측하기 어려워 디버깅이 까다롭고, 예외 발생 지점이 엉뚱한 곳으로 밀리거나, 미평가 thunk가 쌓여 메모리를 오히려 더 먹는 space leak 문제가 생길 수 있습니다.

보안 관점에서 덧붙이면, ORM(Object-Relational Mapping)의 lazy loading이 N+1 쿼리를 유발해 DoS 표면이 되거나, 지연된 입력 검증이 예상과 다른 시점에 실행되는 케이스는 실무에서 종종 문제가 됩니다.