본문 바로가기
CS

[Python] 파이썬다운 객체

by 왕밤빵도라에몽 2025. 4. 7.
728x90

오늘은 아래 클래스에 점진적으로 기능을 추가한다.

class Vector2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

객체 표현 메서드

  • repr(): 개발자에게 유용한 형태로 객체를 표현한 문자열 반환 (콘솔이나 디버거)
  • str(): 사용자에게 유용한 형태로 객체를 표현한 문자열 반환 (print문)

@classmethod@staticmethod

  • @classmethod: 클래스에 연산을 수행하는 메서드를 정의. 클래스 자체를 첫 번째 인수로 받음
  • @staticmethod: 메서드가 특별한 첫 번째 인수를 받지 않게 메서드를 변경

포맷된 함수

f-문자열, format() 내장함수, str.format() 메서드는 각 자료형이 가진 __format__(format_spec) 메서드를 호출함

number = 42
print(f"{number:04}")          # f-string
print(format(number, "04"))    # format() 내장함수
print("{:04}".format(number))  # str.format() 메서드
  • 모두 내부적으로 number.__format__("04") 호출
class Vector2D:
    ... 
    def __format__(self, format_spec):
        return f"({format(self.x, format_spec)}, {format(self.y, format_spec)})"
v = Vector2D(3.1415, 2.718)
print(f"{v:.2f}")  # (3.14, 2.72)
  • vector2d클래스에 문자열 포매팅 함수 추가

해시가능한 Vector2d

해시 가능하게 (__hash__ + __eq__) 추가

  • __hash__() 메서드는 int 형을 반환해야 함.
  • 그리고 동일하다고 판단되는 객체는 해시값이 동일해야 하므로 __eq__() 메서드가 비교하는 객체 속성을 이용해 해시를 계산하는 것이 이상적임!!
class Vector2D:
    ... 
    def __eq__(self, other):
        return isinstance(other, Vector2D) and self.x == other.x and self.y == other.y

    def __hash__(self):
        return hash((self.x, self.y))
s = {Vector2D(1, 2), Vector2D(1, 2)}
print(len(s))  # 1
  • set, dict 의 키로도 사용 가능

위치 패턴 매칭 지원

__match_args_-() 클래스 속성 추가

class Vector2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

v = Vector2D(1, 2)

match v:
    case Vector2D(x=1, y=2):
        print("Matched!")
  • 적용 전
class Vector2D:
    __match_args__ = ("x", "y")

    def __init__(self, x, y):
        self.x = x
        self.y = y

v = Vector2D(1, 2)

match v:
    case Vector2D(1, 2):
        print("Matched!")
  • 적용 후 Vector2D(x=1, y=2) -> Vector2D(1, 2) 이런 식으로 위치 기반 매칭 가능해짐
from dataclasses import dataclass

@dataclass
class Vector2D:
    x: int
    y: int

v = Vector2D(1, 2)

match v:
    case Vector2D(1, 2):
        print("Matched!")  # dataclass는 자동으로 __match_args__ 생성

파이썬에서의 비공개 속성과 보호된 속성

자바와 달리 파이썬에는 private 수정자 같은게 없다.
하지만 비공개하려는 속성을 서브클래스에서 실수로 변경하지 못하게하는 간단한 메커니즘은 있다.

네임 맹글링

인스턴스 속성 명을 두 개의 언더바로 시작하고 언더바가 없거나, 하나의 언더바로 끝나도록 정의하면
파이썬은 언더바와 클래스명을 변수명 앞에 붙여 인스턴스의 __dict__에 저장한다.
-> 클래스 외부에서 실수로 접근하지 않도록 보호하는 용도

class Test:
    def __init__(self):
        self.__value = 42

t = Test()
print(t.__value)  # AttributeError!
  • 사실은 이름이 바뀌어 있음:
print(t._Test__value)  # 42
  • 이런식으로 이름을 살짝 꼬아서 내부에서만 쓰게 만드는 것 == 네임 맹글링
  • 진짜 보안은 아니고 개발자들끼리 약속하는 것
  • 근데 언더바 두 개는 디버깅 시 불편함을 초래할 수 있어서 기본적으로는 언더바 하나로 외부 접근을 막는 신호만 줌

__slots__로 메모리 절약

__slots__ 을 쓰면 클래스 인스턴스가 미리 정해진 속성만 저장하도록 제한하고,
__dict__ 사용을 없애면서 메모리 사용량을 줄임

일반적으로 파이썬에서 클래스를 만들면, 인스턴스 속성은 __dict__에 저장됨

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(1, 2)
print(p.__dict__)
# {'x': 1, 'y': 2}
  • 유연해서 언제든지 속성 추가 가능
p.z = 3
print(p.__dict__)
# {'x': 1, 'y': 2, 'z': 3}
  • 하지만 속성 저장을 위해 인스턴스마다 __dict__ 가 필요 -> 인스턴스를 많이 생성하면 메모리 낭비가 많아짐
class Point:
    __slots__ = ('x', 'y')

    def __init__(self, x, y):
        self.x = x
        self.y = y
  • __slots__ 적용 시, __dict__이 생기지 않음
  • 하지만 __slots__에서 정의한 속성 외에 추가 불가능
p = Point(1, 2)
print(p.x)  # 1

p.z = 3  # AttributeError: 'Point' object has no attribute 'z'

클래스 속성 오버라이드

클래스 속성을 객체 속성의 기본값으로 사용할 수 있다는 점은 파이썬의 독특한 특징이다.

class Dog:
    species = 'Canis familiaris'  # 클래스 속성

dog1 = Dog()
dog2 = Dog()

print(dog1.species)  # Canis familiaris
print(dog2.species)  # Canis familiaris


그러나 존재하지 않는 인스턴스 속성에 값을 저장하면, 새로운 인스턴스 속성을 생성하고, 동일한 이름의 클래스 속성은 변경하지 않는다.

dog1.species = 'Canis lupus'  # dog1 인스턴스에 species 속성 추가

print(dog1.species)  # Canis lupus (인스턴스 속성!)
print(dog2.species)  # Canis familiaris (클래스 속성 그대로!)


그 후 인스턴스가 그 속성을 읽을 때 인스턴스 자체의 속성을 가져오므로, 클래스 속성과는 독립적임
클래스 속성을 바꾸면, 인스턴스 속성이 없는 애들만 영향을 받게 됨

Dog.species = 'Canis major'

print(dog1.species)  # Canis lupus (dog1 은 인스턴스 속성이 있으니까 안 바뀜!)
print(dog2.species)  # Canis major (dog2 는 클래스 속성을 따라가니까 바뀜!)


각 객체마다 서로 다른 속성 값을 갖도록 커스터마이징이 가능해짐

class Bulldog(Dog):
    species = 'Canis bulldogus'

b = Bulldog()
print(b.species)  # Canis bulldogus


클래스 속성은 공개되고, 모든 서브클래스가 상속함
클래스 데이터 속성을 커스터마이즈할 때는 클래스를 상속하는 것이 일반적


인스턴스 속성 조회 순서

  1. 인스턴스의 __dict__
  2. 클래스의 __dict__
  3. 상속받은 부모 클래스의 __dict__
  4. 에러 (AttributeError)

References

  • 『 전문가를 위한 파이썬(2판) 』, 루시아누 하말류, 한빛미디어, Part 1 - Chapter 11
728x90