파이썬의 동적 동작 사용자 정의

Beginner

This tutorial is from open-source community. Access the source code

소개

Python 의 다양한 동작은 특수 메서드 또는 소위 "매직" 메서드를 통해 사용자 정의할 수 있습니다. 이 섹션에서는 그 아이디어를 소개합니다. 또한 동적 속성 접근 및 바운드 메서드에 대해서도 논의합니다.

소개

클래스는 특수 메서드를 정의할 수 있습니다. 이러한 메서드는 Python 인터프리터에 특별한 의미를 갖습니다. 항상 __로 시작하고 끝납니다. 예를 들어 __init__가 있습니다.

class Stock(object):
    def __init__(self):
        ...
    def __repr__(self):
        ...

수십 개의 특수 메서드가 있지만, 몇 가지 특정 예시만 살펴보겠습니다.

문자열 변환을 위한 특수 메서드

객체는 두 가지 문자열 표현을 갖습니다.

>>> from datetime import date
>>> d = date(2012, 12, 21)
>>> print(d)
2012-12-21
>>> d
datetime.date(2012, 12, 21)
>>>

str() 함수는 보기 좋은 출력물을 생성하는 데 사용됩니다.

>>> str(d)
'2012-12-21'
>>>

repr() 함수는 프로그래머를 위해 더 자세한 표현을 생성하는 데 사용됩니다.

>>> repr(d)
'datetime.date(2012, 12, 21)'
>>>

이러한 함수 str()repr()은 클래스 내에서 표시될 문자열을 생성하기 위해 한 쌍의 특수 메서드를 사용합니다.

class Date(object):
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    ## Used with `str()`
    def __str__(self):
        return f'{self.year}-{self.month}-{self.day}'

    ## Used with `repr()`
    def __repr__(self):
        return f'Date({self.year},{self.month},{self.day})'

참고: __repr__()의 관례는 eval()에 제공될 때 기본 객체를 다시 생성하는 문자열을 반환하는 것입니다. 이것이 불가능한 경우, 쉽게 읽을 수 있는 종류의 표현이 대신 사용됩니다.

수학을 위한 특수 메서드

수학 연산자는 다음 메서드 호출을 포함합니다.

a + b       a.__add__(b)
a - b       a.__sub__(b)
a * b       a.__mul__(b)
a / b       a.__truediv__(b)
a // b      a.__floordiv__(b)
a % b       a.__mod__(b)
a << b      a.__lshift__(b)
a >> b      a.__rshift__(b)
a & b       a.__and__(b)
a | b       a.__or__(b)
a ^ b       a.__xor__(b)
a ** b      a.__pow__(b)
-a          a.__neg__()
~a          a.__invert__()
abs(a)      a.__abs__()

항목 접근을 위한 특수 메서드

이들은 컨테이너를 구현하기 위한 메서드입니다.

len(x)      x.__len__()
x[a]        x.__getitem__(a)
x[a] = v    x.__setitem__(a,v)
del x[a]    x.__delitem__(a)

클래스에서 사용할 수 있습니다.

class Sequence:
    def __len__(self):
        ...
    def __getitem__(self,a):
        ...
    def __setitem__(self,a,v):
        ...
    def __delitem__(self,a):
        ...

메서드 호출

메서드 호출은 두 단계의 프로세스입니다.

  1. Lookup: . 연산자
  2. Method call: () 연산자
>>> s = stock.Stock('GOOG',100,490.10)
>>> c = s.cost  ## Lookup
>>> c
<bound method Stock.cost of <Stock object at 0x590d0>>
>>> c()         ## Method call
49010.0
>>>

바운드 메서드 (Bound Methods)

함수 호출 연산자 ()에 의해 아직 호출되지 않은 메서드는 바운드 메서드로 알려져 있습니다. 이는 메서드가 생성된 인스턴스에서 작동합니다.

>>> s = stock.Stock('GOOG', 100, 490.10)
>>> s
<Stock object at 0x590d0>
>>> c = s.cost
>>> c
<bound method Stock.cost of <Stock object at 0x590d0>>
>>> c()
49010.0
>>>

바운드 메서드는 종종 부주의한 명확하지 않은 오류의 원인이 됩니다. 예를 들어:

>>> s = stock.Stock('GOOG', 100, 490.10)
>>> print('Cost : %0.2f' % s.cost)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: float argument required
>>>

또는 디버깅하기 어려운 교활한 동작이 발생할 수 있습니다.

f = open(filename, 'w')
...
f.close     ## Oops, Didn't do anything at all. `f` still open.

이 두 경우 모두, 오류는 후행 괄호를 포함하는 것을 잊어버려서 발생합니다. 예를 들어, s.cost() 또는 f.close()와 같습니다.

속성 접근 (Attribute Access)

속성에 접근하고, 조작하며, 관리하는 다른 방법이 있습니다.

getattr(obj, 'name')          ## Same as obj.name
setattr(obj, 'name', value)   ## Same as obj.name = value
delattr(obj, 'name')          ## Same as del obj.name
hasattr(obj, 'name')          ## Tests if attribute exists

예시:

if hasattr(obj, 'x'):
    x = getattr(obj, 'x'):
else:
    x = None

*참고: getattr()는 유용한 기본값 *arg*도 가지고 있습니다.

x = getattr(obj, 'x', None)

연습 문제 4.9: 객체 출력을 위한 더 나은 출력

stock.py에서 정의한 Stock 객체를 수정하여 __repr__() 메서드가 더 유용한 출력을 생성하도록 합니다. 예를 들어:

>>> goog = stock.Stock('GOOG', 100, 490.1)
>>> goog
Stock('GOOG', 100, 490.1)
>>>

이러한 변경을 한 후 주식 포트폴리오를 읽고 결과 목록을 볼 때 어떤 일이 발생하는지 확인하십시오. 예를 들어:

>>> import report
>>> portfolio = report.read_portfolio('portfolio.csv')
>>> portfolio
... see what the output is ...
>>>

연습 문제 4.10: getattr() 사용 예시

getattr()는 속성을 읽는 대체 메커니즘입니다. 이것은 매우 유연한 코드를 작성하는 데 사용될 수 있습니다. 시작하려면 다음 예제를 시도해 보십시오:

>>> import stock
>>> s = stock.Stock('GOOG', 100, 490.1)
>>> columns = ['name', 'shares']
>>> for colname in columns:
        print(colname, '=', getattr(s, colname))

name = GOOG
shares = 100
>>>

출력 데이터가 columns 변수에 나열된 속성 이름에 의해 완전히 결정된다는 점을 주의 깊게 관찰하십시오.

tableformat.py 파일에서 이 아이디어를 가져와 임의 객체 목록의 사용자 지정 속성을 표시하는 테이블을 인쇄하는 일반화된 함수 print_table()로 확장합니다. 이전의 print_report() 함수와 마찬가지로, print_table()도 출력 형식을 제어하기 위해 TableFormatter 인스턴스를 허용해야 합니다. 작동 방식은 다음과 같습니다:

>>> import report
>>> portfolio = report.read_portfolio('portfolio.csv')
>>> from tableformat import create_formatter, print_table
>>> formatter = create_formatter('txt')
>>> print_table(portfolio, ['name','shares'], formatter)
      name     shares
---------- ----------
        AA        100
       IBM         50
       CAT        150
      MSFT        200
        GE         95
      MSFT         50
       IBM        100

>>> print_table(portfolio, ['name','shares','price'], formatter)
      name     shares      price
---------- ---------- ----------
        AA        100       32.2
       IBM         50       91.1
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50       65.1
       IBM        100      70.44
>>>

요약

축하합니다! 특수 메서드 (Special Methods) 랩을 완료했습니다. LabEx 에서 더 많은 랩을 연습하여 실력을 향상시킬 수 있습니다.