python

파이썬 중고급 스킬들

게으른 the lazy 2025. 1. 10. 14:45

pixabay

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이다. 파이썬에서 True11.0은 모두 같다.

>>> True == 1 == 1.0
True
>>>

 

따라서 다음과 같은 현상이 발생한다.

>>> {True: 'yes', 1: 'no', 1.0: 'maybe'}
{True: 'maybe'}
>>>

 

11.0True와 같으므로 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_colorchange_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>>
>>>