이 글은 슬기로운 파이썬 트릭을 읽고 핵심만 빠르게 정리한 글이다.
전반적으로 대부분 아는 내용이지만 remind up 한다는 느낌으로 정리해본다.
4.1. 객체 비교: is
vs ==
예제
a = [1, 2, 3]
b = a
c = [1, 2, 3]
>>> a == b
True
>>> a is b
True
>>> a == c
True
>>> a is c
False
요점 정리
- 두 변수가 동일한(identical) 객체를 가리키는 경우,
is
표현식은True
로 평가된다. - 두 변수가 동등한(equal: 내용이 같은) 객체를 가리키는 경우,
==
표현식은True
로 평가된다.
4.2. 문자열 변환(모든 클래스는 __repr__
이 필요하다)
예제
class Car:
def __init__(self, color, mileage):
self.color = color
self.mileage = mileage
def __repr__(self):
return f"{self.__class__.__name__}({self.color!r}, {self.mileage!r})"
def __str__(self):
return f"a {self.color} car"
>>> my_car = Car('red', 37281)
>>>
>>> repr(my_car)
"Car('red', 37281)"
>>> str(my_ar)
"a red car"
요점 정리
__str__
및__repr__
메서드를 사용하여 자신의 클래스에서 문자열 변환을 제어할 수 있다.__str__
의 결과는 읽을 수 있어야 한다.__repr__
의 결과는 모호하지 않아야 한다.- 항상
__repr__
을 클래스에 추가하라.__str__
의 기본 구현은__repr__
을 호출하기만 한다.- 따라서 클래스에 최소한의 구현으로 거의 모든 경우에 유용한 문자열 변환 결과가 보장된다.
4.3. 자신만의 예외 클래스 정의하기
Bad Case
def validate(name):
if len(name) < 10:
raise ValueError
Good Case
class BaseValidationError(ValueError):
pass
class NameTooShortError(BaseValidationError):
pass
class NameTooLongError(BaseValidationError):
pass
def validate(name):
if len(name) < 10:
raise NameTooShortError
요점 정리
- 고유한 예외 타입을 정의하면 코드의 의도가 좀 더 명확하게 표시되고 디버깅하기 더 쉬워진다.
- 파이썬 내장
Exception
클래스 또는ValueError
나KeyError
와 같은 구체적인 예외 클래스에서 사용자 정의 예외를 파생시키자. - 상속을 사용하여 논리적으로 그룹화된 예외 계층을 정의할 수 있다.
4.4 재미있고 이득이 되는 객체 복제하기
얕은 복사
>>> xs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> ys = list(a) # 얕은 복사본 만들기
>>
>>> xs.append([10, 11, 12])
>>> xs[1][0] = 'X'
>>> xs
[[1, 2, 3], ['X', 5, 6], [7, 8, 9], [10, 11, 12]]
>>> ys
[[1, 2, 3], ['X', 5, 6], [7, 8, 9]] # 얕은 복사본의 문제 : 두 단계 이상의 참조 객체는 복사되지 않는다.
깊은 복사
>>> import copy
>>>
>>> xs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> ys = copy.deepcopy(xs) # 깊은 복사본 만들기
>>
>>> xs.append([10, 11, 12])
>>> xs[1][0] = 'X'
>>> xs
[[1, 2, 3], ['X', 5, 6], [7, 8, 9], [10, 11, 12]]
>>> ys
[[1, 2, 3], [4, 5, 6], [7, 8, 9]] # 깊은 복사는 재귀적으로 모든 객체를 복사한다.
요점 정리
- 객체의 얕은 복사본을 만들면 자식 객체가 복사되지 않는다. 따라서 사본은 원본과 완전히 독립적이지 않다.
- 깊은 복사는 객체의 자식 객체를 재귀적으로 복사한다.
- 이렇게 얻은 복사본은 원본과 완전히 독립적이지만, 그만큼 시간이 더 걸린다.
copy
모듈을 사용하여 임의의 객체를 복사할 수 있다.
4.5. 추상화 클래스는 상속을 확인한다.
예제
from abc import ABCMeta, abstractmethod
class Base(metaclass=ABCMeta):
@abstractmethod
def foo(self):
pass
@abstractmethod
def bar(self):
pass
class Concrete(Base):
def foo(self):
pass
# bar() 선언하는 걸 잊어버렸다!
>>> c = Concrete()
TypeError: "Can't instantiate abstract class Concrete with abstract methods bar"
요점 정리
- 추상화 클래스는 파생 클래스가 인스턴스화될 때, 기반 클래스의 추상 메서드를 모두 구현했는지 확인한다.
- 추상화 클래스를 사용하면 버그를 방지하고, 클래스 계층을 쉽게 유지 관리할 수 있다.
4.6. 네임드튜플은 어디에 적합한가
네임드 튜플
from collections import namedtuple
# Car 라는 이름의 불변 클래스를 생성하고 color 와 mileage 를 속성으로 가진다.
Car = namedtuple("Car", ["color", "mileage"])
>>> my_car = Car("red", 3812.4)
>>> my_car.color
"red"
>>> my_car.mileage
3812.4
>>> color, mileage = my_car # 일반 튜플처럼 unpacking 도 가능하다.
네임드튜플 상속하기
Car = namedtuple("Car", ["color", "mileage"])
# namedtupled 의 _fields 속성으로 기존 속성목록을 가져온다.
ElectricCar = namedtuple("ElectricCar", Car._fields + ("charge", ))
>>> ElectricCar("red", 1234, 45.0)
ElectricCar(color="red", mileage=1234, charge=45.0)
내장 도우미 메서드
# _asdict() 사용 예
>>> my_car = Car("red", 3812.4)
>>> my_car._asdict()
OrderedDict([('color', 'red'), ('mileage', 3812.4)])
>>>
>>> import json
>>> json.dumps(my_car._asdict())
"{'color': 'red', 'mileage': 3812.4}"
# _replace() 사용 예
>>> my_car._replace(color='blue')
Car(color='blue', mileage=3812.4)
# _make() 사용 예
>>> Car._make(['red', 999])
Car(color='red', mileage=999)
요점 정리
collections.namedtuple
은 불변 클래스를 수동으로 정의하는 메모리 효율적인 지름길이다.- 네임드튜플은 데이터를 이해하기 쉬운 구조로 만들어 주어, 코드를 정리하는 데 도움이 될 수 있다.
4.7 클래스 변수 vs 인스턴스 변수
일반적으로 다 알고 있는 내용(클래스 변수, 인스턴스 변수가 무언인지에 대한 설명)이라 스킵. 별 내용 없다.
4.8. 인스턴스 메서드, 클래스 메서드, 정적 메서드
예제
class MyClass:
def method(self):
"""인스턴스 메서드: self 를 첫 번째 인자로 받음"""
return "instance method called", self
@classmethod
def classmethod(cls):
"""클래스 메서드: cls 를 첫 번째 인자로 받음"""
return "class method called", cls
@staticmethod
def staticmethod():
"""정적 메서드: self, cls 둘 다 안받음"""
return "staticmethod called"
>>> my_class = MyClass()
>>> my_class.method()
('instance method called', <__main__.MyClass object at 0x107d03e50>) # 인스턴스 접근
>>> MyClass.classmethod()
('class method called', <class '__main__.MyClass'>) # 클래스 자체 접근
>>> my_class.staticmethod()
staticmethod called
@classmethod
로 팩토리 메서드 만들기
class Pizza:
"""피자를 생성하는 팩토리"""
def __init__(self, ingredients):
self.ingredients = ingredients
def __repr__(self):
return f"Pizza({self.ingredients!r})"
@classmethod
def margherita(cls):
return cls(["mozzarella", "tomatoes"])
@classmethod
def prosciutto(cls):
return cls(["mozzarella", "tomatoes", "ham"])
>>> Pizza.margherita()
>>> Pizza(['mozzarella', 'tomatoes'])
>>> Pizza.prosciutto()
>>> Pizza(['mozzarella', 'tomatoes', 'ham'])
요점 정리
- 인스턴스 메서드는 인스턴스가 필요하며,
self
를 통해 인스턴스에 접근할 수 있다. - 클래스 메서드는 인스턴스가 필요하지 않다. 인스턴스(
self
) 에는 접근할 수 없지만 클래스 자체(cls
) 에 접근할 수 있다. - 정적 메서드는
cls
또는self
에 접근할 수 없다.
일반함수처럼 작동하지만 자신을 정의한 클래스의 네임스페이스 속한다. - 정적 및 클래스 메서드는 클래스 설계에 대한 개발자의 의도를 전달하고 강제한다.
이러한 점 덕분에, 유지 보수하는데 확실히 도움이 된다.
'더 나은 엔지니어가 되기 위해 > 파이썬을 파이썬스럽게' 카테고리의 다른 글
슬기로운 파이썬 트릭 5, 6 - 반복과 이터레이션 & 딕셔너리 트릭 (0) | 2020.06.06 |
---|---|
슬기로운 파이썬 트릭 4 - 파이썬의 일반 데이터 구조 (0) | 2020.05.30 |
슬기로운 파이썬 트릭 2 - 효과적인 함수 (2) | 2020.05.22 |
슬기로운 파이썬 트릭 1 - 파이썬 코드를 정돈하기 위한 패턴 (0) | 2020.05.19 |
파이썬 클린 코드 3 - 좋은 코드의 일반적인 특징 (2) | 2020.05.18 |