강의영상

- (1/8) 클래스 공부 7단계 (1)

- (2/8) 클래스 공부 7단계 (2)

- (3/8) 클래스 공부 7단계 (3)

- (4/8) 클래스 공부 8단계

- (5/8) 클래스 공부 9단계 (1)

- (6/8) 클래스 공부 9단계 (2)

- (7/8) 클래스 공부 10단계 (1)

- (8/8) 클래스 공부 10단계 (2)

imports

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

클래스공부 7단계

함수공부

- 다시 함수를 공부해봅시다.

def f(x):
    return x+1 
f(3)
4

- 함수의 사용방법?

  • 입력으로 변수 x를 받음 = 입력으로 인스턴스 x를 받음.
  • 출력으로 변수 x+1을 리턴 = 출력으로 인스턴스 x+1을 리턴.

- 사실1: 파이썬에서 함수는 인스턴스를 입력으로 받고 인스턴스를 출력한다.

- 함수의 자료형?

?f
Signature: f(x)
Docstring: <no docstring>
File:      /tmp/ipykernel_2046213/2907208198.py
Type:      function
  • type이 functiond이다.
  • f는 function class의 instance이다.
  • 결국 f 역시 하나의 오브젝트에 불과하다.

- 사실2: 함수도 결국 인스턴스이다. -> 함수의 입력으로 함수를 쓸 수도 있고 함수의 출력으로 함수가 나올 수도 있다.

함수형 프로그래밍

(예제1) 숫자입력, 함수출력

def f(a):
    def _f(x):
        return (x-a)**2 
    return _f
g=f(10) # g(x)=(x-10)**2 
g(2) # (2-10)**2 = 64
64
  • 해석: f(a)는 a를 입력으로 받고 g(x)=(x-a)^2 를 함수를 리턴해주는 함수

(예제1)의 다른표현: 익명함수 lambda

표현1

def f(a):
    _f = lambda x: (x-a)**2 ### lambda x: (x-a)**2 가 실행되는 순간 함수오브젝트가 만들어지고 그것이 _f 로 저장됨 
    return _f
g=f(10) # g(x)=(x-10)**2 
g(3) # (3-10)**2 = 49
49

표현2

def f(a):
    return lambda x: (x-a)**2 
g=f(10) # g(x)=(x-10)**2 
g(3) # (3-10)**2 = 49
49
  • lambda x: (x-a)**2 는 $\text{lambda}(x) = (x-a)^2$의 느낌으로 기억하면 외우기 쉽다.
  • lambda x: (x-a)**2 는 "아직 이름이 없는 함수오브젝트를 (가칭 lambda 라고 하자) 만들고 기능은 x를 입력으로 하고 (x-a)**2를 출력하도록 하자" 라는 뜻으로 해석하면 된다.

(예제2) 함수입력, 숫자출력

def f(x):
    return x**2 
def d(f,x): # 함수를 입력을 받는 함수를 정의 
    h=0.000000000001
    return (f(x+h)-f(x))/h 
d(f,4) # f'(4) = 2*4 = 8
8.000711204658728

(예제3) 함수입력, 함수출력

def f(x): 
    return x**2 
def derivate(f): 
    def df(x): 
        h=0.000000000001
        return (f(x+h)-f(x))/h 
    return df
ff = derivate(f)
ff(7) # f의 도함수
14.004797321831575

원래함수 시각화

x = np.linspace(-1,1,100)
plt.plot(x,f(x))
[<matplotlib.lines.Line2D at 0x7fdff26c2bb0>]

도함수 시각화

x = np.linspace(-1,1,100)
plt.plot(x,ff(x))
[<matplotlib.lines.Line2D at 0x7fdff25d4520>]

(예제3)의 다른표현

def f(x): 
    return x**2 
def derivate(f): 
    h=0.000000000001
    return lambda x: (f(x+h)-f(x))/h 
ff = derivate(f)
ff(10)
20.00888343900442

(예제4) 함수들의 리스트

flst = [lambda x: x, lambda x: x**2, lambda x: x**3] 
flst
[<function __main__.<lambda>(x)>,
 <function __main__.<lambda>(x)>,
 <function __main__.<lambda>(x)>]
for f in flst:
    print(f(2))
2
4
8
for f in flst: 
    plt.plot(x,f(x),'--')

위의코드는 아래와 같음

plt.plot(x,(lambda x: x)(x),'--')
plt.plot(x,(lambda x: x**2)(x),'--')
plt.plot(x,(lambda x: x**3)(x),'--')
[<matplotlib.lines.Line2D at 0x7fdff24d0550>]

정리

- 지금까지 개념

  • 함수: 변수를 입력으로 받아서 변수를 출력하는 개념
  • 변수: 어떠한 값을 저장하는 용도로 쓰거나 함수의 입력 혹은 출력으로 사용함

- 파이썬의 함수형프로그래밍을 잘하려면?

  • 변수든 함수이든 둘다 인스턴스임.
  • 변수를 함수처럼: 메소드
  • 함수를 변수처럼($\star$): 함수자체를 함수의 입력으로 혹은 출력으로 쓸 수도 있음. 함수를 특정 값처럼 생각해서 함수들의 list를 만들 수도 있다.

callable object

- 함수 오브젝트의 비밀?

f = lambda x: x+1 
lst=[1,2,3]
set(dir(f)) & {'__call__'}
{'__call__'}
  • 함수 오브젝트에는 숨겨진 기능 __call__이 있다.
f.__call__(3) # f(3)
4
f.__call__(4) # f(4)
5
  • 여기에 우리가 정의한 내용이 있다.

- 함수처럼 쓸 수 없는 인스턴스는 단지 call이 없는 것일 뿐이다.

class Klass: 
    def __init__(self):
        self.name='guebin'
a=Klass()
a()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_2046213/3657634075.py in <module>
----> 1 a()

TypeError: 'Klass' object is not callable
  • a는 callable이 아니라고 한다.
class Klass2(Klass):  # 상속 
    def __call__(self):
        print(self.name)
b=Klass2()
b()
guebin
  • b는 callable object! 즉 숨겨진 메서드로 __call__를 가진 오브젝트!
  • Klass는 callable object를 만들지 못하지만 Klass2는 callable object를 만든다.

- 클래스로 함수를 만들기

class AddConstant: 
    def __init__(self,c):
        self.c = c 
    def __call__(self,a):
        return a + self.c 
f = AddConstant(3) # callable object생성, f.c에는 3이 저장되어있음. 
f(5) # f.c 와 5를 더하는 기능을 수행, # f(x) = x+3 을 수행함
8
f(10) 
13

- 클래스도 그러고보니까 오브젝트 아니었나? 클래스도 함수처럼 Klass()와 같이 사용하면 인스턴스를 만들었음. -> Klass.__call__()Klass()와 같은 역할을 할 것이다.

class Klass: 
    def __init__(self):
        self.name='hynn'
a=Klass.__call__() # 이것이 a=Klass() 와 같은 효과 
a.name
'hynn'

파이썬의 비밀 1~4

  • 파이썬의 비밀1: 자료형은 클래스의 이름이다.
  • 파이썬의 비밀2: 클래스에는 __str__처럼 숨겨진 메서드가 존재한다. 이를 이용하여 파이썬 내부의 기능을 가로챌 수 있다.
  • 파이썬의 비밀3: 주피터노트북에서는 "오브젝트이름+엔터"를 쳐서 나오는 출력은 __repr__로 가로챌 수 있따. (주피터의 비밀)
  • 파이썬의 비밀4: 함수와 클래스는 숨겨진 메소드에 __call__을 가진 오브젝트일 뿐이다.

클래스공부 8단계

for문의 복습

- 아래와 같은 예제들을 관찰하여 for문을 복습하자.

(예제1)

for i in [1,2,3,4]:
    print(i)
1
2
3
4

(예제2)

for i in (1,2,3,4): 
    print(i)
1
2
3
4

(예제3)

for i in '1234':
    print(i)
1
2
3
4

(예제4)

a=5 
for i in a:
    print(i) 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_2046213/2370651252.py in <module>
      1 a=5
----> 2 for i in a:
      3     print(i)

TypeError: 'int' object is not iterable
  • 5라고 출력되어야 하지 않나?

- 의문1:

for i in ???:
    print(i)

에서 ??? 자리에 올수 있는 것이 무엇일까?

(예제5)

상황1

lst = [[1,2,3,4],[3,4,5,6]]
for l in lst: 
    print(l)
[1, 2, 3, 4]
[3, 4, 5, 6]

상황2

df=pd.DataFrame(lst)
df
0 1 2 3
0 1 2 3 4
1 3 4 5 6
for i in df:
    print(i)
0
1
2
3

칼럼이름들이 나오는것 같음 -> 확인해보자.

df.columns = pd.Index(['X'+str(i) for i in range(1,5)])
df
X1 X2 X3 X4
0 1 2 3 4
1 3 4 5 6
for i in df: 
    print(i) 
X1
X2
X3
X4

- 의문2: for의 출력결과는 어떻게 예측할 수 있을까?

for문의 동작원리

- 의문1의 해결: 아래의 ??? 자리에 올 수 있는 것은 dir()하여 __iter__가 있는 object이다.

for i in ???:
    print(i)

이러한 오브젝트를 iterable object라고 한다.

- 확인

a=[1,2,3] 
set(dir(a)) & {'__iter__'}
{'__iter__'}
a=1,2,3
set(dir(a)) & {'__iter__'}
{'__iter__'}
a='123'
set(dir(a)) & {'__iter__'}
{'__iter__'}
a=5
set(dir(a)) & {'__iter__'}
set()
  • 예상대로 예제1~4에서는 int의 클래스의 instance만 __iter__ 가 없다.

- __iter__의 역할: iterable object를 iterator로 만들 수 있다!

lst = [1,2,3] 
lst 
[1, 2, 3]
lst[1] # 충실한 리스트
2
ltor = iter(lst)
#ltor = lst.__iter__() 
ltor
<list_iterator at 0x7fdff23cf070>
ltor[1] # 더이상 리스트가 아니다
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_2046213/3608210670.py in <module>
----> 1 ltor[1] # 더이상 리스트가 아니다

TypeError: 'list_iterator' object is not subscriptable
ltor?
Type:        list_iterator
String form: <list_iterator object at 0x7fdff23cf070>
Docstring:   <no docstring>

- iterator가 되면 무엇이 좋은가? -> 숨겨진 기능 __next__가 열린다.

set(dir(lst)) & {'__next__'}, set(dir(ltor)) & {'__next__'}
(set(), {'__next__'})
  • lst에는 __next__가 없지만 ltor에는 있다!

- 그래서 __next__의 기능은? -> 원소를 차례대로 꺼내준다 + 더 이상 꺼낼 원소가 없으면 StopIteration Error를 발생시킨다.

lst
[1, 2, 3]
ltor.__next__() 
1
ltor.__next__() 
2
ltor.__next__() 
3
ltor.__next__()
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
/tmp/ipykernel_2046213/190431829.py in <module>
----> 1 ltor.__next__()

StopIteration: 

- for문의 동작원리

for i in lst:
    print(i)

(1) lst.__iter__() 혹은 iter(lst) 를 이용하여 lst를 iterator로 만든다. (iterable object를 iterator object로 만든다)

(2) iterator에서 .__next__()함수를 호출하고 결과를 i에 저장한뒤에 for문 블락안에 있는 내용 (들여쓰기 된 내용)을 실행한다. -> 반복

(3) StopIteration 에러가 발생하면 for문을 멈춘다.

- 아래의 ??? 자리에 올 수 있는 것이 iterable object가 아니라 iterator 자체이여도 for문이 돌아갈까? (당연히 돌아가야 할 것 같음)

for i in ???:
    print(i)
for i in iter([1,2,3]):
    print(i)
1
2
3
  • 당연히가능!

- a가 iterator일때 iter(a) 의 출력결과가 a와 같도록 조정한다면 for문의 동작원리 (1)-(3)을 수정하지 않아도 좋다. -> 실제로 이렇게 동작한다.

- 요약

  • iterable object는 숨겨진 기능으로 __iter__를 가진다.
  • iterator object는 숨겨진 기능으로 __iter____next__를 가진다. (즉 iterator는 그 자체로 iterable object가 된다!)
lst = [1,2,3]
ltor = iter(lst) 
set(dir(lst)) & {'__iter__', '__next__'}
{'__iter__'}
set(dir(ltor)) & {'__iter__', '__next__'}
{'__iter__', '__next__'}

- 의문2의 해결: for의 출력결과는 어떻게 예측할 수 있을까? iterator를 만들어서 .__next__()의 출력값을 확인하면 알 수 있다.

for i in df:
    print(i)
X1
X2
X3
X4
dftor=iter(df)
dftor.__next__()
'X1'
dftor.__next__()
'X2'
dftor.__next__()
'X3'
dftor.__next__()
'X4'
dftor.__next__()
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
/tmp/ipykernel_2046213/2067653472.py in <module>
----> 1 dftor.__next__()

StopIteration: 

range()

- 파이썬에서 for문을 처음 배울 때: range(5)를 써라!

for i in range(5):
    print(i)
0
1
2
3
4
  • range(5)가 도데체 무엇이길래?

- range(5)의 정체는 그냥 iterable object이다.

set(dir(range(5))) & {'__iter__','__next__'}
{'__iter__'}

- 그래서 언제든지 iterator로 바꿀 수 있다.

rtor= iter(range(5))
rtor
<range_iterator at 0x7fdff23aec00>
set(dir(rtor)) &  {'__iter__','__next__'}
{'__iter__', '__next__'}

- for문에서 range(5)가 행동하는 방법?

rtor = iter(range(5))
rtor.__next__()
0
rtor.__next__()
1
rtor.__next__()
2
rtor.__next__()
3
rtor.__next__()
4
rtor.__next__()
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
/tmp/ipykernel_2046213/1496931225.py in <module>
----> 1 rtor.__next__()

StopIteration: 

zip

- 이터레이터의 개념을 알면 for문에 대한 이해도가 대폭 상승한다.

for i in zip([1,2,3],'abc'):
    print(i)
(1, 'a')
(2, 'b')
(3, 'c')
  • zip은 뭐지?
zip([1,2,3],'abc')
<zip at 0x7fdff2359bc0>

- 어차피 for i in ????: 의 ???? 자리는 iterable object의 자리이다.

set(dir(zip([1,2,3],'abc'))) & {'__iter__','__next__'}
{'__iter__', '__next__'}
  • __next__()함수가 있음 $\to$ zip([1,2,3],'abc')은 그자체로 iterator 였다!
z= zip([1,2,3],'abc')
z.__next__()
(1, 'a')
z.__next__()
(2, 'b')
z.__next__()
(3, 'c')
z.__next__()
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
/tmp/ipykernel_2046213/4267025455.py in <module>
----> 1 z.__next__()

StopIteration: 

사용자정의 이터레이터

- 내가 이터레이터를 만들어보자.

class Klass: # 찌를 내는순간 for문이 멈추도록 하는 이터레이터를 만들자
    def __init__(self): 
        self.candidate = ["묵","찌","빠"] 
    def __iter__(self):
        return self 
    def __next__(self):
        action = np.random.choice(self.candidate)
        if action == "찌":
            print("찌가 나와서 for문을 멈춥니다")
            raise StopIteration
        else:
            return action
a= Klass()
set(dir(a)) & {'__iter__', '__next__'} # a는 이터레이터!
{'__iter__', '__next__'}
a.__next__()
'빠'
a.__next__()
'빠'
a.__next__()
찌가 나와서 for문을 멈춥니다
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
/tmp/ipykernel_2046213/1464353201.py in <module>
----> 1 a.__next__()

/tmp/ipykernel_2046213/2569486800.py in __next__(self)
      8         if action == "찌":
      9             print("찌가 나와서 for문을 멈춥니다")
---> 10             raise StopIteration
     11         else:
     12             return action

StopIteration: 
a.__next__()
'묵'
for i in a:
    print(i)
빠
묵
묵
묵
빠
찌가 나와서 for문을 멈춥니다

파이썬의 비밀 1~5

  • 파이썬의 비밀1: 자료형은 클래스의 이름이다.
  • 파이썬의 비밀2: 클래스에는 __str__처럼 숨겨진 메서드가 존재한다. 이를 이용하여 파이썬 내부의 기능을 가로챌 수 있다.
  • 파이썬의 비밀3: 주피터노트북에서는 "오브젝트이름+엔터"를 쳐서 나오는 출력은 __repr__로 가로챌 수 있따. (주피터의 비밀)
  • 파이썬의 비밀4: 함수와 클래스는 숨겨진 메소드에 __call__을 가진 오브젝트일 뿐이다.
  • 파이썬의 비밀5: for문의 비밀 (iterable object, iterator, StopIteration Error)

클래스공부 9단계

예비학습 (변수의 범위)

커널을 재시작하고 아래를 관찰하자

예제1

- 관찰1: 함수내의변수 출력

def f():
    x=10 
    print(x)
f()
10

- 관찰2: 함수내의 변수가 없을 경우 출력이 되지 않음

def g():
    print(x)
g()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_2090322/258099874.py in <module>
----> 1 g()

/tmp/ipykernel_2090322/1814961753.py in g()
      1 def g():
----> 2     print(x)

NameError: name 'x' is not defined

- 관찰3: 동일한 이름의 변수가 globald에 있다면 함수내에 (local에) 그 이름의 변수가 선언되지 않아도 global의 변수를 빌려서 사용함

x=20 
def g():
    print(x)
g()
20

- 관찰4: f()가 실행되면서 x=10이 함수내에 (=local에) 실행되지만 이 결과가 외부의 x=20에 (=global에) 영향을 미치지는 못함

f()
10
x
20

예제2

(코드1)

x = 38 
def nextyear():
    y= x+1 
    print(x,y) 
nextyear()
38 39

(코드2)

x = 38 
def nextyear():
    y= x+1 
    print(x,y) 
    x= 0
nextyear()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
/tmp/ipykernel_2090322/3080202250.py in <module>
      4     print(x,y)
      5     x= 0
----> 6 nextyear()

/tmp/ipykernel_2090322/3080202250.py in nextyear()
      1 x = 38
      2 def nextyear():
----> 3     y= x+1
      4     print(x,y)
      5     x= 0

UnboundLocalError: local variable 'x' referenced before assignment

- 해석:

  • 잘못된 해석: 코드1은 실행되었고, 코드2에서 에러가 났다. 코드1과 2의 차이점은 x=0 이라는 코드가 코드2에 추가로 포함되어있다는 것이다. 따라서 x=0이 잘못된 코드이고 이걸 실행하는 과정에서 에러가 발생했다.
  • 올바른 해석: 코드1에서는 x가 global variable 이고 코드2에서는 x가 local variable이어서 생기는 문제

- 코드2의 올바른 수정

x = 38 
def nextyear():
    x= 0
    y= x+1 
    print(x,y) 
nextyear()
0 1

인스턴스 변수, 클래스 변수 (복습?)

- 예비학습이 주는 교훈

(원칙1) global 에서 정의된 이름은 local 에서 정의된 이름이 없을 경우 그를 대신할 수 있다 (local은 경우에 따라서 global에 있는 변수를 빌려 쓸 수 있다)

(원칙2) local과 global에서 같은 이름 'x'가 각각 정의되어 있는 경우? global의 변수와 local의 변수는 각각 따로 행동하며 서로 영향을 주지 않는다. (독립적이다)

  • 만약에 local이 global의 변수를 같이 쓰고 있었다고 할지라도, 추후 새롭게 local에 새롭게 같은 이름의 변수가 정의된다면 그 순간 local과 global의 변수를 각자 따로 행동하며 서로 영향을 주지 않는다. $\to$ 아래예제확인
x= 10 
def f():
    print(x)
f() # x를 빌려쓰는 신세
10
def f():
    x=20 # 이제 새롭게 x를 정의했으니까 
    print(x)
f() # 다른길을 간다.
20

- 이전에 공부하였던 인스턴스변수와 클래스변수 역시 비슷한 행동을 보인다.

class Moo:
    x=0 # 클래스변수
moo=Moo()

(관찰1)

Moo.x, moo.x
(0, 0)
  • moo.x는 사실 정의한적 없지만 Moo.x를 빌려쓰고 있다. (원칙1)

(관찰2)

Moo.x = 100 
Moo.x, moo.x
(100, 100)
  • Moo.x 를 변화시키면 moo.x 도 변화한다 (빌려쓰고 있는 것이니까, 원칙1의 재확인)

(관찰3)

moo.x = 200
Moo.x, moo.x
(100, 200)
  • moo.x=200을 하는 순간 새롭게 인스턴스변수를 선언한 셈이된다. 따라서 원칙2가 적용되어 이제부터 Moo.x 와 moo.x 는 서로 독립적으로 행동한다.

(관찰4)

Moo.x = - 99 
Moo.x, moo.x 
(-99, 200)
moo.x = 99
Moo.x, moo.x 
(-99, 99)
  • Moo.x 를 바꾼다고 해서 moo.x 가 영향받지 않고 moo.x 를 바꿔도 Moo.x 가 영향받지 않음 (완전히 독립, 원칙2의 재확인)

- 포인트: (1) 클래스변수와 인스턴스 변수의 구분 (2) 인스턴스 변수가 정의되지 않으면 클래스변수를 빌려쓸 수 있음 (3) 인스턴스변수와 클래스변수가 같은 이름으로 저장되어 있으면 각각 독립적으로 행동

인스턴스 메서드

- self 비밀: 사실 클래스에서 정의된 함수의 첫번째 인자의 이름이 꼭 self일 필요는 없다. (무엇으로 전달하든 클래스안에서 정의된 메소드의 첫번째 인자는 기본적으로 instance의 태명역할을 한다)

class Moo: 
    def __init__(abab):
        abab.name = 'guebin'
    def f(cdcd):
        print(cdcd.name)
moo=Moo()
moo.name
'guebin'
moo.f()
guebin

- 인스턴스 메서드: 위의 __init__f와 같이 첫번째 인자를 인스턴스의 태명으로 받는 함수를 인스턴스 메서드 (간단히 메서드) 라고 한다.

  • 인스턴스 메소드는self.f()와 같이 사용한다. 의미는 f(self) 이다.
moo.name = 'hynn'
moo.__init__() # 인스턴스메서드의 사용예시: self.__init__()의 꼴로 사용 
moo.name
'guebin'
moo.f() # 인스턴스메서드의 사용예시: self.__init__()의 꼴로 사용 
guebin

- 아래와 같이 사용할 수 없다.

클래스.메서드 할 수 없음

Moo.__init__()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_2090322/3136314191.py in <module>
----> 1 Moo.__init__()

TypeError: __init__() missing 1 required positional argument: 'abab'
Moo.f()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_2090322/2343752147.py in <module>
----> 1 Moo.f()

TypeError: f() missing 1 required positional argument: 'cdcd'

클러스 메서드

- 클래스 메서드: 함수의 첫 인자로 클래스오브젝트를 받는 메서드를 클래스메서드라고 한다.

- 목표: Moo.f()와 같은 형태로 사용할 수 있는 함수를 만들어 보자 -> 클래스메서드를 만들어보자!

class Moo: 
    def f(self):
        print("인스턴스 메서드")
moo=Moo()
moo.f()
인스턴스 메서드
Moo.f()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_2090322/2343752147.py in <module>
----> 1 Moo.f()

TypeError: f() missing 1 required positional argument: 'self'
class Moo: 
    @classmethod
    def f(cls): # 함수의 첫 인자로 클래스오브젝트를 받는다. cls는 클래스 Moo의 별명?이라고 생각하면 된다. 
        print("클래스 메서드")
moo=Moo()
Moo.f()
클래스 메서드
moo.f() # 인스턴스 메서드를 따로 정의한적은 없지만 같은 이름의 클래스 메서드가 있으므로 빌려와서 씀!
클래스 메서드

- 예제

class Moo:
    @classmethod
    def set_class_x(cls,value): # 클래스 메서드 
        cls.x = value # 클래스변수선언, note: Moo.x = value 와 같은 코드!
    def set_instance_x(self,value): # 인스턴스 메서드
        self.x = value # 인스턴스 변수선언
moo=Moo()
Moo.set_class_x(10) # 클래스메서드로 클래스변수에 10을 설정 
Moo.set_instance_x(10) # 클래스에서 인스턴스 메서드를 사용 -> 사용불가
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_2090322/4018557741.py in <module>
----> 1 Moo.set_instance_x(10) # 클래스에서 인스턴스 메서드를 사용 -> 사용불가

TypeError: set_instance_x() missing 1 required positional argument: 'value'
Moo.x, moo.x # 인스턴스변수는 따로 설정하지 않았지만 클래스 변수값을 빌려쓰고 있음
(10, 10)
moo.set_class_x(20) # 인스턴스에서는 원래 set_class_x 라는 메서드는 없지만 클래스에서 빌려씀 
Moo.x, moo.x # 현재 moo.x는 클래스 변수를 빌려쓰고 있는 상황이므로 같이 바뀜 
(20, 20)
moo.set_instance_x(-20) # 인스턴스에서 인스턴스 메서드를 사용하여 인스턴스 변수값을 -20으로 설정 
#-> 이때부터 인스턴스변수와 클래스 변수는 서로 독립적인 노선을 간다.
Moo.x, moo.x
(20, -20)
Moo.set_class_x(30) # 독립적인 노선을 가기로 했으므로 클래스변수만 30으로 바뀜 
Moo.x, moo.x
(30, -20)
moo.set_class_x(-40) # 여전히 인스턴스에서 set_class_x라는 함수는 없으므로 클래스메소드를 빌려쓰고 있음
Moo.x, moo.x
(-40, -20)

스태틱 메서드

- 스태틱 메서드: 첫 인자로 인스턴스와 클래스 모두 받지 않음. (클래스안에 정의되어 있지만 그냥 함수와 같음)

class Cals: 
    @staticmethod
    def add(a,b):
        return a+b
    @staticmethod
    def sub(a,b):
        return a-b 
fs = Cals()
fs.add(1,2)
3
fs.sub(1,2)
-1
  • fs는 그냥 함수들을 묶어놓은 느낌? 정리하게 편하게?

클래스공부 10단계

문자열 join

- 예제

lst = list('abcd')
lst
['a', 'b', 'c', 'd']
''.join(lst)
'abcd'

- 해설: ''는 string object 이고 .join는 string object에 소속된 메서드이다.

a=''
a.join(lst) # join(a,lst) 와 같은 효과 
'abcd'

- join의 간단한 사용방법

'-'.join(lst)
'a-b-c-d'

matplotlib

- 파이썬의 모든것은 객체이다: matplotlib의 다른 사용 (객체지향적 언어로 그림그리기!)

- 그림오브젝트 생성

fig = plt.figure() # 그림오브젝트 생성되고 fig라는 이름이 붙음
<Figure size 432x288 with 0 Axes>

- 그림오브젝트의 액시즈를 확인 -> 아무것도 없음

fig.axes
[]

- (0,0)자리에 (가로=1,세로=1) 크기의 액시즈를 넣어보자.

fig.add_axes([0,0,1,1]) 
<Axes:>
fig.axes
[<Axes:>]
fig

- (0,1.2) 위치에 (가로=1,세로=1) 크기의 액시즈 추가

fig.add_axes([0,1.2, 1,1]) 
fig

- (0.5,0.5) 위치에 (가로=1, 세로=1) 크기의 그림 추가

fig.add_axes([0.5,0.5, 1,1])
<Axes:>
fig

- fig의 세번째 액시즈에 접근

a3 = fig.axes[2] # 이것역시 오브젝트임 
a3
<Axes:>

- 액시즈의 메소드중에 plot이 있음 -> 이것으로 그림을 그려봄

a3.plot([1,2,3],[4,5,3],'--r')
[<matplotlib.lines.Line2D at 0x7f7c980c5ca0>]
fig

- 다시 세번째 축에 접근하여 다른그림을 그려보자.

fig.axes[-1].plot([1,2,3],[5,4,3],':o')
fig

- 이제 첫번째 축에 접근하여 새로운 그림을 그려보자.

fig.axes[0].plot([1,2,3],[4,1,4],'--b')
fig

- 클래스에 대한 이해가 없다면 위와 같은 그림을 그리기도 힘들고 코드를 해석하기도 힘듦

참조와 에일리어싱

- 아래의 코드를 관찰하자.

a=[1,2,3]
b=a
a=a+[4]

현재 a,b의 출력결과는?

a, b
([1, 2, 3, 4], [1, 2, 3])

- 이제 다시 아래의 코드를 관찰하자.

a=[1,2,3]
b=a
a.append(4)

현재 a,b의 출력결과는?

a,b
([1, 2, 3, 4], [1, 2, 3, 4])

- 아래의 코드를 다시 살펴보자.

a=[1,2,3]
b=a
a.append(4)

a,b라는 변수들은 메모리에 어떻게 저장이 되어있을까?

상상력을 조금 발휘하면 아래와 같이 여길 수 있다.

(1) 메모리는 변수를 담을 방이 여러개 있는 호텔이라고 생각하자.

(2) 아래를 실행하였을 경우

a=[1,2,3]
  • 메모리주소1에 존재하는 방을 a라고 하고, 그 방에 [1,2,3]을 넣는다.

(3) 아래를 실행하였을 경우

b=a
  • 메모리주소38에 존재하는 방을 b라고 하고, 그 방에 a를 넣어야하는데, a는 [1,2,3]이니까 [1,2,3]을 넣는다.

(4) 아래를 실행하면

a.append(4)
  • 방 a로가서 [1,2,3]을 [1,2,3,4]로 바꾼다.
  • 그리고 방 b에는 아무것도 하지 않는다.

- R에서는 맞는 비유인데, 파이썬은 적절하지 않은 비유이다.

id(a)
140173103370496
id(b)
140173103370496

실제로는 a,b가 저장된 메모리 주소가 동일함

- 파이썬에서는 아래가 더 적절한 비유이다.

(1) 메모리는 변수를 담을 방이 여러개 있는 호텔이라고 생각하자.

(2) 아래를 실행하였을 경우

a=[1,2,3]
  • 메모리주소139851743661952에서 [1,2,3]을 생성해요
  • 방 139851743661952의 방문에 a라는 포스트잇을 붙인다.
  • 앞으로 [1,2,3]에 접근하기 위해서는 여러 메모리방중에서 a라는 포스트잇이 붙은 방을 찾아가면 된다.

(3) 아래를 실행하였을 경우

b=a
  • a라는 포스트잇이 있는데, a라는 포스트잇이랑 b라는 포스트잇과 같은 효과를 주도록 한다.
  • 쉽게말하면 b라는 포스트잇을 방 139851743661952의 방문에 붙인다는 이야기.
  • 앞으로 [1,2,3]에 접근하기 위해서는 여러 메모리방중에서 a라는 포스트잇이 붙어 있거나 b라는 포스트잇이 붙어있는 방을 찾아가면 된다.

(4) 아래를 실행하면

a.append(4)
  • a라는 포스트잇이 붙어있는 방으로 가서, 그 내용물 append함수를 써서 4를 추가하라. 즉 내용물 [1,2,3]을 [1,2,3,4]로 바꾸라.
  • 같은방에 a,b라는 포스트잇이 모두 붙어있음. 따라서 b라는 포스트잇이 붙은 방을 찾아가서 내용물을 열어보면 [1,2,3,4]가 나온다.

- 결론: 파이썬의 모든것은 오브젝트이다. 그리고 모든 오브젝트는 메모리주소위에 올라간다. 하지만 그 메모리주소에 붙어있는 포스트잇이 하나라는 보장은 없다.