파이썬 중고급 스킬들
Real Python의 Intermediate and Advanced Features라는 재생목록을 보고 기억할 만한 내용을 정리한 것입니다.
is와 ==
==
는 두 객체의 값이 같은지 보고, is
는 동일한 객체인지 본다.
>>> a = [1, 2, 3]
>>> b = a
>>> a == b
True
>>> a is b
True
>>> c = [1, 2, 3]
>>> a == c
True
>>> a is c
False
>>>
unpacking operator
*
는 시퀀스를 요소를 하나씩 풀어서 전달한다. **
는 딕셔너리의 키-밸류 쌍을 풀어서 전달한다.
>>> l = [1, 3, 2, 4]
>>> print(*l)
1 3 2 4
>>> d = {0: 'zero', 1: 'one', 2: 'two'}
>>> print(*d)
0 1 2
>>> d = {0: 'zero', 1: 'one', 2: 'two'}
>>> {**d}
{0: 'zero', 1: 'one', 2: 'two'}
>>> {**d, 4:'four'}
{0: 'zero', 1: 'one', 2: 'two', 4: 'four'}
>>>
>>> def fun(x, y, z):
... print(x, 2*y, 3*z)
...
>>> d = {'x': 1, 'y': 10, 'z': 100}
>>> fun(**d)
1 20 300
>>>
True == 1 == 1.0
Boolean은 integer의 subclass이다. 파이썬에서 True
와 1
과 1.0
은 모두 같다.
>>> True == 1 == 1.0
True
>>>
따라서 다음과 같은 현상이 발생한다.
>>> {True: 'yes', 1: 'no', 1.0: 'maybe'}
{True: 'maybe'}
>>>
1
과 1.0
은 True
와 같으므로 True
키는 업데이트하지 않고 밸류만 바꾼다.
딕셔너리의 키는 hashable해야 한다.
>>> hash(True)
1
>>> hash(1)
1
>>> hash([1, 2, 3])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> {[1, 2, 3]: 3}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>>
사용자 정의 클래스의 인스턴스도 딕셔너리의 키로 사용될 수 있다. 클래스에는 __hash__
가 기본으로 들어있기 때문이다.
>>> class MyC:
... pass
...
>>> {MyC(): 0, MyC(): 1}
{<__main__.MyC object at 0x0000017C7B097140>: 0,
<__main__.MyC object at 0x0000017C7B097320>: 1}
>>> hash(MyC())
102134486795
>>> hash(MyC())
102134486816
>>>
사용자 정의 클래스에는 __eq__
도 기본으로 장착되어 있다. __eq__
를 따로 오버라이딩 하지 않으면 사용자 정의 클래스의 모든 객체는 서로 다르다.
>>> c1 = MyC()
>>> c2 = MyC()
>>> c1 == c2
False
>>> c1.__eq__(c2)
NotImplemented
>>>
__repr__
과 __str__
__str__
은 객체를 str()
이나 print()
에 넣었을 때 동작하는 메서드이다. __repr__
은 객체를 표현하는 문자열을 반환한다. __str__
이 구현되어 있지 않으면 __repr__
이 호출된다. (참고: https://docs.python.org/3/reference/datamodel.html#object.__repr__)
class Car:
def __init__(self, color):
self.color = color
def __str__(self):
return f'(str) a {self.color} car'
def __repr__(self):
return f'(repr) a {self.color} car'
>>> my_car = Car('red')
>>> my_car
(repr) a red car
>>> print(my_car)
(str) a red car
>>> str(my_car)
'(str) a red car'
>>>
__str__
은 간단한 정보만 표시하는 것이 좋다. __repr__
은 자세한 정보 표시에 사용한다.
>>> import datetime
>>> today = datetime.date.today()
>>> print(today)
2025-01-10
>>> today
datetime.date(2025, 1, 10)
>>>
인스턴스 메서드, 클래스 메서드, 정적 메서드
- 인스턴스 메서드: 인스턴스와 클래스에 모두 접근 및 수정할 수 있다.
- 클래스 메서드: 클래스는 수정할 수 있다. 인스턴스에는 접근할 수 없다.
- 정적 메서드: 클래스와 인스턴스 아무것도 수정할 수 없다. 독립적인 함수이다.
class Car:
type = 'car'
def __init__(self, color):
self._color = color
def __repr__(self):
return f'a nice {self._color} {self.type}'
def change_color(self, color):
self._color = color
def change_type(self, new_type):
self.__class__.type = new_type
@classmethod
def change_class_type(cls, new_type):
cls.type = new_type
@staticmethod
def is_valid_color(color):
valid_colors = {'red', 'blue', 'green', 'black', 'white', 'gray'}
return color in valid_colors
>>> my_car = Car('red')
change_color
와 change_type
은 인스턴스 메서드이므로 인스턴스와 클래스에 모두 접근할 수 있다.
>>> my_car = Car('red')
>>> my_car
a nice red car
>>> my_car.change_color('blue')
>>> my_car
a nice blue car
>>> my_car.change_type('truck')
>>> my_car
a nice blue truck
>>> new_car = Car('green')
>>> new_car
a nice green truck
change_class_type
은 클래스 메서드이므로 클래스에만 접근할 수 있다.
>>> my_car.change_class_type('sedan')
>>> my_car
a nice blue sedan
>>> new_car
a nice green sedan
is_valid_color
는 정적 메서드이므로 클래스와는 별도로 동작한다.
>>> my_car.is_valid_color('gray')
True
>>> my_car.is_valid_color('pink')
False
컨텍스트 매니저
클래스에 __enter__
와 __exit__
를 구현하면 컨텍스트 매니저로 활용할 수 있다.
class ManagedFile:
def __init__(self, name):
self.name = name
def __enter__(self):
self.file = open(self.name, 'w')
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
>>> with ManagedFile('D:\hello.txt') as f:
f.write('hello, world!')
>>> print(open('D:\hello.txt').read())
hello, world!
>>>
또는 contextlib.contextmanager
를 이용할 수도 있다.
>>> from contextlib import contextmanager
>>> @contextmanager
def managed_file(name):
try:
f = open(name, 'w')
yield f
finally:
f.close()
>>> with managed_file('D:\hello.txt') as f:
f.write('hello, world!')
>>> print(open('D:\hello.txt').read())
hello, world!
>>>
인스턴스 변수명 앞에 쓰이는 밑줄의 의미
파이썬의 클래스 변수는 접근 제어가 되지 않는다. 다만 변수 앞에 _
나 __
를 붙여서 약속은 할 수 있다. _
로 시작하면 protected, __
로 시작하면 private이다.
class Test:
def __init__(self):
self.foo = 11
self._bar = 23
self.__baz = 45
>>> t = Test()
>>> dir(t)
['_Test__baz', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_bar', 'foo']
>>> t.foo
11
>>> t._bar
23
>>> t.__baz
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute '__baz'. Did you mean: '_bar'?
>>>
이것만 보면 __
로 시작하면 접근 불가능한 것처럼 보이지만, 사실 가능하다.
>>> t._Test__baz
45
>>> t._Test__baz = 100
>>> t._Test__baz
100
>>>
이것을 네임 맹글링name mangling이라고 부른다.
딕셔너리에 없는 키를 접근할 경우
get()
을 이용하면 된다.
>>> d = {0: 'zero', 1: 'one', 2: 'two'}
>>> d[3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 3
>>> d.get(1, 'DoNotExist')
'one'
>>> d.get(3, 'DoNotExist')
'DoNotExist'
>>>
이해하기 쉬운 예외 처리 만들기
원하는 에러를 상속 받아서 새로운 클래스를 만든다.
class NameTooShortError(ValueError):
pass
def validate(name):
if len(name) < 10:
raise NameTooShortError(name)
>>> validate('GandalftheWhite')
>>> validate('Gandalf')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in validate
NameTooShortError: Gandalf
>>>
범주형 자료의 발생 횟수를 세고 싶다면
collections.Counter()
를 사용하면 된다.
>>> import collections
>>> inven = collections.Counter()
>>> inven.update({'apple': 1, 'banana': 3})
>>> inven
Counter({'banana': 3, 'apple': 1})
>>> inven.update({'banana': 10, 'cherry': 5})
>>> inven
Counter({'banana': 13, 'cherry': 5, 'apple': 1})
>>>
타입 힌팅에서 자기 자신을 참조하기
클래스 선언 중에 반환값 타입을 자기 자신으로 쓰면 에러가 발생한다.
>>> class MyClass:
... def get_instance(self) -> MyClass:
... return self
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in MyClass
NameError: name 'MyClass' is not defined
>>>
반환값 타입을 문자열로 쓰면 된다.
>>> class MyClass:
... def get_instance(self) -> "MyClass":
... return self
...
>>> c = MyClass()
>>> c.get_instance
<bound method MyClass.get_instance of <__main__.MyClass object at 0x0000023E471EE2D0>>
>>>
끗