Type Python! (OOP Study)...ing

김형준·2022년 8월 31일
post-thumbnail

해당 게시글은 개인적인 학습 목적의 글입니다.

게시글 내 모든 정보 출처: 🔗 인프런 강의


Intro


환경 설정

VSCode 단축키

  • 좌측 메뉴 열고 닫기 : ctrl + b
  • 터미널 열고 닫기 : ctrl + j
  • Extensions 열기 : ctrl + shift + x
  • 환경 설정(settings) : ctrl + ,
  • 명령어 창: ctrl + shift + p

VSCode Extensions (Market Place)

  • Python 검색 후 install
  • 그 외 커스터마이징은 검색하여 적용할 것

기본 환경 설정 터미널 명령어

  • python : Python 설치가 잘 되었는지 확인 & 콘솔창 실행
    • 파이썬 코드 입력 가능한 상태로 변경
    • exit()을 통해 종료
  • pip : pip 확인
    • pip는 Python으로 작성된 패키지 라이브러리를 관리해주는 시스템
  • pip list : 현재 설치된 파이썬 패키지를 확인할 수 있다.
  • python [파일명.py] : 해당 파일을 실행시킨다.
    • 명령어 앞의 경로가 정확해야한다.
  • 🔗 venv 공식 문서
  • python -m venv [가상환경 이름-통상적으로 venv] : 가상 환경 생성
  • 맥: source [venv]/bin/activate : 가상환경 venv 접속.
    • 윈도우(PowerShell): [venv]\Scripts\Activate.ps1
    • 윈도우(cmd.exe): [venv]\Scripts\activate.bat
    • 성공시 경로 앞에 (venv)가 생긴다.
  • deactivate: 가상환경 venv 나가기.

Formatter & Linter 설정

Formatter: 코드 스타일을 수정해주는 툴 ( spacing, 줄 바꿈, 주석 등) -> black 사용

  • 환경설정 열고 (ctrl + ,) Editor: Default Formatter를 Python으로 변경
  • Format On Save 체크
  • ctrl + shift + p 누르고 Select Interpreter 입력하여, 인터프리터 변경
    • 맥은 which python 입력 후 경로 복사하여 위 검색창에 붙여놓고 클릭 (2번 실행)
    • 윈도우는 where python 입력 후 경로 복사하여 위 검색창에 붙여놓고 클릭 (2번 실행)
  • 파이썬 파일 저장 후 우측 하단의 Formatter 경고 창에서 use black 클릭
    • 클릭했으나 변경이 되지 않았는지 계속해서 경고창이 떴다.
    • 환경설정(ctrl + ,) 열고 python formatting provider 검색 후 black으로 변경!
    • 여기까지 하면 파일 저장 시 black이 이쁘게 코드를 정리해준다. (줄 바꿈과 같이 전체적인 포맷팅 자동화)

Linter: 코드 컨벤션과 에러 체크를 도와주는 툴 -> flake8 사용

  • Python은 인터프리터 언어이기 때문에, 컴파일 시 기계어로 변경하며 오류를 바로 뱉어내 인지할 수 있는 컴파일 언어와 달리, 오류를 발견하기 힘들다.
    • 즉, 실제 오류를 가진 함수가 실행될 때 오류가 발생한다.
    • 예상치 못한 상황에서 버그가 발생되며 시스템 장애가 생기는 것..!
  • Linter는 이를 위해 파이썬의 input 등을 인터프리터로 실행하기 전에 한번 확인을 해주는 툴
  • 명령어 창 ctrl + shift + p 누르고 python: Select Linter 입력
  • flake8 선택 (종류는 다양하다.) -> 종류 별 차이점 찾아보자.
  • 이제 잘못된 문법을 사용하고 저장하면! 바로 빨간 줄이 뜨는 것을 볼 수 있다.

pip 명령어 정리

  • pip는 파이썬으로 작성된 패키지, 라이브러리, 프레임워크를 관리해주는 툴이다.

    • $ pip install pip --upgrade : pip 업그레이드
    • $ pip install "패키지~=3.0.0" : 3.0.0 버전의 패키지를 설치한다.
    • $ pip install [패키지] : 패키지를 설치한다.
    • $ pip uninstall [패키지]: 패키지를 삭제한다.
    • $ pip --version : 설치된 pip 버전을 확인할 수 있다.
    • $ pip freeze : 설치된 패키지를 확인할 수 있다.
    • $ pip freeze > requirements.txt : requirements.txt파일에 설치된 패키지를 출력한다.
    • $ pip install -r requirements.txt : requirements.txt파일에 기록된 패키지를 설치한다.
  • 추가적으로 requirements.txt에 주석으로 Python version도 명시해주면 좋다.


Main Study


Decorator

  • 함수의 내부를 수정하지 않고 기능에 변화를 주고 싶을 때 사용한다.
  • 일반적으로 함수의 전처리나 후처리에 대한 필요가 있을때 사용을 한다.
  • 또한 데코레이터를 이용해, 반복을 줄이고 메소드나 함수의 책임을 확장한다.
  • 기능을 추가할 함수를 인자로 넘겨 사용한다.
# 적용 전

def copyright(func):
    def new_func():
        print("@Copyright")
        func()
        return "end!"

    return new_func


def smile():
    print("😊")


def sunglass():
    print("😎")


def angry():
    print("🤬")


def doggy():
    print("🐶")


def madness():
    print("👹")


def scary():
    print("😫")


def hmm():
    print("🤔")


def surprise():
    print("😨")


# 새로운 함수로 재정의 해준다. 이 때, 각 object는 새로운 함수로 재정의 된다.
# 예를 들어, smile의 경우 copyright()로 재정의 된다.
# 이때 smile은 재정의된 함수다.
# Python에선 소괄호 ()를 붙일 경우 실행하라는 뜻이 되어,
# smile()을 인자로 넘기면
# smile()의 return 값인 None이 인자로 넘어가 not callable 오류가 발생한다.
smile = copyright(smile)
sunglass = copyright(sunglass)
angry = copyright(angry)
doggy = copyright(doggy)
madness = copyright(madness)
scary = copyright(scary)
hmm = copyright(hmm)
surprise = copyright(surprise)

smile()
sunglass()
angry()
doggy()
madness()
scary()
hmm()
surprise()

print(smile)
# 함수 자체를 print하면 smile이 먼저 실행되고 -> copyright(smile)
# 해당 함수의 return 값을 print한다. 
# 여기에선 copyright의 return 값이 없으므로 None 이 찍힌다.
# 현재는 return 값을 end! 로 지정하여, end! 가 찍힌다.
print(smile())
  • 함수 자체를 소괄호 없이 넘기는건 Java를 사용할 때 보지 못했던 것 같다.
  • 짐작하건데, Python에선 함수 자체도 객체로 인식하는 것 같다.
  • 실제 print로 소괄호 없이 찍어보면, 함수 자체가 마치 클래스와 같이 찍힌다.
  • 이 부분은 조금 더 공부해보며 알아보자!
def copyright(func):
    def new_func():
        print("@Copyright")
        func()
        return "end!"

    return new_func


# Decorator 적용!
@copyright
def smile():
    print("😊")


@copyright
def sunglass():
    print("😎")


@copyright
def angry():
    print("🤬")


@copyright
def doggy():
    print("🐶")


@copyright
def madness():
    print("👹")


@copyright
def scary():
    print("😫")


@copyright
def hmm():
    print("🤔")


@copyright
def surprise():
    print("😨")


smile()
sunglass()
angry()
doggy()
madness()
scary()
hmm()
surprise()

OOP

  • Object Oriented Programming
  • 데이터를 추상화시켜 상태(속성)와 행위(메서드)를 가진 객체(object)로 만들고 그 객체들 간의 유기적인 상호작용을 통해 로직(흐름)을 구성하는 프로그래밍 방법
  • 프로그램을 실제 세상에 가깝게 모델링하는 기법

OOP 원칙

  • 캡슐화(Encapsulation): 객체의 속성과 행위를 하나로 묶고, 구현된 일부를 감추어 은닉한다.
  • 추상화(Abstraction): 불필요한 정보는 숨기고 필요한 정보만을 표현함으로써 공통의 속성이나 행위를 하나로 묶어 이름을 붙이는 것
  • 상속(Inheritance): 부모의 속성과 행위를 물려받은 자식은 이를 재정의하여 사용할 수 있다.
  • 다형성(Polymorphism): 여러 형태를 가질 수 있도록 한다. 즉, 객체를 부품화할 수 있도록 한다.

Class

  • 로봇 설계도, 붕어빵 틀
  • 어떤 문제를 해결하기 위한 데이터를 만들기 위해 OOP 원칙에 따라 집단에 속하는 속성과 행위를 변수와 메서드로 정의한 것.

Instance (Object)

  • 실제 로봇, 붕어빵
  • Class에서 정의한 것을 토대로 실제 메모리 상에 할당된 것(실제 사물)으로 실제 프로그램에서 사용되는 데이터.
  • 하나의 Class로 만들어진 여러 Instance(Object)는 각각 독립적이다.

절차 지향 VS 객체 지향

절차 지향 프로그래밍

  • 절차 지향 프로그래밍으로 계산기를 만들어보자.
def plus(a, b):
    return a + b


def minus(a, b):
    return a - b


def multiple(a, b):
    return a * b


def divide(a, b):
    return a / b


print(plus(1, 2))
print(multiple(1, 2))
  • 절차지향 프로그래밍은 순서에 따라 구현할 기능들을 구현해 나간다.
  • 위와 같이 코딩할 경우, 매번 변수를 인자로 넘겨줘야 한다.
  • 위의 예제는 간단하지만, 만약 복잡한 코드의 경우 위와 같이 순서에 따라 코딩한다면 굉장히 이해하기 어려운 비효율적인 코드가 된다.
  • 또한 유지보수가 어렵다는 단점도 지닌다.
  • 다만, 절차지향을 지양하는 대신 객체 지향과 적절히 혼용하여 사용함이 가장 올바른 코딩으로 알고 있다. 추후에 더욱 공부해보자.

객체 지향 프로그래밍

  • 역시 객체 지향 프로그래밍으로 계산기를 만들어보자.
class Calculator:
    # 생성자 : 메모리에 올라오는 즉시 실행된다.
    def __init__(self, a, b):
        self.a = a
        self.b = b

    # 인스턴스 메서드
    def plus(self):
        return self.a + self.b

    def minus(self):
        return self.a - self.b

    def multiple(self):
        return self.a * self.b

    def divide(self):
        return self.a / self.b


# 아래와 같이 생성하며, 생성 시 self는 instance인 calculator1로,
# a는 1, b는 2로 할당된다.
# 또한 생성자에서 정의한 self.a 는 인스턴스 변수가 되며,
# 이때 파라미터로 받은 a를 할당하는 것이다.
calculator1 = Calculator(1, 2)
calculator2 = Calculator(3, 4)

print(calculator1.a)
print(calculator1.b)
print(calculator1.plus())
print(calculator1.multiple())

print(calculator2.a)
print(calculator2.b)
print(calculator2.plus())
print(calculator2.multiple())

# 아래와 같이 할당된 인스턴스 변수여도 재할당이 가능하다.
calculator2.a = 9
  • 일단 구현하며 느낀점은 Java와 문법적인 차이만 있을 뿐, 상당히 유사하게 코딩할 수 있다는 점.
  • class를 구현하고, 가장 먼저 생성자를 만들어준다.
    • 이때, 생성자는 __init__(self) 와 같은 이름으로 지정되며,
    • self는 생성된 인스턴스 자체를 의미한다.
    • 또한 생성자의 구현부에는 self.[인스턴스변수명] 와 같이 인스턴스 변수를 지정할 수 있다.
  • 위의 코드를 통해 클래스, 생성자, 인스턴스 변수, 인스턴스 메서드에 대해 간단히 이해할 수 있다.

추상화 (Abstraction)

  • 불필요한 정보는 숨기고, 중요한(필요한) 정보만을 표현함으로써 공통의 속성 값이나 행위(methods)를 하나로 묶어 이름을 붙이는 것이다.
# abstraction : 추상화

class Robot:

    # 클래스 변수 : 인스턴스들이 공유하는 변수
    population = 0

    # 생성자 함수 : 인스턴스가 생성될 때 초기화가 되는 함수
    def __init__(self, name, code):
        self.name = name  # 인스턴스 변수
        self.code = code  # 인스턴스 변수

        # 모든 인스턴스들이 공유하는 클래스 변수의 값을, 생성될 때 마다 + 1 씩 해준다.
        Robot.population += 1

    # 인스턴스 메서드
    def say_hi(self):
        # code..
        print(f"Greetings, my masters call me {self.name}.")

    # 인스턴스 메서드
    def calc_add(self, a, b):
        return a + b

    # 인스턴스 메서드
    def die(self):
        print(f"{self.name} is being destroyed!")

        # 죽었을 경우 클래스 변수인 population 1 감소
        Robot.population -= 1

        if Robot.population == 0:
            print(f"{self.name} was the last one.")
        else:
            print(f"There are still {Robot.population} robots working.")

    # 인스턴스 메서드
    def fixed(self):
        print(f"{self.name} is fixed now!")

        # 고쳤을 경우 클래스 변수인 population 1 증가
        Robot.population += 1
        print(f"There are {Robot.population} robots working now.")

    # 클래스 메서드
    @classmethod
    def how_many(cls):  # cls는 인스턴스인 self 대신 클래스를 받는 것 (class를 의미).
        print(f"We have {cls.population} robots.")


print(Robot.population)  # 0

siri = Robot("siri", 120392491)
jarvis = Robot("jarvis", 1232349729)
bixby = Robot("bixby", 3452234134435)

print(Robot.population)  # 3

print(siri.name)
print(siri.code)
siri.calc_add(2, 3)
siri.die()

Robot.how_many()

siri.fixed()
Robot.how_many()
  • 위 코드는 객체 지향 관점에서 로봇이라는 클래스를 정의한 것. (추상화)
  • Java에서는 이 외에도 추상 클래스, 추상 메서드를 abstract 예약어를 통해 정의할 수 있다.
    • 추상 클래스의 경우 여러 클래스간 비슷한 필드와 메서드를 공통적으로 추출해 만들어진 클래스를 뜻하고
    • 추상 클래스 내 추상 메서드는 구현부 (body)가 없이 정의된다.
      • 따라서 추상 클래스는 상속받는 자식 클래스에서 반드시 오버라이딩하여 구현부를 작성해줘야 한다.
  • 그 외에 파이썬 코드를 작성하며 배운 것.
    • 인스턴스 메서드를 호출하면, 자동으로 인스턴스가 첫번 째 인자로 (self로) 넘어가게 된다.
    • 클래스 변수와, 클래스 메서드의 구현
      • 클래스 변수의 경우 모든 인스턴스가 그 값을 공유하게 된다.
      • 클래스 메서드의 경우 파이썬이 제공하는 기본 Decorator인 @classmethod를 붙여 사용한다.
      • 클래스 메서드는 self라는 인스턴스를 받는 예약어 대신, cls라는 예약어를 사용한다.
      • 이는 인스턴스가 아닌 클래스 자체를 받는다는 것을 뜻한다.
  • 나아가 스태틱 변수, 스태틱 메서드도 있는데, 아래에서 다룰 예정!

Namespace

  • namespace : 개체를 구분할 수 있는 범위
  • 아래는 클래스와 인스턴스의 정보를 보기 편리한 매직 메서드
    • __dict__ : 네임 스페이스를 확인할 수 있다.
    • dir() : 네임 스페이스의 key 값을 확인할 수 있다.
    • __doc__ : class의 주석을 확인한다.
    • __class__ : 어떤 클래스로 만들어진 인스턴스인지 확인할 수 있다.
  • 파이썬은 메모리 효율을 위해 instance 메서드를 class의 네임스페이스에 저장한다.
    • class의 네임스페이스에는 instance 메서드가 담겨있다.
    • instance의 네임스페이스에는 instance 메서드가 없다.
      • 따라서 instance.[인스턴스메서드]를 작성하면, class의 네임스페이스에 있는 인스턴스메서드를 찾아 실행한다.
  • 이와 같은 원리로!
    • 인스턴스에서 class 변수, class 메서드를 사용할 수 있다!
  • 반대로!
    • class는 instance 메서드를 바로 사용할 수 없다.
    • instance 메서드의 첫 번째 파라미터인 self에는 클래스가 아닌 인스턴스가 와야하기 때문에 Type Error가 발생한다.
    • 단, 인자로 instance를 넘겨준다면 사용 가능하다!
class Robot:
	# ... 위 코드와 동일하여 생략.

siri = Robot("siri", 120392491)
jarvis = Robot("jarvis", 1232349729)
bixby = Robot("bixby", 3452234134435)

# 클래스의 네임스페이스에는 인스턴스 메서드까지 저장되어있는 것을 확인할 수 있다.
print(Robot.__dict__)
"""
{'__module__': '__main__', 'population': 3,
'__init__': <function Robot.__init__ at 0x000002CE9B24BA30>,
 'say_hi': <function Robot.say_hi at 0x000002CE9B24BAC0>,
 'calc_add': <function Robot.calc_add at 0x000002CE9B24BB50>,
 'die': <function Robot.die at 0x000002CE9B24BBE0>,
 'fixed': <function Robot.fixed at 0x000002CE9B24BC70>,
 'how_many': <classmethod(<function Robot.how_many at 0x000002CE9B24BD00>)>,
 '__dict__': <attribute '__dict__' of 'Robot' objects>,
 '__weakref__': <attribute '__weakref__' of 'Robot' objects>, '__doc__':None}
"""

# 인스턴스의 네임스페이스에는 인스턴스 변수만 저장된 것을 알 수 있다.
print(siri.__dict__)
print(jarvis.__dict__)
"""
{'name': 'siri', 'code': 120392491}
{'name': 'jarvis', 'code': 1232349729}
"""

# 파이썬에서 인스턴스는 클래스 변수와 클래스 메서드에 접근이 가능하다!!
print(siri.population)
siri.how_many()

# 클래스 자체로 인스턴스 메서드를 사용할 수 없지만,
# 인자로 인스턴스를 넘겨주면 사용 가능하다!
# 아래 두 줄의 코드는 동일한 기능을 수행한다.
Robot.say_hi(siri)
siri.say_hi()


# dir() : 사용할 수 있는 모든 변수, 메서드의 키 값을 출력해준다.
print(dir(siri))
"""
['__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__', 'calc_add', 'code', 'die',
'fixed', 'how_many', 'name', 'population', 'say_hi']
"""

# __doc__ : class의 주석을 확인할 수 있다.
print(Robot.__doc__)
"""
[Robot Class]
Author: 김형준
Role: ~~~
"""

# __class__ : 해당 인스턴스가 어떤 클래스인지 확인할 수 있다.
print(siri.__class__)
# <class '__main__.Robot'>
  • 위 코드를 작성하며 파이썬이 제공하는 매직 메서드를 확인할 수 있었다.
  • 협업 시 다른 사람이 내가 구현한 class 혹은 내가 다른 사람이 class를 활용할 때 매우 유용할 것 같다는 생각!
  • 또한 파이썬의 네임스페이이스에 대한 개념을 이해할 수 있었다.

Static Method

  • 특징
    • @classmethod 와 같이 @staticmethod는 인스턴스를 생성하지 않아도 클래스 이름으로 사용 가능하다.
    • @classmethod와 달리 cls를 파라미터로 받지 않아도 된다.
    • static method의 경우 부모 클래스의 속성 값을 가져오지만,
    • class method의 경우 현재 클래스의 속성 값을 가져온다.
      • 이 부분은 뒤에 상속을 공부한 후 다시 이해해보자!
    • 인스턴스 속성에 접근하거나 인스턴스 메서드를 사용할 수 없다.
    • 이러한 특징들로, 유틸리티성 메서드를 정의할 때 사용된다..!
class Robot:

    # 클래스 변수 : 인스턴스들이 공유하는 변수
    population = 0

    # 생성자 함수 : 인스턴스가 생성될 때 초기화가 되는 함수
    def __init__(self, name, code):
        self.name = name  # 인스턴스 변수
        self.code = code  # 인스턴스 변수
        Robot.population += 1

    # ... 생략

	# 아래와 같이 데코레이터를 사용한다.
    @staticmethod
    def is_this_Robot():
        return "Yes!, this is Robot!!"


siri = Robot("siri", 123123132)
print(siri.is_this_Robot())
  • 아직 파이썬에서 static method의 정확한 쓰임새를 모르겠다.
  • 마저 학습하고 다시 정리해보자.

profile
BackEnd Developer

0개의 댓글