이번 주 학습 내용

변수와 명명 규칙부터 클래스까지, Python 기초 문법 전반을 다뤘다. 전반적으로 흐름을 따라가기 편한 한 주였다.

01. 변수

Python은 변수 선언 시 타입을 명시하지 않는다. 값을 할당하는 순간 타입이 결정된다.

언더스코어 컨벤션이 인상적이었다. 단순히 스타일 규칙이 아니라 Python이 언더스코어의 위치와 개수로 용도를 구분한다.

  • _ — 무시할 값 또는 마지막 실행 결과. x, _, z = (1, 2, 3)처럼 필요 없는 값을 버릴 때 사용
  • __ — name mangling. 클래스 외부에서 직접 접근을 차단하는 private 용도
  • __name__ — Python이 예약한 매직 어트리뷰트. 키워드처럼 취급

숫자 리터럴에 _로 자릿수를 구분할 수 있다는 것도 처음 알았다. 1_000_0001000000과 동일하다.

02. 자료형

숫자형, 불리언, 문자열, 컬렉션(list / set / dict)을 다뤘다.

  • 숫자형0b(2진수), 0o(8진수), 0x(16진수) 리터럴 표현. // 정수 나눗셈, ** 거듭제곱 연산자
  • 문자열strip(), split(), replace(), find() 등 메서드 체계. 포매팅은 f-string을 우선 사용. 다른 타입과 + 연산 불가 — 명시적 형 변환 필요
  • list — 순서 있음, 중복 허용, 수정 가능. 인덱싱과 슬라이싱으로 접근
  • set — 순서 없음, 중복 제거. union(|), intersection(&), difference(-), symmetric_difference(^) 집합 연산 지원
  • dict — key-value 쌍. key는 불변 타입만 허용. get()으로 키 없을 때 None 반환, keys() / values() / items()로 순회

Python은 암시적 형 변환이 제한적이다. True + 3 = 4, 3 + 5.0 = 8.0은 되지만 3 + "5"는 TypeError다.

03. 제어문

if / elif / else 기본 분기와 삼항 연산자를 복습했다.

  • 삼항 연산자값1 if 조건 else 값2 형식. 한 줄로 간결하게 표현 가능
  • match-case — Python 3.10에서 추가된 패턴 매칭. switch문과 유사하지만 패턴 기반으로 동작. case _:가 default 역할
  • for문 — 리스트, 문자열, 딕셔너리(기본적으로 key 순회) 모두 순회 가능. 중첩 리스트도 언패킹해서 순회할 수 있다
  • 리스트 컴프리헨션[val + 'k' for val in a]처럼 한 줄로 리스트 생성. 가독성과 성능 모두 좋다
  • break / continuebreak는 반복문 완전 종료, continue는 해당 회차 건너뜀

04. 함수

함수 정의, 인자 전달 방식, 가변 인자, lambda를 다뤘다.

  • 기본값 매개변수def greeting(name, greet="처음 뵙겠습니다")처럼 기본값 설정 가능. 기본값 있는 매개변수는 뒤에 위치해야 한다
  • 키워드 인자 — 호출 시 information(color='black', name='monkey')처럼 순서 무관하게 전달 가능. 단, 위치 인자 뒤에만 올 수 있다
  • 다중 반환return a+b, a-b처럼 여러 값을 반환하면 tuple로 묶인다. 언패킹으로 받을 수 있다
  • *args — 위치 인자를 tuple로 수집. **kwargs — 키워드 인자를 dict로 수집
  • lambda — 이름 없는 익명 함수. map(), filter(), sorted(key=...)에서 자주 활용
  • 전달 방식 — 불변 타입(int, str)은 값 복사, 가변 타입(list, dict)은 참조 전달. 함수 안에서 리스트를 수정하면 원본도 바뀐다

05. 클래스

클래스의 기본 구조와 OOP 개념인 캡슐화, 상속을 다뤘다.

  • 클래스 변수 vs 인스턴스 변수 — 클래스 변수는 모든 인스턴스가 공유. __init__에서 self.변수로 선언해야 인스턴스마다 독립적으로 생성된다
  • 접근 제어__변수명으로 선언하면 name mangling이 적용되어 외부에서 직접 접근 불가. getter/setter 메서드를 통해 제어
  • 상속class Student(Person):으로 부모 클래스를 상속. super()로 부모 메서드 호출
  • 다중 상속 — Python은 여러 부모 클래스를 동시에 상속할 수 있다. 같은 이름의 메서드가 있을 경우 MRO(Method Resolution Order) 순서로 결정

KPT 회고

Keep

모르는 문법이 나왔을 때 그냥 넘기지 않고 직접 실행해보며 확인하는 방식이 좋았다. 특히 set 집합 연산이나 언더스코어 컨벤션처럼 평소에 대충 알고 있던 부분을 코드로 확인하니 확실히 정리됐다.

이 흐름을 다음 주에도 유지하자.

Problem

쉽다는 생각에 손으로 따라치기보다 눈으로만 훑고 넘어간 부분이 있었다. 나중에 직접 써보려니 막히는 메서드 이름이나 인자 순서가 있었다.

클래스 실습에서 오타로 인한 에러를 찾는 데 생각보다 시간이 걸렸다.

Try

예제를 그대로 따라치는 것에서 벗어나 변형 예제를 직접 만들어보는 방식으로 전환한다. 예를 들어 dict 메서드를 배우면 그 자리에서 간단한 학생 성적부 dict를 만들어보는 식으로.

새롭게 발견한 것

클래스 변수의 공유 함정. 클래스 변수로 선언한 리스트는 모든 인스턴스가 공유한다. 인스턴스에서 append로 수정하면 클래스 변수 자체가 바뀌어버린다.

# 잘못된 예 — skills가 모든 인스턴스에 공유됨 class Character: skills = [] # 클래스 변수 def add_skill(self, skill): self.skills.append(skill) # 클래스 변수를 직접 수정 my = Character() your = Character() my.add_skill('곤봉 휘두르기') your.add_skill('마법진') print(my.skills) # ['곤봉 휘두르기', '마법진'] — 의도와 다름 # 올바른 예 — __init__에서 인스턴스 변수로 선언 class Character: def __init__(self): self.skills = [] # 인스턴스마다 독립적으로 생성

언더스코어 컨벤션. 별도의 키워드 없이 이름 규칙만으로 접근 제어와 용도를 표현한다는 점이 Python다웠다. _는 무시할 값, __는 외부 접근 차단, __name__은 Python 예약 어트리뷰트.

트러블슈팅

문제 : super().__init__() 호출 시 TypeError 발생

다단계 상속에서 super().__init__()을 호출할 때 self를 명시적으로 넘겼더니 인자 개수 오류가 발생했다.

# 수정 전 — self를 직접 전달 class Student(Learner): def __init__(self, name, subject): super().__init__(self, subject) # TypeError! self.name = name # TypeError: Learner.__init__() takes 2 positional arguments but 3 were given

super().__init__()은 이미 인스턴스에 바인딩된 메서드를 반환하기 때문에 self가 자동으로 전달된다. 여기에 self를 한 번 더 넘기면 인자가 하나 초과된다.

# 수정 후 — self 제거 class Student(Learner): def __init__(self, name, subject): super().__init__(subject) # 정상 동작 self.name = name

습관적으로 self를 넣었다가 생긴 실수였다. super().메서드()는 이미 self가 바인딩된 상태임을 기억하자.

다음 주 목표

  • 모듈, 패키지, 파일 I/O, 예외 처리 학습
  • 예제를 그대로 따라치지 않고 변형 예제를 직접 만들어보기
  • Python 공식 문서를 병행해서 읽는 습관 들이기
아는 것과 설명할 수 있는 것은 다르다.
기초를 다시 밟는 이번 주가 나중에 가장 단단한 발판이 될 것이다.