필요성
처음 배울 때는 내가 그냥 Class 안쓰고 OOP를 안쓰면 되는 것 아닌가 싶어서 대충 공부하고 넘어갔지만
현업을 하면 할 수록 혹은 open source나 논문등을 읽으면 읽을 수록 공부의 필요성을 느껴서 정리하였다
목적
우리가 OOP 즉, Object-Oriented Programming (객체지향프로그래밍)을 하는 이유는
OOP가 가지고 있는 장점들 때문이다
대표적으로
만들어 놓은 코드를 재사용한다 던지
기능에 따라 분류하여 구현하여서 협업에 있어서 코드 이해 없이 사용할 수 있다 던지
Python을 사용하는 곳이라면 반드시 필요하다
OOP란?
OOP의 뜻은 Object-Oriented Programming (객체지향프로그래밍)이다
여기서 빼놓을 수 없는 단어는 "객체"이다
객체?
객체란: 속성(Attribute)와 행동(Action)을 가지는 밑에 예시의 붕어빵 이라 생각하면 된다
OOP는 이러한 객체의 개념을 프로그래밍으로 표현한다
객체의 속성(Attribute)은 OOP의 변수(Variable)과 같고
객체의 행동(Action)은 OOP의 함수(Method)와 같다
Class / Instance 란?
Class란 OOP의 설계도의 역할을 한다
즉 Class를 붕어빵 틀이라고 생각을 하면
이 틀을 통해 다양한 종류의 붕어빵 (Instance)를 만들어 낼 수 있다
코드로 예시를 들어보면 다음과 같은 예시를 들 수 있다
Class를 선언(붕어빵 틀을 만들어 주는 부분)
#!/usr/bin/env python3
class MyClass:
print("class가 만들어졌습니다")
def __init__(self):
print("Instance가 생성되었습니다")
pass
>>> class가 만들어졌습니다
Instance를 여러개 만들어보기
Instance1 = MyClass()
Instance2 = MyClass()
Instance3 = MyClass()
>>>Instance가 생성되었습니다
>>>Instance가 생성되었습니다
>>>Instance가 생성되었습니다
이런식으로 붕어빵을 찍어낸다고 생각할 수 있다
Class 선언방법
class는 다음과 같이 둘 중에 세가지 방법으로 선언을 하는데 (일단은 세개만이라 했습니다)
# 1 옛날식 표현
class MyClass(object):
pass
# 2 python3 식 표현
class MyClass:
pass
# 3 inheritance
class Parent_class:
pass
class MyClass(Parent_class):
pass
3번은 나중에 설명을 하고 1,2를 먼저 설명하면
제일 먼저 가운데 이름 MyClass 부분은 class의 이름을 정하는 부분인데
우리가 함수/변수를 선언 할때 했던 작명방식 snake_case ex) my_score= 20 이 아닌
대소문자를 통해 구분하는 CamerlCase로 이름을 만든다
(또한 첫문자는 대문자를 적는게 좋다 ex) class stool: 이 아닌 class Stool: )
파이썬에서 _ _의 의미
위에서 class를 구현할 때 다음과 같이
class MyClass:
def __init__(self):
pass
__init__(self)라는 것을 썼다
또한 다른 코드를 보더라도 class를 만들 때 대부분 init을 사용하는데 이것의 역할은 무엇일까?
제일 먼저 _ _의 의미는 이미 예약된 예약어라는 뜻이다
(즉, 자동으로 돌아가는 코드를 건드리고 싶다 라는 느낌으로 본인은 사용하고 있다)
우리는 _ _를 사용하므로써 특수한 예약 함수나 변수 그리고 함수명 변경(맨글링)으로 사용한다
__init__(self,arg1,arg2,.....):
__init__은 객체 초기화 예약함수 로써 속성 정보등을 초기화할 때 사용한다
다음과 같이 작성을 할 때
class MyClass:
def __init__(self,name,num):
self.name = name
self.student_num = num
Instance를 생성할 때 필요한 parameter를 자동으로 class 내부에 저장을 해준다고 생각하면 된다
즉, self.name 은 내부의 name이라는 변수에 저장을 한다는 뜻이고
name은 외부에서 입력받은 값이다
하지만 위와 같이 작성을 하면 나중에 협업자가 생성자를 만들때
init 함수의 내부를 보고 사용처를 알아서 이에 맞게 value를 할당해야하는 번거로움이 있기 때문에 위와 같이 적기보다는
class MyClass:
def __init__(self, name:str, num:int):
self.name = name
self.student_num = num
이렇게 적어주는 것이 좋다
그러면 instance는 다음과 같이
Instance1 = MyClass("철수",20230111)
Instance2 = MyClass("영희",20230122)
Instance3 = MyClass("바둑",20230333)
생성을 할 수 있고 만약에 선언한 argument와 갯수가 다를 시
Instance1 = MyClass()
#TypeError: __init__() missing 2 required positional arguments: 'name' and 'num'
Instance1 = MyClass("철수")
#TypeError: __init__() missing 1 required positional argument: 'num'
Type 에러가 나온다
하지만 우리는 이렇게 에러를 띄우는 것이 아닌 가끔은 default값을 지정하고 싶어한다
이럴때 위에 init 부분에 default 값을 채워 주면 에러가 나오지 않는다
class MyClass:
def __init__(self,name:str = "anonymous" ,num:int = 0000000):
self.name = name
self.student_num = num
Instance1 = MyClass()
Instance1.name
'anonymous'
그런데 나는 별로 뭔 값을 넣어줄지 모르겠다 하면 "None"을 default로 지정해주면 된다
class MyClass:
def __init__(self,name:str = None ,num:int = None):
self.name = name
self.student_num = num
Instance1 = MyClass()
그렇다면 argument의 역할을 알겠는데 사용할 때 무시했던 self는 무엇일까??
Self
self는 생성된 instance 자기 자신을 나타낸다
(또한 이 self가 있어야지 class 함수로 인정해 준다)
__main__ , __add___ , __str__, __eq__
__init__ 이외에도 다양한 예약함수들이 있는데 아래의 링크에서 기능확인이 가능하다
https://corikachu.github.io/articles/python/python-magic-method
파이썬 더블 언더스코어: Magic Method | Corikachu
파이썬에서 __를 달고있는 메소드들에 대해서 살펴봅시다.
corikachu.github.io
예를 들어 __str__를 사용해보자
class 에 __str__ 함수를 선언해주면
class를 출력해줄 수가 있다
즉, class 가 String처럼 쓰일 때 어떻게 사용될 것인지를 지정해주는 것이다
class MyClass:
def __init__(self,name:str = "anonymous" ,num:int = 0000000):
self.name = name
self.student_num = num
Instance1 = MyClass()
print(Instance1)
>>> <__main__.MyClass object at 0x7f6d687f6100>
class MyClass:
def __init__(self,name:str = "anonymous" ,num:int = 0000000):
self.name = name
self.student_num = num
def __str__(self):
return "This is class of containing students information"
Instance1 = MyClass()
print(Instance1)
>>> This is class of containing students information
위에 블로그 설명에 따르면 __str__(self)는 비공식적인 문자열이지만 객체를 이해하기 쉽게 표현할 수 있는 문자열이라고 한다
그렇다면 우리가 제일 많이 사용하는 더블 언더스코어를 알아보자
if __name__=="__main__":
-------------------------------
if __name__=="__main__":
함수이름 ()
-------------------------------
우리는 class를 사용할 때 저 위에 문장을 습관 처럼 사용한다
하지만 이것을 왜 사용하는지에 대한 대답을 잘 하지 못하기에 역할에 대해서 알아보자
__name__ 은 해당 파이썬 파일의 이름(모듈의 이름) 이 담긴다
예를 들어 function.py 파일이면 __name__ 은 Function이다
하지만
__name__변수에는 파일안에 있는 해당 함수를 실행 시키면 __name__의 변수에는 모듈, 파일 이름이 담기는 것이 아닌 __main__이라는 값이 지정된다
아래의 코드 예시를 살펴보자
다음과 같은 파일 구조를 가진다
/home/***/STUDY_MYSELF/
├── test1.py
└── test2.py
test1.py는
from test2 import test2_function
def test1_function():
print("test1_function is react")
if __name__ =="__main__":
print("test1_function if sentence react")
print("\n terminal run test1.py \n")
print("######case test 1#######")
test1_function()
print("\n")
print("######case test 2#######")
test2_function()
test2.py는
def test2_function():
print("test2_function is react\n")
if __name__ =="__main__":
print("test2_function if sentence react")
test2_function()
이라고 할때
(test1,py 와 같이 다른 파일에서 함수나 클래스를 가져다 쓰고 싶은 경우
from <파일이름> import <가져올 함수/클래스 이름> )
test1.py를 실행해보면 출력이
test2_function is react
terminal run test1.py
######case test 1#######
test1_function is react
test1_function if sentence react
######case test 2#######
test2_function is react
다음과 같이 나온다
test1의 if __name__문은 실행된 반면에
test2의 if __name__문은 실행되지 않았다
반면에 test2.py을 실행하면
test2_function is react
test2_function if sentence react
정상적으로 if문이 실행되는 것을 확인할 수 있다
그러면 이것이 왜 문제일까???
=> if __name == '__main__'을 쓰지 않으면 예상치 못한 결과값을 얻을 수 있다
위에 예시로 추가 설명을 해보자
출력에 맨 처음 부분을 보면 import를 할때 test2_function()이 실행이 되어
test2_function is react라는 문구가 출력된 것을 볼 수 있다
즉, import 해 올때 모듈 안에 있던 내용 (함수 정의, 함수를 실행 시키는 파라미터)등이 실행되는 것을 막기 위해서
action 부분을 if __name == "__main__"에 정의를 해주어서 예상치 못한 출력이나 행동을 예방한다
Class 속성값 변경
class의 속성값을 변경 혹은 불러오려면 다음 두가지 방법이 있다
그 값을 그대로 이용하는 경우 (비추)
class MyClass:
def __init__(self,name:str = "anonymous" ,num:int = 0000000):
self.name = name
self.student_num = num
Instance1 = MyClass()
print(Instance1.name)
Instance1.name = "ben"
print(Instance1.name)
class 내부함수를 이용하는 경우 (get / set 함수 선언)
class MyClass:
def __init__(self,name:str = "anonymous" ,num:int = 0000000):
self.name = name
self.student_num = num
def set_name(self, new_name:str):
self.name = new_name
def get_name(self):
return self.name
Instance2 = MyClass()
print(Instance2.get_name())
Instance2.set_name("ben")
print(Instance2.get_name())
위에서 언급했듯이 다음과 같이 내부함수를 이용하는 것을 추천하다
이는 OOP의 개념과 캡슐화를 적용한 방법이며 이렇게 해야하는 이유는
private으로 선언되었을 경우 문제가 발생하기 때문이다
Class의 private 변수
class에 private 변수를 선언하고 싶으면
초기화 하는 부분 __init__ 부분에 변수 앞에 _ _를 붙여주면 된다
예시
#!/usr/bin/env python3
class MyClass:
def __init__(self,name:str = "anonymous" ,num:int = 0000000):
self.name = name
self.__student_num = num
def get_num(self):
return self.__student_num
Instance1 = MyClass("홍길동",20231111)
print(Instance1.get_num())
print(Instance1.__student_num)
>>> 20231111
>>> AttributeError: 'MyClass' object has no attribute '__student_num'
위와 같이 내장함수로는 접근이 가능하지만 외부에서 접근이 막아진다
Class의 Inheritance (상속)
부모 클래스로 부터 속성과 Method를 물려받은 자식 클래스를 생성하는것
class ParentClass:
def __init__(self,name:str = None ,num:int = None):
self.name = name
self.student_num = num
def get_name(self):
return self.name
def __str__(self):
return "This is parent class"
class ChildClass(ParentClass):
def __str__(self):
return "This is child class"
Instance1 = ParentClass("어머니",20231111)
Instance2 = ChildClass("아들",20232222)
print(Instance1," and name attribute is : ",Instance1.get_name())
print(Instance2," and name attribute is : ",Instance2.get_name())
>>> This is parent class and name attribute is : 어머니
>>> This is child class and name attribute is : 아들
다음과 같이 부모의 유전자를 닮게
부모의 선언 방법(init)이나 부모의 함수등을 전부 물려받을 수 있다
또한 자식 class에서 Overwritting을 하여 물려받은 함수를 자기에 맞게 바꿀 수도 있다
이렇게 바꿀 수 있어서 같은 함수 이름이여도 클래스에 따라 다르게 작동하는 것을
다형성 (Polymorphism)이라고 한다
그렇다면 만약에 부모의 속성을 부르고 싶으면 어떻게 할까??
다음의 예시코드를 보자
class ParentClass:
def __init__(self,name:str = None ,num:int = None):
self.name = name
self.student_num = num
def get_name(self):
return self.name
def __str__(self):
return "This is parent class"
class ChildClass(ParentClass):
def __init__(self,name,num,gender):
super().__init__(name,num)
self.gender = gender
def __str__(self):
return "This is child class"
def print_attribute(self):
print("name : %s, number : %d , gender : %s" % (self.name, self.student_num, self.gender))
Instance1 = ParentClass("어머니",20231111)
Instance2 = ChildClass("아들",20232222,"남자")
print(Instance1," and name attribute is : ",Instance1.get_name())
print(Instance2," and name attribute is : ",Instance2.get_name())
Instance2.print_attribute()
>>> This is parent class and name attribute is : 어머니
>>> This is child class and name attribute is : 아들
>>> name : 아들, number : 20232222 , gender : 남자
이렇게 자식도 "super()" method를 이용하여서 부모의 객체를 부를 수 있다
사용할때는 super() = 부모클래스 이름 이라고 생각하고 코드를 작성하면 된다
추가적으로 class에는 Visibility라는 속성이 있는데
이는 객체 안에 모든 변수를 아무한테나 다 보여주는 것이 아니고
코드 또한 전부 읽을 필요없이 사용하게 하는 것이다
다양한 캡슐화 방법과 주석, 그리고 argument를 그냥 name 이라 적는 것이 아닌 name : str 와 같이 적는 방법 등으로
가시성을 적용한다
Decorator
Decorator 관련해서는 아래에 정리를 했으니 포스팅을 참고하면 된다
Python Decorator 에 관하여
Decorate 처음 논문을 구현한 코드 등을 보거나 깃허브를 둘러보면 이 Decorate 때문에 당황한 경험이 있을 것이다 쉽게 말 그대로 코드를 꾸밀 수 있는 기능으로 생각하면 편하다 (@를 사용한다) 아
aisj.tistory.com
Decorator를 왜 뜬금없이 언급했냐???
가끔 @property @classmethod @staticmethod 등이 나오기 때문이다
하나씩 정리해 보면
@property
property는 숨겨진 변수를 반환 할 수 있도록 해준다
(즉 함수명을 변수명처럼 사용할 수 있도록 해준다)
예시를 보자
#!/usr/bin/env python3
class MyClass:
def __init__(self,name:str = "anonymous" ,num:int = 0000000):
self.name = name
self.__student_num = num
def get_student_num(self):
return self.__student_num
Instance1 = MyClass("홍길동",20231111)
print(Instance1.get_student_num)
다음을 실행 시키면
20231111 이 나오면서 값을 확인 할 수 있다
이제 property 를 이용하면
#!/usr/bin/env python3
class MyClass:
def __init__(self,name:str = "anonymous" ,num:int = 0000000):
self.name = name
self.__student_num = num
def get_student_num(self):
return self.__student_num
@property
def student_num(self):
return self.__student_num
Instance1 = MyClass("홍길동",20231111)
print(Instance1.student_num)
>>> 20231111
똑같은 결과를 얻을 수 있다
그럼 왜 쓰는 걸까???
위에서 말했듯이
@property는 함수를 변수 처럼 사용할 수 있도록 해준다는 것이다
위와 똑같은 예시에 @property 만 빼면
#!/usr/bin/env python3
class MyClass:
def __init__(self,name:str = "anonymous" ,num:int = 0000000):
self.name = name
self.__student_num = num
def student_num(self):
return self.__student_num
Instance1 = MyClass("홍길동",20231111)
print(Instance1.student_num)
<bound method MyClass.student_num of <__main__.MyClass object at 0x7f8b601438e0>>
라는 출력을 얻을 수 있다
위에 뜻을 해석해보면
bound란 어떤 클래스에 속해있는 method 라는 뜻이다 (우리가 매개 변수로 self 로 적어서 생김)
즉, 이 student_num 은 MyClass라는 클래스에 속해있는 매소드 입니다~ 라고 출력해 주는 것이다
( )를 쓰지 않으면 출력이 되지 않지만 아래와 같이
class MyClass:
def __init__(self,name:str = "anonymous" ,num:int = 0000000):
self.name = name
self.__student_num = num
@property
def student_num(self):
return self.__student_num
Instance1 = MyClass("홍길동",20231111)
print(Instance1.student_num)
@property를 붙여준다면 변수처럼 ( )를 쓰지 않고 실행을 시켜줄 수 있다
내가 생각하기엔 그냥 get , set 매소드를 변수마다 써주는 게 너무 지저분해서 사용하는 것 같다
마찬가지로 set 을 설정 하고 싶으면 @<필드명>.setter 를 해주면 된다
class MyClass:
def __init__(self,name:str = "anonymous" ,num:int = 0000000):
self.name = name
self.__student_num = num
@property
def student_num(self):
return self.__student_num
@student_num.setter
def student_num(self,after_num):
self.__student_num = after_num
Instance1 = MyClass("홍길동",20231111)
Instance1.student_num = 20232222
print(Instance1.student_num)
이렇게 코드를 작성하면
student_num 하나로 get / set 함수를 모두 해결이 가능하다
@classmethod / @staticmethod
위의 두개의 매소드를 정적메소드 라고 한다
정적메소드란 클래스에서 직접 접근을 할 수 있는 메소드 이다
메소드는 크게 세개가 있는데
- class method
- static method
- instance method
가장 먼저 우리가 잘 알고있는 method 는 instance method인데
instance 메소드는 항상 객체 자신을 의미하는 "self"라는 파라미터를 첫 번째 인자로 가진다
Static method 와 Class method는 상속을 할 때 차이점이 드러난다
Staticmethod는 부모클래스에서 호출하던 자식클래스에서 선언을 하던 클래스 변수를 모두 바꿀 수 있다
다음 예시를 살펴보면
class ParentClass:
name = None
def __init__(self,name:str = None ,num:int = None):
#self.name = name
self.student_num = num
@staticmethod
def change_name(new_name):
ParentClass.name = new_name
def __str__(self):
return "부모 클래스"
class ChildClass(ParentClass):
def __str__(self):
return "자식 클래스"
Instance1 = ParentClass()
Instance2 = ChildClass()
print(Instance1,"에서 staticmethod 호출 ")
Instance1.change_name("어머니")
print(Instance1.name , Instance2.name)
print(Instance2,"에서 staticmethod 호출 ")
Instance2.change_name("자식")
print(Instance1.name , Instance2.name)
부모 클래스 에서 staticmethod 호출
어머니 어머니
자식 클래스 에서 staticmethod 호출
자식 자식
다음과 같이 자식에서 바꿔도 부모에 영향을 주는 것을 볼 수 있다
좀 더 자세하게 알아보면
class CustomClass:
# instance method
def add_instance_method(self, a,b):
return a + b
# classmethod
@classmethod
def add_class_method(cls, a, b):
return a + b
# staticmethod
@staticmethod
def add_static_method(a, b):
return a + b
다음과 같이 한 class 내에 classmethod 와 staticmethod를 같이 선언해준다
먼저 선언부에서 3개의 차이는 극명하게 들어난다
- instance method : 첫번째 인자에 self를 적어준다
- class method : 첫번째 인자에 cls를 적어준다
- static method : 첫번째 인자를 적지 않는다
그러면 사용할때도 다를까?
사용은 모두 동일하게 첫번째 인자를 제외한 나머지로 사용한다
Instance1 = CustomClass()
print(Instance1.add_instance_method(3,5))
print(Instance1.add_class_method(3,5))
print(Instance1.add_static_method(3,5))
>>> 8
>>> 8
>>> 8
이는 파이썬에서 특별하게 instance method 도 클래스에 직접 접근할 수 있기 때문에 이러한 특징을 보인다
그러면 이들의 차이는 무엇일까?
다음 예시에서
class MyClass:
CLASS_PARAM = 100
def __init__(self, instance_param):
self.instance_param = instance_param
def method(self):
print("This is class method")
@classmethod
def class_method(cls):
print("\n##### class method #####")
print(cls.CLASS_PARAM)
cls.method(cls)
# print(cls.instance_param) 는 불가.
@staticmethod
def static_method():
print("\n##### static method #####")
print("Static!!")
# cls.method() 불가.
# print(cls.CLASS_PARAM) 불가.
# print(self.instance_param) 불가.
def instance_method(self):
print("\n##### instance method #####")
print(self.instance_param)
print(self.CLASS_PARAM)
일반적으로 우리가 아는 듯이 Instance를 선언해주고 사용하는 다음 예시
Instance1 = MyClass('This is instance_param')
Instance1.class_method()
Instance1.static_method()
Instance1.instance_method()
에서는 큰 차이를 보이지 않는다
다만 static method는 self 나 cls 변수가 없기 때문에
instance 변수/함수 나 class 변수/함수를 부를 방법이 없다
또한 class method도 아직 instance가 만들어지기 전이기 때문에
instance 변수/함수 를 부를 수가 없다
하지만 class는 만들어졌기 때문에 class method는 호출이 가능하다
instance_method 는 self(자아) 즉, instance가 만들어지기 전에는 작동하지 않기 때문에
instance 함수/변수 , class 함수/변수를 모두 가져올 수 있다
그러면 instance_method로 통일하면 되지 왜 class_method 와 instance method를 사용할까??
가장 큰 장점은 instance를 만들지 않고 사용이 가능하다는 장점이다
이는 메모리를 사용하지 않게 되어서 부담이 줄어든다
다음의 예시를 보자
MyClass.class_method()
MyClass.static_method()
MyClass.instance_method()
다음 세개의 코드를 실행한다고 할때
다음과 같이 instance_method는 동작하지 않는다
이렇게 class, static과 instance의 차이를 알겠는데
class와 static의 차이는 언제 많이 날까??
이 둘의 차이는 상속에서 차이가 난다
class Language:
default_language = "English"
def __init__(self):
self.show = '나의 언어는 ' + self.default_language
@classmethod
def class_my_language(cls):
return cls()
@staticmethod
def static_my_language():
return Language()
def print_language(self):
print(self.show)
class KoreanLanguage(Language):
default_language = "한국어"
a = KoreanLanguage.static_my_language()
b = KoreanLanguage.class_my_language()
a.print_language()
b.print_language()
>>> 나의 언어는 English
>>> 나의 언어는 한국어
다음을 실행했을 때 결과와 같이
static method는 부모 클래스의 class attribute를 가져오고
class method는 cls 인자를 통해서 자신의 class attribute를 가져온다는 점이다
'Python > Basic' 카테고리의 다른 글
Python에서의 *args 와 **kwargs (0) | 2023.04.04 |
---|---|
Python Decorator 에 관하여 (0) | 2023.04.04 |
[AI for Python] 행렬의 곱셈 정리 (0) | 2022.10.04 |
[Python Basic] Python 에 수식표현(Sympy) (0) | 2022.10.03 |
[Python Basic]조건문과 반복문 (0) | 2022.09.23 |