2025/12/31 강의 (함수, 클래스)

Soogyung Gwon·2026년 1월 6일

구름을잡아라

목록 보기
3/60

함수와 클래스

  • 변수는 Value의 이름표, 함수는 Code의 이름표

def hello():
print("hello")

id(hello) # 함수 객체가 할당한 주소를 확인 할 수 있음

파이썬에서는 함수도 객체

-> 파이썬에서는 함수도 객체이기 때문에, 나중에 같은 이름에 다른 값을 대입하면 그 이름은 새 객체를 가리킴

def f():
    print("function executed!")

f = "hello"
f()

TypeError: 'str' object is not callable
  • f라는 이름이 처음엔 함수 객체를 가리킴
  • 이후 f = "hello" 를 만나면
  • f는 이제 문자열 객체를 가리킴
  • 함수 객체는 어디에도 저장되지 않으면 사라짐(GC 대상)

함수인지 확인하는 방법

callable(f)

# or 

import inspect
inspect.isfunction(f)

함수의 ()는 호출

함수를 def 정의 한 뒤에 ()를 붙이면 함수가 호출되는 이유

call 메소드가 init 이 자동 호출되는 것과 비슷하게 호출되기 때문

class Func:
def call(self):
print("function executed!")

f = Func()
f()

-> 실행하면 파이썬 인터프리터는 call 메소드를 객체 공간에서 먼저 찾지만 없다면 클래스 공간으로 가서 찾는데 클래스 공간에 call 메소드가 존재하므로 이를 호출

위치인자

Positional variable argument(*args)
위치 인자(인수)란 함수를 호출할 때 전달되는 값이 매개변수에 순서대로 전달되는 인자
(함수 호출 시, 인자의 순서(위치)로 값이 전달되는 인자, 이름은 안 중요하고 위치만 중요함)

위치 인자 vs 키워드 인자

f(1, 2) # 위치 인자
f(a=1, b=2) # 키워드 인자
f(1, b=2) # 혼합 (가능)

def print_number(a, b, c):
	print(a, b, c)
    
print_number(7, 8, 9)

--> 인자 7은 파라미터 'a'에 인자 8은 파라미터 'b'에 9 인자는 파라미터 'c'에 연결

호출 방법

방법1

print_number(*[7, 8, 9])

방법2

print_number(*(7, 8, 9))

방법3

x = [7, 8, 9]
print_number(*x)

방법1과 방법2가 같은 결과인 이유

  • 는 “자료형”이 아니라 “iterable에 들어 있는 값들”을 펼치기 때문에 리스트든 튜플이든 결과는 같다.

가변인자 - 위치가변인자

def foo(*args):
	print(args)
    
foo(1,2,3)
foo(1,2,3,4)

함수 호출 시 args라는 변수는 여러개의 입력에 대해 튜플로 저장한 후, 이 튜플 객체를 바인딩

가변인자 종류: 1. 위치 가변 인자 - *args

가변인자 종류: 2. 키워드 가변 인자 - **kwargs

def foo(**kwargs):
	print(kwargs)

foo(a=1, b=2, c=3)

{'a': 1, 'b': 2, 'c': 3}

키워드가변인자 + 위치가변인자

파이썬 함수 정의에서 인자의 순서는 고정
일반 위치 인자 → 위치 가변 인자 (*args) → 키워드 전용 인자 → 키워드 가변 인자 (**kwargs)

def f(a, b, *args, **kwargs):
    print(a, b)
    print(args)
    print(kwargs)

f(1, 2, 3, 4, x=10, y=20)

1 2
(3, 4)
{'x': 10, 'y': 20}

왜?

파이썬의 해석 순서 때문:

위치 인자 → 왼쪽부터
남은 위치 인자 → *args
키워드 인자 → 이름 매칭
남은 키워드 인자 → **kwargs

**kwargs 는 "남은 모든 키워드 인자"를 이미 다 가져가버리기 때문에 그 뒤에 뭘 둘 수가 없음

키워드 전용 인자

def f(a, b, *, c, d):
    print(a, b, c, d)
f(1, 2, c=3, d=4)   # OK
f(1, 2, 3, 4)       # x
  • 뒤에 오는 인자는 반드시 키워드로만 전달 가능

전체 예제:

def f(a, b, *args, c=0, d=0, **kwargs):
    pass

위치 + 키워드 섞어 쓰기 예제:

def f(a, b, c):
    print(a, b, c)

f(1, c=3, b=2)

잘못된 위치 인자 사용 예제:

def f(a, *, b):
    pass

f(x=1, b=2) #에러
f(1, b=2)

잘못된 생각
-> * 뒤에만 키워드 강제니까 앞에 있는 a 는 아무 키워드나 받아도 되는 거 아닌가?

함수 인자로 리스트 & 튜플 전달하기

-> 인덱싱 수행후 전달 가능

def foo(a,b,c):
	print(a, b, c)

data = [1, 2, 3]
foo(data[0], data[1], data[2])

-> 함수 인자의 개수가 많은 경우에는 * 사용

foo(*data)

lambda 함수

def mul15(x):
	return 5 * x

lambda 키워드를 사용하는 경우

a = lambda x : 5 * x

-> a 가 함수 객체를 바인딩 하므로 a()를 통해 함수 호출 가능

a(2)
10

내장 함수

http://docs.python.org/ko/3/library/functions.html

함수안의 함수 (내부함수/외부함수)

def outer():
	def inner():
    	print("inner")
    return inner
    
d = outer()
d()

위의 코드에서 내부 함수 정의대신 어떤 값을 바인딩 하는 경우

def outer():
	inner = 3
    return inner
    
f = outer()
print(f)

3

만약 위의 코드에서 f() 하면: 에러 발생. 3은 int지 함수가 아니기 때문

LEGB 규칙

-> 어떤 변수가 바인딩 하는 값을 참조하는 경우 L, E, G, B 순으로 탐색함
변수 의미
L - Local의 약자로 함수 안을 의미
E - Enclosed funcition locals 의 약자로 내부함수에서 자신의 외부 함수의 범위를 의미
G - Global의 약자로 함수 바깥 즉, 모듈 범위
B - Built-in의 약자로 open, range와 같은 파이썬 내장함수를 의미

예제1)

a = 10

def test():
  a = 20
  print(a)

test()

20

Local 안의 20이 출력

예제2)

a = 10

def test():
  print(a)

test()

10

Local 안에 a 가 존재하지 않고 Enclosed function locals 영역은 존재하지 않으므로 Global 영역의 a를 출력

예제 3)

a = 10

def test():
  a = 20
  print(a)

test()
print(a)

20
10

Local 영역에서 Global의 변수 값을 수정할 수 없음 (로컬 변수는 함수 호출 끝날때 소멸)

예제4)
로컬 내에서 글로벌 변수 수정을 위해서는 Global 영역의 변수를 선언하는 것이 필요

a = 10

def test():
  Global a
  a = 20

test()
print(a)

20

Enclosed Function Locals

def outer():
  num = 3 # Enclosed Function Locals 영역의 변수
  def inner():
    print(num)
  return inner

f = outer()
f()
print(f.__closure__[0].cell_contents)

3
3

f 가 num 을 기억하는 이유

내부 함수 객체는 함수 객체가 생성될 때 자신이 참조하는 Enclosed Function locals 영역의 변수를 자신의 객체 안에 저장해 두기 때문

  • function 타입의 객체는 __closure__ 라는 속성을 갖고 있는데,
  • 이 속성은 튜플 타입의 객체를 바인딩 - print(type(f.__closure__))
  • 튜플의 0번은 cell 타입의 객체를 바인딩 - print(type(f.__closure__[0]))
  • 이 객체의 cell_contents 속성에 3이 저장되어있음

함수의 구조:

function object
├─ code → code object (바이트코드)
├─ globals → 전역 네임스페이스
├─ defaults → 기본 인자
├─ kwdefaults → 키워드 전용 기본값
├─ closure → cell 객체들
├─ annotations→ 타입 정보
├─ dict → 사용자 정의 속성
└─ metadata → name, module, doc

파이썬의 function 객체는
실행 코드(code) + 스코프(globals, closure) + 메타데이터를 담은 1급 객체이다.

클로저 (Closure)

외부 함수에서 내부 함수를 정의
내부 함수에서 참조하는 변수는 내부 함수 객체에 같이 저장
외부 함수는 내부 함수 객체를 리턴

클로저냐 클래스냐 - to be added

클래스

클래스는 데이터와 이를 처리하는 메서드(함수)로 구성

class Person:
  pass

p = Person()
p.data = 3

클래스에 프로퍼티를 그냥 추가할 수 있는 이유

파이썬 객체는 기본적으로 dict 를 가지고 있고, 인스턴스 속성은 여기에 동적으로 저장되기 때문
Person 클래스는 인스턴스 구조를 제한하지 않음
인스턴스는 빈 dict 를 가진 상태로 생성

p.data =3 # p.__dict__['data'] = 3

속성 접근 순서

파이썬은 아래 순서로 찾습니다 (MRO 포함):

인스턴스 p.__dict__ 클래스 Person.__dict__ 부모 클래스 (object.__dict__) 없으면 AttributeError

다른 언어는?

  • C++ / Java
    클래스 구조 고정
    컴파일 시 멤버 결정
    런타임 동적 추가 X

  • Python
    런타임 동적 모델
    객체 = 해시 테이블 기반 속성 저장
    동적 언어의 특징

속성 추가 막는 방법

class Person:
    __slots__ = ("name",)

p = Person()
p.name = "Tom"
p.data = 3   # ❌ AttributeError

생성자 (Initialization)

객체를 초기화하거나 초깃값 세팅을 위해 필요

class Person:
  def __init__(self):
   print("born...")
        
p = Person()

인스턴스 개수 세기

생성된 인스턴스의 개수는 각 인스턴스가 갖고 있기 보다는 모든 인스턴스가 참조할 수 있는 공간인 클래스에 저장되는 것이 좋음

class MyClass:
  count = 0

  def __init__(self):
    MyClass.count += 1

  def get_count(self):
    return MyClass.count

a = MyClass()
b = MyClass()
c = MyClass()

print(a.get_count())
print(MyClass.count)

매직 메서드 (Magic Method)

https://docs.python.org/3.14/reference/datamodel.html
메소드 중에 __로 시작해서 __로 끝나는 메소드를 매직 메소드 혹은 특별 메소드라 부름
예를 들어 __init__

class Car:
  def __init__(self):
    print("자동차 제작 완료")

a = Car()

리스트, 튜플, 딕셔너리, 정수, 실수, 문자열 등과 같은 타입 역시 클래스를 통해 만들어진 기본 데이터 타입

Stock 타입에 대해서 덧셈 연산자를 수행할 때 어떤 정해진 동작이 수행되도록 하려면?

class Stock:
  pass

a = Stock()
b = Stock()

print(a + b)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipython-input-298669745.py in <cell line: 0>()
      5 b = Stock()
      6 
----> 7 print(list(a + b))

TypeError: unsupported operand type(s) for +: 'Stock' and 'Stock'

Stock 클래스에 + 연산이 정의되어 있지 않아서 에러
(파이썬에서 a + b는 내부적으로 a.__add__(b)를 호출, 그런데 __add__ 메서드가 Stock 클래스에 없기 때문에)

1) __add__ 연산자를 오버로딩

class Stock:
    def __init__(self, items=None):
        self.items = items or []

    def __add__(self, other):
        return self.items + other.items

a = Stock([1, 2, 3])
b = Stock([4, 5])

print(list(a + b))
c = a + b
type(c)

[1, 2, 3, 4, 5]
list

a, b -> Stock
a+b = list

Sotck을 더했지만 add의 반환값이 객체가 아니기때문에 c 는 list 가 됨

Stock 객체에 문자열 외 값(예: int)을 넣어 만드는 경우 에러 발생.

2) __add__가 Stock 객체 반환하게 하기

class Stock:
    def __init__(self, items=None):
        self.items = items or []

    def __add__(self, other):
        return Stock(self.items + other.items)

a = Stock([1, 2])
b = Stock([3, 4])

c = a + b
print(list(c.items))
type(c)
list(c)

[1, 2, 3, 4]
Stock
TypeError: 'Stock' object is not iterable

Stock 객체가 iterable 가능한 객체라고 선언된 적이 없으므로

3) __iter__ 추가

class Stock:
    def __init__(self, items=None):
        self.items = items or []

    def __add__(self, other):
        return Stock(self.items + other.items)

    def __iter__(self):
        return iter(self.items)

a = Stock([1, 2])
b = Stock([3, 4])

print(list(a + b))

call 매직 메서드

어떤 클래스 타입에 ()를 붙여주면 __call__ 이 호출.

class MyFunc:
  def __call__(self, *args, **kwargs):
    print("호출됨")

f = MyFunc()
f()

파이썬 함수는 'function' 클래스의 객체

점(.)의 비밀

변수가 어떤 객체를 바인딩 하고 있을 때 점(.)을 찍으면 클래스의 __getattribute__ 라는 매직 메소드를 호출

class Stock:
  def __getattribute__(self, item):
     print(item, "객체에 접근하셨습니다.")

s = Stock()
s.data

객체 속성과 관련된 함수

hasattr(object, name)

내장 함수의 인자로 넘겨주는 문자열 타입의 이름이 존재하면 True 아니면 False

class Car:
  def __init__(self):
    self.wheels = 4

  def drive(self):
    print("drive")

mycar = Car()

print(hasattr(mycar, "wheels"))
print(hasattr(mycar, "drive"))

True
True

getattr(object, name, [default])

class Car:
  def __init__(self):
    self.wheels = 4

  def drive(self):
    print("drive")

mycar = Car()

getattr(mycar, "wheels")

method = getattr(mycar, "drive")
method()

drive

추상클래스

추상 클래스(abstract class)란 메서드의 이름만 존재하는(메서드 구현은 없이) 클래스로 보통 객체 지향 설계에서 부모 클래스에 메서드만을 정의하고 이를 상속받은 클래스가 해당 메서드를 반드시 구현하도록 강제하기 위해 사용

from abc import *

class Car(metaclass=ABCMeta):
  @abstractmethod
  def drive(self):
    pass

class K5(Car):
  pass

K5 = K5()

TypeError: Can't instantiate abstract class K5 without an implementation for abstract method 'drive'

drive 메서드를 추가해 주어야 함

from abc import *

class Car(metaclass=ABCMeta):
  @abstractmethod
  def drive(self):
    pass

class K5(Car):
  def drive(self):
    print("K5 drive")

K5 = K5()
K5.drive()

K5 drive

from abc import * 이유

이 클래스는 반드시 구현되어야 하는 규약(인터페이스)을 가진다”는 것을 파이썬이 강제로 검사하게 만들기 위해서입니다

ABCMeta

이 클래스를 추상 클래스로 만들어주는 메타클래스
“아직 완성되지 않은 클래스”라는 딱지를 붙임

@abstractmethod

“이 메서드는 반드시 자식 클래스에서 구현해야 한다”
구현 안 하면 인스턴스 생성 자체를 막음

profile
오랜시간 망설였던 코딩을 다시 해보려고 노력하고 있는 사람

0개의 댓글