강의영상

- (1/4) motivating example

- (2/4) __str__, 파이썬의 비밀2

- (3/4) __repr__, 파이썬의 비밀3

- (4/4) 주피터노트북의 비밀 (_repr_html_), __repr____str__의 우선적용 순위

imports

import numpy as np

클래스공부 4단계

Motivating Example

- 가위바위보

class RPS: 
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
    def throw(self):
        print(np.random.choice(self.candidate))
a=RPS() # __init__는 암묵적으로 실행
a.throw()

- 생각해보니까 throw는 choose + show 의 결합인것 같다.

class RPS: ## 시점1
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
    def choose(self):
        self.actions = np.random.choice(self.candidate)
    def show(self):
        print(self.actions)
a=RPS() ## 시점2
a.actions ## 시점3
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/tmp/ipykernel_410503/3398782843.py in <module>
----> 1 a.actions ## 시점3

AttributeError: 'RPS' object has no attribute 'actions'
a.choose() ## 시점4
a.actions ## 시점5 
'바위'
a.show() ## 시점6
바위

보충학습: 위와 같은코드

class _RPS: ## 시점1
    pass # <- 이렇게하면 아무기능이 없는 비어있는 클래스가 정의된다 
_a = _RPS() ## 시점2
def _init(_a,candidate=['가위','바위','보']):
    _a.candidate = candidate 
_init(_a)
_a.actions ## 시점3
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/tmp/ipykernel_410503/1882391892.py in <module>
----> 1 _a.actions ## 시점3

AttributeError: '_RPS' object has no attribute 'actions'
def _choose(_a): ## 시점4
    _a.actions = np.random.choice(_a.candidate)
_choose(_a)
_a.actions ## 시점5
'바위'
def _show(_a): ## 시점6
    print(_a.actions)
_show(_a)
바위

- 또 다른 인스턴스 b를 만들자. b는 가위만 낼 수 있다.

b=RPS(['가위'])
b.candidate
['가위']
b.choose()
b.show()
가위

- a,b의 선택들을 모아서 기록을 하고 싶다.

class RPS:
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
        self.actions = list() 
    def choose(self):
        self.actions.append(np.random.choice(self.candidate))
    def show(self):
        print(self.actions[-1])
a=RPS()
b=RPS(['가위'])
for i in range(5):
    a.choose()
    a.show()
가위
보
보
바위
가위
a.actions
['가위', '보', '보', '바위', '가위']
for i in range(5):
    b.choose()
    b.show()
가위
가위
가위
가위
가위
b.actions
['가위', '가위', '가위', '가위', '가위']

- info라는 함수를 만들어서 a,b 오브젝트가 가지고 있는 정보를 모두 보도록 하자.

(예비학습) 문자열 \n이 포함된다면?

'asdf\n1234'
'asdf\n1234'
print('asdf\n1234')
asdf
1234

예비학습 끝

class RPS: 
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
        self.actions = list() 
    def choose(self):
        self.actions.append(np.random.choice(self.candidate))
    def show(self):
        print(self.actions[-1])
    def info(self):
        print("낼 수 있는 패: {}\n기록: {}".format(self.candidate,self.actions))
a=RPS()
b=RPS(['가위'])
for i in range(5):
    a.choose()
    a.show()    
가위
바위
가위
바위
바위
for i in range(5):
    b.choose()
    b.show()
가위
가위
가위
가위
가위
a.info()
낼 수 있는 패: ['가위', '바위', '보']
기록: ['가위', '바위', '가위', '바위', '바위']
b.info()
낼 수 있는 패: ['가위']
기록: ['가위', '가위', '가위', '가위', '가위']

- 만들고보니까 info와 print의 기능이 거의 비슷함 $\to$ print(a)를 하면 a.info()와 동일한 효과를 내도록 만들 수 있을까?

- 말도 안되는 소리같다. 왜?

  • 이유1: print는 파이썬 내장기능, 내장기능을 우리가 맘대로 커스터마이징해서 쓰기는 어려울 것 같다.
  • 이유2: 이유1이 해결된다고 쳐도 문제다. 그럼 지금까지 우리가 사용했던 수 많은 print()의 결과는 어떻게 되는가?

- 그런데 a의 자료형(RPS자료형)에 해당하는 오브젝트들에 한정하여 print를 수정하는 방법이 가능하다면? (그럼 다른 오브젝트들은 수정된 print에 영향을 받지 않음)

__str__

- 관찰1: 현재 print(a)의 결과는 아래와 같다.

print(a)
<__main__.RPS object at 0x7f091837a550>
  • a는 RPS클래스에서 만든 오브젝트이며 a가 저장된 메모리 주소는 0x7fef32fef8b0라는 의미

- 관찰2: a에는 __str__이 있다.

set(dir(a)) & {'__str__'}
{'__str__'}

이것을 함수처럼 사용하니까 아래와 같이 된다.

a.__str__()
'<__main__.RPS object at 0x7f091837a550>'

?? print(a)를 해서 나오는 문자열이 리턴된다..

print(a.__str__()) # 이거 print(a)를 실행한 결과와 같다?
<__main__.RPS object at 0x7f091837a550>

- 생각: 만약에 내가 a.__str__()라는 함수를 재정의하여 리턴값을 'guebin hahaha'로 바꾸게 되면 print(a)해서 나오는 결과는 어떻게 될까? (약간 해커같죠)

(예비학습) 함수덮어씌우기

def f():
    print('asdf')
f()
asdf
def f():
    print('guebin hahaha')
f()
guebin hahaha

이런식으로 함수가 이미 정의되어 있더라도, 내가 나중에 덮어씌우면 그 함수의 기능을 다시 정의한다.

(해킹시작)

class RPS: 
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
        self.actions = list() 
    def choose(self):
        self.actions.append(np.random.choice(self.candidate))
    def show(self):
        print(self.actions[-1])
    def __str__(self):
        return 'guebin hahaha'
    def info(self):
        print("낼 수 있는 패: {}\n기록: {}".format(self.candidate,self.actions))
a=RPS()
print(a)
guebin hahaha
print(a.__str__())
guebin hahaha

- __str__의 리턴값을 info에서 타이핑했던 문자열로 재정의한다면?

class RPS: 
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
        self.actions = list() 
    def choose(self):
        self.actions.append(np.random.choice(self.candidate))
    def show(self):
        print(self.actions[-1])
    def __str__(self):
        return "낼 수 있는 패: {}\n기록: {}".format(self.candidate,self.actions)
a=RPS()
print(a)
낼 수 있는 패: ['가위', '바위', '보']
기록: []
a.choose()
a.show()
print(a)
낼 수 있는 패: ['가위', '바위', '보']
기록: ['보']

파이썬의 비밀2

- print(a)print(a.__str__())는 같은 문법이다.

- 참고로 a.__str__()str(a)도 같은 문법이다.

a.__str__()
"낼 수 있는 패: ['가위', '바위', '보']\n기록: ['보']"
str(a)
"낼 수 있는 패: ['가위', '바위', '보']\n기록: ['보']"

- 지금까지 우리가 썼던 기능들 확인!

(예제1)

a=[1,2,3]
print(a)
[1, 2, 3]
a.__str__()
'[1, 2, 3]'
str(a)
'[1, 2, 3]'

(예제2)

a={1,2,3}
print(a)
{1, 2, 3}
a.__str__()
'{1, 2, 3}'
str(a)
'{1, 2, 3}'

(예제3)

a=np.array(1)
a.shape
()
print(a.shape)
()
a.shape.__str__()
'()'
str(a.shape)
'()'

(예제4)

a=range(10) 
print(a)
range(0, 10)
a.__str__()
'range(0, 10)'
str(a)
'range(0, 10)'

(예제5)

a = np.arange(100).reshape(10,10)
print(a)
[[ 0  1  2  3  4  5  6  7  8  9]
 [10 11 12 13 14 15 16 17 18 19]
 [20 21 22 23 24 25 26 27 28 29]
 [30 31 32 33 34 35 36 37 38 39]
 [40 41 42 43 44 45 46 47 48 49]
 [50 51 52 53 54 55 56 57 58 59]
 [60 61 62 63 64 65 66 67 68 69]
 [70 71 72 73 74 75 76 77 78 79]
 [80 81 82 83 84 85 86 87 88 89]
 [90 91 92 93 94 95 96 97 98 99]]
a.__str__()
'[[ 0  1  2  3  4  5  6  7  8  9]\n [10 11 12 13 14 15 16 17 18 19]\n [20 21 22 23 24 25 26 27 28 29]\n [30 31 32 33 34 35 36 37 38 39]\n [40 41 42 43 44 45 46 47 48 49]\n [50 51 52 53 54 55 56 57 58 59]\n [60 61 62 63 64 65 66 67 68 69]\n [70 71 72 73 74 75 76 77 78 79]\n [80 81 82 83 84 85 86 87 88 89]\n [90 91 92 93 94 95 96 97 98 99]]'
str(a)
'[[ 0  1  2  3  4  5  6  7  8  9]\n [10 11 12 13 14 15 16 17 18 19]\n [20 21 22 23 24 25 26 27 28 29]\n [30 31 32 33 34 35 36 37 38 39]\n [40 41 42 43 44 45 46 47 48 49]\n [50 51 52 53 54 55 56 57 58 59]\n [60 61 62 63 64 65 66 67 68 69]\n [70 71 72 73 74 75 76 77 78 79]\n [80 81 82 83 84 85 86 87 88 89]\n [90 91 92 93 94 95 96 97 98 99]]'

__repr__

- 생각해보니까 print를 해서 우리가 원하는 정보를 확인하는건 아니었음

a=[1,2,3]
a
[1, 2, 3]
print(a) 
[1, 2, 3]

- a + 엔터print(a) + 엔터와 같은효과인가?

(반례)

a=np.array([1,2,3,4]).reshape(2,2)
a
array([[1, 2],
       [3, 4]])
print(a)
[[1 2]
 [3 4]]

- a + 엔터print(a) + 엔터가 다른 경우도 있다. $\to$ 추측: 서로 다른 숨겨진 기능이 있다! $\to$ 결론: 추측이 맞다. 그 기능은 __repr__에 저장되어있음.

class RPS: 
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
        self.actions = list() 
    def choose(self):
        self.actions.append(np.random.choice(self.candidate))
    def show(self):
        print(self.actions[-1])
    def __repr__(self):
        return "낼 수 있는 패: {}\n기록: {}".format(self.candidate,self.actions)
a=RPS()
a # print(a.__repr__())
낼 수 있는 패: ['가위', '바위', '보']
기록: []

- 그럼 우리가 지금까지 했던것은?

a = np.array([1,2,3])
a
array([1, 2, 3])
print(a)
[1 2 3]
a.__repr__()
'array([1, 2, 3])'
a.__str__()
'[1 2 3]'

파이썬의 비밀3

- 대화형콘솔에서 오브젝트이름+엔터를 쳐서 나오는 출력은 __repr__의 결과와 연관있다.

a = np.array(range(10000)).reshape(100,100)
a
array([[   0,    1,    2, ...,   97,   98,   99],
       [ 100,  101,  102, ...,  197,  198,  199],
       [ 200,  201,  202, ...,  297,  298,  299],
       ...,
       [9700, 9701, 9702, ..., 9797, 9798, 9799],
       [9800, 9801, 9802, ..., 9897, 9898, 9899],
       [9900, 9901, 9902, ..., 9997, 9998, 9999]])
a.__repr__()
'array([[   0,    1,    2, ...,   97,   98,   99],\n       [ 100,  101,  102, ...,  197,  198,  199],\n       [ 200,  201,  202, ...,  297,  298,  299],\n       ...,\n       [9700, 9701, 9702, ..., 9797, 9798, 9799],\n       [9800, 9801, 9802, ..., 9897, 9898, 9899],\n       [9900, 9901, 9902, ..., 9997, 9998, 9999]])'

- 참고로 a.__repr__()repr(a)와 같다.

repr(a)
'array([[   0,    1,    2, ...,   97,   98,   99],\n       [ 100,  101,  102, ...,  197,  198,  199],\n       [ 200,  201,  202, ...,  297,  298,  299],\n       ...,\n       [9700, 9701, 9702, ..., 9797, 9798, 9799],\n       [9800, 9801, 9802, ..., 9897, 9898, 9899],\n       [9900, 9901, 9902, ..., 9997, 9998, 9999]])'

주피터노트북의 비밀 (_repr_html_)

- 요즘에는 IDE의 발전에 따라서 오브젝트이름+엔터칠떄 나오는 출력의 형태도 다양해지고 있음

import pandas as pd 
df = pd.DataFrame({'a':[1,2,3],'b':[2,3,4]})
df
a b
0 1 2
1 2 3
2 3 4
  • 예쁘게나온다.

- 위의결과는 print(df.__repr__())의 결과와 조금 다르게 나온다?

print(df.__repr__())
   a  b
0  1  2
1  2  3
2  3  4

- print(df.__repr__())는 예전 검은화면에서 코딩할때가 나오는 출력임

Python 3.10.2 | packaged by conda-forge | (main, Feb  1 2022, 19:28:35) [GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
> >> import pandas as pd 
>>> df = pd.DataFrame({'a':[1,2,3],'b':[2,3,4]})>>> df
   a  b
0  1  2
1  2  3
2  3  4
>>>

- 주피터에서는? "오브젝트이름+엔터"치면 HTML(df._repr_html_())이 실행되고, _repr_html_()이 정의되어 있지 않으면 print(df.__repr__())이 실행된다.

df._repr_html_()
'<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border="1" class="dataframe">\n  <thead>\n    <tr style="text-align: right;">\n      <th></th>\n      <th>a</th>\n      <th>b</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>0</th>\n      <td>1</td>\n      <td>2</td>\n    </tr>\n    <tr>\n      <th>1</th>\n      <td>2</td>\n      <td>3</td>\n    </tr>\n    <tr>\n      <th>2</th>\n      <td>3</td>\n      <td>4</td>\n    </tr>\n  </tbody>\n</table>\n</div>'
  • html코드!
from IPython.core.display import HTML
HTML(df._repr_html_())
a b
0 1 2
1 2 3
2 3 4

- 물론 df._repr_html_()함수가 내부적으로 있어도 html이 지원되지 않는 환경이라면 print(df.__repr__())이 내부적으로 수행된다.

__repr____str__의 우선적용 순위

(예제1)

- 아래의 예제를 관찰하자.

class RPS: 
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
        self.actions = list() 
    def choose(self):
        self.actions.append(np.random.choice(self.candidate))
    def show(self):
        print(self.actions[-1])
    def __repr__(self):
        return "낼 수 있는 패: {}\n기록: {}".format(self.candidate,self.actions)
a=RPS()
a
낼 수 있는 패: ['가위', '바위', '보']
기록: []
a.__repr__()
"낼 수 있는 패: ['가위', '바위', '보']\n기록: []"
repr(a)
"낼 수 있는 패: ['가위', '바위', '보']\n기록: []"

- 여기까지는 상식수준의 결과임. 이제 아래를 관찰하라.

print(a) # print(a.__str__())
낼 수 있는 패: ['가위', '바위', '보']
기록: []
a.__str__()
"낼 수 있는 패: ['가위', '바위', '보']\n기록: []"
str(a)
"낼 수 있는 패: ['가위', '바위', '보']\n기록: []"
  • __str__()은 건드린적이 없는데? $\to$ 건드린적은 없는데 기능이 바껴있음
a.__str__??
Signature:      a.__str__()
Call signature: a.__str__(*args, **kwargs)
Type:           method-wrapper
String form:    <method-wrapper '__str__' of RPS object at 0x7f08f1eebb90>
Docstring:      Return str(self).
a.__repr__??
Signature: a.__repr__()
Docstring: Return repr(self).
Source:   
    def __repr__(self):
        return "낼 수 있는 패: {}\n기록: {}".format(self.candidate,self.actions)
File:      /tmp/ipykernel_410503/2192172939.py
Type:      method

(예제2)

- 아래의 예제를 관찰하자.

class RPS: 
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
        self.actions = list() 
    def choose(self):
        self.actions.append(np.random.choice(self.candidate))
    def show(self):
        print(self.actions[-1])
    def __str__(self):
        return "낼 수 있는 패: {}\n기록: {}".format(self.candidate,self.actions)
a=RPS()
print(a)
낼 수 있는 패: ['가위', '바위', '보']
기록: []
a
<__main__.RPS at 0x7f08f1f0f290>
a.__str__()
"낼 수 있는 패: ['가위', '바위', '보']\n기록: []"
a.__repr__()
'<__main__.RPS object at 0x7f08f1f0f290>'
a.__str__??
Signature: a.__str__()
Docstring: Return str(self).
Source:   
    def __str__(self):
        return "낼 수 있는 패: {}\n기록: {}".format(self.candidate,self.actions)
File:      /tmp/ipykernel_410503/793119288.py
Type:      method
a.__repr__??
Signature:      a.__repr__()
Call signature: a.__repr__(*args, **kwargs)
Type:           method-wrapper
String form:    <method-wrapper '__repr__' of RPS object at 0x7f08f1f0f290>
Docstring:      Return repr(self).

(예제3)

class RPS: 
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
        self.actions = list() 
    def choose(self):
        self.actions.append(np.random.choice(self.candidate))
    def show(self):
        print(self.actions[-1])
    def __repr__(self):
        return "guebin hahaha"
    def __str__(self):
        return "낼 수 있는 패: {}\n기록: {}".format(self.candidate,self.actions)
a=RPS()
a
guebin hahaha
print(a)
낼 수 있는 패: ['가위', '바위', '보']
기록: []

- __str____repr__을 건드리지 않고 출력결과를 바꾸고 싶다면?

class RPS: 
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
        self.actions = list() 
    def choose(self):
        self.actions.append(np.random.choice(self.candidate))
    def show(self):
        print(self.actions[-1])
    def _repr_html_(self):
        html_str = """
        낼 수 있는 패: {} <br/> 
        기록: {}
        """
        return html_str.format(self.candidate,self.actions)
a=RPS()
str(a)
'<__main__.RPS object at 0x7f08f1eb5590>'
repr(a)
'<__main__.RPS object at 0x7f08f1eb5590>'
a
낼 수 있는 패: ['가위', '바위', '보']
기록: []
for i in range(5):
    a.choose()
    a.show()
바위
보
보
보
가위
a
낼 수 있는 패: ['가위', '바위', '보']
기록: ['바위', '보', '보', '보', '가위']

숙제

아래의 클래스를 수정하여

class RPS: 
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
        self.actions = list() 
    def choose(self):
        self.actions.append(np.random.choice(self.candidate))
    def show(self):
        print(self.actions[-1])
    def _repr_html_(self):
        html_str = """
        낼 수 있는 패: {} <br/> 
        기록: {}
        """
        return html_str.format(self.candidate,self.actions)

클래스에서 생성된 인스턴스의 출력결과가 아래와 같도록 하라.

학번: 202143052 
낼 수 있는 패: ['가위', '바위', '보']
기록: ['가위', '가위', '보', '보', '바위']