Class 고급

- 오브젝트

  • 클래스 오브젝트
  • 인스턴스 오브젝트

- 클래스 (=클래스 오브젝트)

- 인스턴스 (=인스턴스 오브젝트)

클래스 속성 vs 인스턴스 속성

예제1

class Testclass1: 
    x=0 
    y=0
    def my_print(self):
        self.x += 1 
        Testclass1.y +=1 
        print("현재 인스턴스에서 %s 회 출력" % self.x)
        print("전체 인스턴스에서 총 %s 회 출력" % self.y)
f=Testclass1
a=Testclass1()
b=f()
a.my_print()
현재 인스턴스에서 1 회 출력
전체 인스턴스에서 총 1 회 출력
b.my_print()
현재 인스턴스에서 1 회 출력
전체 인스턴스에서 총 2 회 출력
b.my_print()
현재 인스턴스에서 2 회 출력
전체 인스턴스에서 총 3 회 출력
a.my_print()
현재 인스턴스에서 2 회 출력
전체 인스턴스에서 총 4 회 출력
a.my_print()
현재 인스턴스에서 3 회 출력
전체 인스턴스에서 총 5 회 출력

- 신기한점: 각 인스턴스에서 instance.my_print()를 실행한 횟수를 서로 공유하는 듯 하다.

분석

- 코드를 시점별로 분석해보자.

- 분석을 위해서 커널을 재시작한다.

[시점1] : Testclass1를 선언하는 시점

class Testclass1: 
    x=0 
    y=0
    def my_print(self):
        self.x += 1 
        Testclass1.y +=1 
        print("현재 인스턴스에서 %s 회 출력" % self.x)
        print("전체 인스턴스에서 총 %s 회 출력" % self.y)
dir(Testclass1)
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'my_print',
 'x',
 'y']
# dir(b)
# 이 둘은 아직 존재 X

이 시점에는 Testclass1만이 존재한다. Testclass1를 바로 클래스 오브젝트라고 부름.

Testclass1.x
0
Testclass1.y
0

현재시점에서는 클래스 오브젝트의 수 1개, 인스턴스 오브젝트의 수 0개, 따라서 총 오브젝트 수는 1개임.

[시점2] 클래스에 별칭을 지정하는 시점

f=Testclass1
f.x
0
f.y
0
Testclass1.x
0
Testclass1.y
0

이 시점에서 클래스 오브젝트는 2개가 있는 것 처럼 보인다.

- 그렇다면 이 2개의 클래스 오브젝트는 컴퓨터의 어딘가에 저장이 되어 있을 것이다.

- 구체적으로는 메모리에 저장되어있을것.

- 2개의 클래스오브젝트는 서로 다른 메모리 공간에 저장되어 있을것이다.

- 진짜인가? 확인해보자. id()는 오브젝트(클래스 오브젝트, 인스턴스 오브젝트)가 저장된 메모리 주소를 확인하는 명령어이다.

id(f)
2274923570048

f라는 오브젝트는 93967322676384 메모리에 저장되어 있다.

id(Testclass1)
2274923570048

- 어? 그런데 Testclass1의 오브젝트 역시 93967322676384 메모리에 저장되어 있다.

- 추론: 사실 93967322676384라는 메모리공간에 저장된 어떠한 것은 동일한데, 그것을 어떤사람은 Testclass1 이라고 부르고 어떤사람은 f라고 부른다.

- 이는 마치 별명이랑 비슷하다. 나라는 오브젝트를 어떤사람은 최규빈이라고 부르고, 어떤사람은 팬더라고 부른다. 부르는 이름이 2개라고 해서 나라는 오브젝트가 2개가 있는것은 아니다.

- 결국 이 시점에서 클래스 오브젝트의 수는 여전히 1개라고 볼 수 있다. (인스턴스 오브젝트의 수는 0개)

[시점3] : 클래스 오브젝트로부터 인스턴스 오브젝트를 만드는 시점

a=Testclass1() # 인스턴스 object 만듦
b=f() # 인스턴스 object 만듦
id(Testclass1),id(f),id(a),id(b)
(2274923570048, 2274923570048, 2274938585920, 2274938586544)

이 순간에는 클래스 오브젝트 1개, 인스턴스 오브젝트 2개 존재한다. 즉 총 3개의 오브젝트가 존재한다.

- 메모리주소 93967322676384 에 존재하는 오브젝트는 클래스 오브젝트이며 Testclass1 또는 f 라고 불린다.

- 메모리주소 139694857660688 에 존재하는 오브젝트는 인스턴스 오브젝트이며 a라고 불린다.

- 메모리주소 139694848860656 에 존재하는 오브젝트는 인스턴스 오브젝트이며 b라고 불린다.


Testclass1.x, Testclass1.y
(0, 0)
f.x,f.y
(0, 0)

a.x,a.y
(0, 0)
b.x,b.y
(0, 0)

[시점4]

a.my_print()
현재 인스턴스에서 1 회 출력
전체 인스턴스에서 총 1 회 출력
(f.x,f.y),(a.x,a.y),(b.x,b.y)
((0, 1), (1, 1), (0, 1))

- 특징

  • a.my_print()를 실행하면 a.x 의 값이 1이 증가한다.
  • a.my_print()를 실행하면 f.y, a.y, b.y 의 값이 동시에 1이 증가한다. (공유가 되는 느낌)

[시점5]

b.my_print()
현재 인스턴스에서 1 회 출력
전체 인스턴스에서 총 2 회 출력
(f.x,f.y),(a.x,a.y),(b.x,b.y)
((0, 2), (1, 2), (1, 2))

[시점6]

b.my_print()
현재 인스턴스에서 2 회 출력
전체 인스턴스에서 총 3 회 출력
(f.x,f.y),(a.x,a.y),(b.x,b.y)
((0, 3), (1, 3), (2, 3))

[시점7]

a.my_print()
현재 인스턴스에서 2 회 출력
전체 인스턴스에서 총 4 회 출력
(f.x,f.y),(a.x,a.y),(b.x,b.y)
((0, 4), (2, 4), (2, 4))

[시점8]

a.my_print()
현재 인스턴스에서 3 회 출력
전체 인스턴스에서 총 5 회 출력
(f.x,f.y),(a.x,a.y),(b.x,b.y)
((0, 5), (3, 5), (2, 5))

예제2

- 아래처럼 코드를 바꿔도 잘 동작할것 같다.

class Testclass2: 
    def __init__(self): # 클래스가 생성되는 시점에서는 실행되지 않고, 인스턴스가 생성되는 시점에서 실행될 것
        self.x=0
        self.y=0
    def my_print(self):
        self.x += 1 
        Testclass2.y +=1 
        print("현재 인스턴스에서 %s 회 출력" % self.x)
        print("전체 인스턴스에서 총 %s 회 출력" % self.y)
c=Testclass2()
# 인스턴스 object 생성
 

왜 에러가 나는가?

dir(Testclass2)
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'my_print']
dir(Testclass1)
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'my_print',
 'x',
 'y']

- 관찰1: Testclass2에서는 Testclass1과는 다르게 x,y가 없다.

dir(c)
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'my_print',
 'x',
 'y']

관찰2: 그런데 c라는 인스턴스 오브젝트에서는 x,y가 있다.

- 추론: __init__함수는 클래스 오브젝트가 만들어지는 시점에서는 실행되지 않고, 인스텐스 오브젝트가 만들어지는 시점에 실행된다.

- 결국 __init__ 함수의 역할은 클래스 오브젝트에서 인스턴스 오브젝트를 만든후에 초기화를 위해서 실행하는 어떠한 일련의 명령들을 묶어놓은 것에 불과하다.

즉 위의 코드는 굳이 따지면 아래를 실행한 것과 동일하다.

class Testclass2: 
#     def __init__(self):
#         self.x=0
#         self.y=0
    def my_print(self):
        self.x += 1 
        Testclass2.y +=1 
        print("현재 인스턴스에서 %s 회 출력" % self.x)
        print("전체 인스턴스에서 총 %s 회 출력" % self.y)
c=Testclass2()
c.x=0
c.y=0

- 이 상황에서

c.my_print()

를 실행하면

c.x += 1 
Testclass2.y +=1 
print("현재 인스턴스에서 %s 회 출력" % c.x)
print("전체 인스턴스에서 총 %s 회 출력" % c.y)

이 실행되는데, 이때 Testclass2.y 이 정의되어 있지 않으므로

Testclass2.y +=1

에서 에러가 난다.

예제 3

- 그렇다면 아래와 같이 수정하면 어떨까?

class Testclass3: 
    def __init__(self):
        self.x=0
        Testclass3.y=0
    def my_print(self):
        self.x += 1 
        Testclass3.y +=1 
        print("현재 인스턴스에서 %s 회 출력" % self.x)
        print("전체 인스턴스에서 총 %s 회 출력" % self.y)
#     def __init__(self): # 클래스가 생성되는 시점에서는 실행되지 않고, 인스턴스가 생성되는 시점에서 실행될 것
#         self.x=0
#         self.y=0
#     def my_print(self):
#         self.x += 1 
#         Testclass2.y +=1 
#         print("현재 인스턴스에서 %s 회 출력" % self.x)
#         print("전체 인스턴스에서 총 %s 회 출력" % self.y)
a=Testclass3()
b=Testclass3()
a.my_print()
현재 인스턴스에서 1 회 출력
전체 인스턴스에서 총 1 회 출력
b.my_print()
현재 인스턴스에서 1 회 출력
전체 인스턴스에서 총 2 회 출력
a.my_print()
현재 인스턴스에서 2 회 출력
전체 인스턴스에서 총 3 회 출력
a.my_print()
현재 인스턴스에서 3 회 출력
전체 인스턴스에서 총 4 회 출력
b.my_print()
현재 인스턴스에서 2 회 출력
전체 인스턴스에서 총 5 회 출력
b.my_print()
현재 인스턴스에서 3 회 출력
전체 인스턴스에서 총 6 회 출력

Testclass1과 동일한 기능이 수행되는것 같다.

- 그런데 조금만 생각해보면 엉터리라는 것을 알 수 있다. 아래의 코드를 관찰하여보자.

class Testclass3: 
    def __init__(self):
        self.x=0
        Testclass3.y=0
    def my_print(self):
        self.x += 1 
        Testclass3.y +=1 
        print("현재 인스턴스에서 %s 회 출력" % self.x)
        print("전체 인스턴스에서 총 %s 회 출력" % self.y)
        
a=Testclass3()
a.my_print()
a.my_print()
b=Testclass3() # 초기화
b.my_print()
현재 인스턴스에서 1 회 출력
전체 인스턴스에서 총 1 회 출력
현재 인스턴스에서 2 회 출력
전체 인스턴스에서 총 2 회 출력
현재 인스턴스에서 1 회 출력
전체 인스턴스에서 총 1 회 출력

- Testclass3는 인스턴스를 생성할때마다 y=0이 설정된다. 그래서

b=Testclass3()

이 시점에서 의도하지 않게 '전체 인스턴스에서 출력된 횟수'를 의미하는 y가 초기화되었다.

- 코드는 엉터리이지만, Testclass3은 의외로 분석할만한 가치가 있다. 특히 위의 실행결과를 시점별로 Testclass1과 비교해보면 재미있다.

- Testclass1

### Testclass1 
## 시점1: 클래스 오브젝트 생성 
class Testclass1: 
    x=0 
    y=0
    def my_print(self):
        self.x += 1 
        Testclass1.y +=1 
        print("현재 인스턴스에서 %s 회 출력" % self.x)
        print("전체 인스턴스에서 총 %s 회 출력" % self.y)
## 시점2: 인스턴스 오브젝트 a를 생성 
a=Testclass1()
## 시점3: a에서 메소드 실행 
a.my_print()
## 시점4: a에서 메소드를 한번 더 실행 
a.my_print()
## 시점5: 인스턴스 오브젝트 b를 생성
b=Testclass1()
## 시점6: b에서 메소드를 실행
b.my_print()
현재 인스턴스에서 1 회 출력
전체 인스턴스에서 총 1 회 출력
현재 인스턴스에서 2 회 출력
전체 인스턴스에서 총 2 회 출력
현재 인스턴스에서 1 회 출력
전체 인스턴스에서 총 3 회 출력
시점1 시점2 시점3 시점4 시점5 시점6
Testclass1.x 0 0 0 0 0 0
Testclass1.y 0 0 1 2 2 3
a.x 값없음 0 1 2 2 2
a.y 값없음 0 1 2 2 3
b.x 값없음 값없음 값없음 값없음 0 1
b.y 값없음 값없음 값없음 값없음 2 3

Testclass3

#### Testclass3
## 시점1: 클래스 오브젝트 생성 
class Testclass3: 
    def __init__(self):
        self.x=0
        Testclass3.y=0
    def my_print(self):
        self.x += 1 
        Testclass3.y +=1 
        print("현재 인스턴스에서 %s 회 출력" % self.x)
        print("전체 인스턴스에서 총 %s 회 출력" % self.y)
## 시점2: 인스턴스 오브젝트 a를 생성 
a=Testclass3()
## 시점3: a에서 메소드 실행 
a.my_print()
## 시점4: a에서 메소드를 한번 더 실행 
a.my_print()
## 시점5: 인스턴스 오브젝트 b를 생성
b=Testclass3()
## 시점6: b에서 메소드를 실행
b.my_print()
현재 인스턴스에서 1 회 출력
전체 인스턴스에서 총 1 회 출력
현재 인스턴스에서 2 회 출력
전체 인스턴스에서 총 2 회 출력
현재 인스턴스에서 1 회 출력
전체 인스턴스에서 총 1 회 출력
시점1 시점2 시점3 시점4 시점5 시점6
Testclass3.x 값없음 값없음 값없음 값없음 값없음 값없음
Testclass3.y 값없음 0 1 2 0 1
a.x 값없음 0 1 2 2 2
a.y 값없음 0 1 2 0 1
b.x 값없음 값없음 값없음 값없음 0 1
b.y 값없음 값없음 값없음 값없음 0 1

Testclass3.y가 업데이트 되면 a.y, b.y도 자동으로 업데이트 된다.

네임스페이스

예제1

class Testclass1: 
    x=0
Testclass1.x
0
a=Testclass1()
# 인스턴스
a.x
0

Testclass1.x를 수정하면 a.x가 강제로 수정된다.

Testclass1.x=100
a.x
100

- a.x를 수정한다고 하여 Testclass1.x가 강제로 수정되는 것은 아님

a.x=200
Testclass1.x
100
a.x
200

- 이건 왜이러지?

Testclass1.x=300
a.x
200

- 아래의 상황과 비슷하다.

x=39
def nextyear():
    y=x+1
    print(x,y)
nextyear()
39 40
x
39
# x=39
# def nextyear():
#     y=x+1
#     print(x,y)
#     x=0
# nextyear()

- [code2]와 [code1]의 차이점은 x=0이라는 코드가 추가로 포함되었는지 유무다.

- code1에서는 x는 global variable, code2에서는 x가 local variable 이라서 생기는 문제점이다.

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

다시 우리의 예제로 돌아오자.

### 시점1
class Testclass1: 
    x=0
### 시점2
a=Testclass1()
### 시점3
Testclass1.x=100
### 시점4
a.x=200  ` 순간 a.x의 속성이 instance로 변함`
### 시점5
Testclass1.x=300
시점1 시점2 시점3 시점4 시점5
Testclass1.x 0 0 100 100 300
a.x 값없음 0 100 200 200
a.x의 속성 - class class instance instance

a.x가 클래스로부터 물려받은 속성인지 (그래서 클래스와 연결되어있는지) 아니면 instance가 독자적으로 가지고 있는 속성인지 어떻게 알 수 있을까?

class Testclass1: 
    x=0
print('시점1',Testclass1.x)
### 시점2
a=Testclass1()
print('시점2',Testclass1.x,a.x,a.__dict__)
### 시점3
Testclass1.x=100
print('시점3',Testclass1.x,a.x,a.__dict__)
### 시점4
a.x=200
print('시점4',Testclass1.x,a.x,a.__dict__)
# 이젠 독자성을 지님
### 시점5
Testclass1.x=300
print('시점5',Testclass1.x,a.x,a.__dict__)
시점1 0
시점2 0 0 {}
시점3 100 100 {}
시점4 100 200 {'x': 200}
시점5 300 200 {'x': 200}

참고 : _dict__ 용도? 클래스 객체의 속성 정보를 확인하기 위해 사용. 객체가 가진 여러가지 속성들을 딕셔너리 형태로 편하게 확인할 수 있다.

예제2

x=11 ## 전역변수 ... A

def f():
    x=22 ## 함수 f안에 설정된 지역변수 
    print(x) ## 전역에 x=11 있지만 함수안에 x=22가 있으므로 x=22를 사용. --> 22출력됨 

def g():
    print(x) ## 함수 g안에 x를 찾아봤는데 없음 --> 전역에서 x를 찾음 --> x=11 --> 11출력함. 

class Testclass2: 
    x=33 ## 클래스 변수 ... B
    def m1(self):
        x=44 ## 메소드 변수 ... C
    def m2(self):
        self.x=44 ## 인스턴스 변수 ... D 

- 결과를 관찰하고 해석해보자.

print(x)
11

Note: 전역변수 출력

f()
22

Note: $f$ 에서 설정된 지역변수 22가 출력됨

x
11

Note: $f$ 내의 지역변수를 사용하여도 전역변수는 변하지 않음. (함수내부에서 선언된 x=22는 함수외부에 영향을 주지못함)

g()
11

Note: g에서 설정된 지역변수가 따로 없으므로 전역변수 출력

x,Testclass2.x
(11, 33)

Note: 전역변수 x와 클래스오브젝트에 설정된 변수 x

a=Testclass2()
(x,Testclass2.x,a.x),a.__dict__
((11, 33, 33), {})

Note: 전역변수, 클래스 오브젝트내의 변수, 인스턴스내의 변수(a.__dict__의 결과로 보아 인스턴스내의 변수는 클래스 오브젝트내의 변수를 빌려쓰고 있다. ).

Testclass2.x=200
(x,Testclass2.x,a.x),a.__dict__
((11, 200, 200), {})

Note: 클래스오브젝트에서 변수를 고치면 인스턴스에 영향을 미침, 아직 인스턴스가 독자성을 갖지 않음

a.m1()
(x,Testclass2.x,a.x),a.__dict__
((11, 200, 200), {})

Note: 메소드 m1내에서 선언된 x=44라는 선언은 아무것도 변화시킬수 없음.

a.m2()
(x,Testclass2.x,a.x),a.__dict__ # 독자성을 갖는 것
((11, 200, 44), {'x': 44})

Note: 메소드 m2에 있는 self.x는 결국 a.x라는 의미이고, 이 선언은 클래스오브젝트 내의 변수와 독립적으로 인스턴스오브젝트 내에서 통용되는 변수를 선언하는 것임. 이 선언의 결과는 a.__dict__의 출력결과에서도 확인가능.

Testclass2.x=300
(x,Testclass2.x,a.x),a.__dict__
((11, 300, 44), {'x': 44})

Note: 이제는 a.x와 Testclass2.x 는 분리된 상태이므로, Testclass2.x의 값을 바꾸어도 a.x에는 값의 변화가 없음.

전역변수(A), 클래스 변수(B), 메소드 변수(C), 인스턴스 변수(D)

A>B>D>C

연산자 오버로딩

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

1+1
2

- 생각해보니까 1은 int class 에서 생성된 인스턴스이다.

- 코드를 관찰하니 instance와 instance를 +라는 연산이 연결하는 형태임.

class Student: 
    def __init__(self,age=20.0,semester=1):
        self.age=age
        self.semester=semester
    def __add__(self,val):
        # val==0: 휴학 
        # val==1: 등록
        if val==0: 
            self.age=self.age +0.5 
        elif val==1: 
            self.age=self.age+0.5
            self.semester=self.semester+1
        return self
    def __repr__(self):
        return '나이: %s \n학기: %s' % (self.age,self.semester)
guebin=Student()
guebin.age
20.0
guebin.semester
1
guebin
나이: 20.0 
학기: 1
guebin+1
나이: 20.5 
학기: 2
guebin+0
나이: 21.0 
학기: 2
guebin+0+0+0+0+1+0+1
나이: 24.5 
학기: 4

- 연산자 오버로드 핵심아이디어

  • 클래스가 일반적인 파이썬 연산을 재정의하는 것, 즉 가로채는 것이라고 생각해도 됨
  • 여기에서 연산은 단순히 덧셈, 뺄셈을 의미하는게 아니라, print(), +, [0](인덱싱) 와 같은 파이썬 내장문법을 모두 포괄하는 개념이라 이해하는 것이 옳다.
class Student2(Student):
    def __getitem__(self,index):
        return [self.age,self.semester][index]
hynn=Student2()
hynn+1+1+0+0
나이: 22.0 
학기: 3
hynn[0]
22.0
hynn[1]
3
hynn[:]
[22.0, 3]

도움말 작성방법

- 넘파이의 경우 아래와 같이 도움말이 잘 작성되어 있다.

import numpy as np
a=np.array([1,2,3])
# a?

- 하지만 우리는?

hynn?
Type:        Student2
String form:
나이: 22.0 
학기: 3
Docstring:   <no docstring>

- 우리도 도움말을 작성하고 싶다.

class Student2(Student):
    '''
    Student2는 Student의 개선 
    
    # Student 클래스의 기능 
    1. 출력기능 (__repr__)
    2. 연산기능 (__add__): 학기와 나이를 카운트 
    Examples
    --------
    >>> hynn=Student2()
    >>> hynn+1
    나이: 20.5 
    학기: 2
    
    # Student2에서 추가된 기능 
    1. 인덱싱
    '''
    def __getitem__(self,index):
        return [self.age,self.semester][index]
hynn=Student2()
hynn?
Type:        Student2
String form:
나이: 20.0 
학기: 1
Docstring:  
Student2는 Student의 개선 

# Student 클래스의 기능 
1. 출력기능 (__repr__)
2. 연산기능 (__add__): 학기와 나이를 카운트 
Examples
--------
>>> hynn=Student2()
>>> hynn+1
나이: 20.5 
학기: 2

# Student2에서 추가된 기능 
1. 인덱싱
hynn=Student2(21,1)
hynn
나이: 21 
학기: 1
hynn?
Type:        Student2
String form:
나이: 21 
학기: 1
Docstring:  
Student2는 Student의 개선 

# Student 클래스의 기능 
1. 출력기능 (__repr__)
2. 연산기능 (__add__): 학기와 나이를 카운트 
Examples
--------
>>> hynn=Student2()
>>> hynn+1
나이: 20.5 
학기: 2

# Student2에서 추가된 기능 
1. 인덱싱

self에 대한 진실

사실 이름이 self가 아니어도 된다.

-클래스의 첫번째 인자는 굳이 self가 아니라 임의로 a라고 명명해도 됨

class MooYaHo: 
    '''
    201821994
    '''
    def __init__(a):
        a.text='mooyaho'
moo1=MooYaHo()
moo1?
Type:        MooYaHo
String form: <__main__.MooYaHo object at 0x00000211AF1B7C10>
Docstring:   201821994
moo1=MooYaHo()
moo1.text
'mooyaho'

그런데 self를 많이 쓴다.