지금 코드에는 추상 클래스
인 도형 클래스, 그리고 직사각형 클래스, 원 클래스, 원통 클래스, 정삼각형, 직각삼각형 같은 여러 도형 클래스와 그림판을 나타내는 Paint
클래스가 있다.
from math import sqrt, pi
from abc import ABC, abstractmethod
class Shape(ABC):
"""도형 클래스"""
@abstractmethod
def area(self) -> float:
"""도형의 넓이를 리턴한다: 자식 클래스가 오버라이딩할 것"""
pass
@abstractmethod
def perimeter(self) -> float:
"""도형의 둘레를 리턴한다: 자식 클래스가 오버라이딩할 것"""
pass
def larger_than(self, shape):
"""해당 인스턴스의 넓이가 파라미터 인스턴스의 넓이보다 큰지를 불린으로 나타낸다"""
return self.area() > shape.area()
class Rectangle(Shape):
"""직사각형 클래스"""
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
"""직사각형의 넓이를 리턴한다"""
return self.width * self.height
def perimeter(self):
"""직사각형의 둘레를 리턴한다"""
return 2 * self.width + 2 * self.height
def __str__(self):
"""직사각형의 정보를 문자열로 리턴한다"""
return "밑변 {}, 높이 {}인 직사각형".format(self.width, self.height)
class Circle(Shape):
"""원 클래스"""
def __init__(self, radius):
self.radius = radius
def area(self):
"""원의 넓이를 리턴한다"""
return pi * self.radius * self.radius
def perimeter(self):
"""원의 둘레를 리턴한다"""
return 2 * pi * self.radius
def __str__(self):
"""원의 정보를 문자열로 리턴한다"""
return "반지름 {}인 원".format(self.radius)
class Cylinder:
"""원통 클래스"""
def __init__(self, radius, height):
self.radius = radius
self.height = height
def __str__(self):
"""원통의 정보를 문자열로 리턴하는 메소드"""
return "밑면 반지름 {}, 높이 {}인 원기둥".format(self.radius, self.height)
class EquilateralTriangle(Shape):
"""정삼각형 클래스"""
def __init__(self, x, y, side):
self._x = x
self._y = y
self.side = side
def area(self):
"""정삼각형의 넓이를 리턴한다"""
return sqrt(3) * self.side * self.side / 4
def perimeter(self):
"""정삼각형의 둘레를 리턴한다"""
return 3 * self.side
def __str__(self):
"""정삼각형의 정보를 문자열로 리턴하는 메서드"""
return "한 변의 길이가 {}인 정삼각형".format(self.side)
class RightTriangle(Shape):
def __init__(self, base, height):
self.base = base
self.height = height
def area(self):
return self.base * self.height / 2
def perimeter(self):
return sqrt(self.base ** 2 + self.height ** 2) + self.base + self.height
def __str__(self):
"""지각삼각형의 정보를 문자열로 리턴한다"""
return "밑변 {}, 높이 {}인 직각삼각형".format(self.base, self.height)
class Paint:
"""그림판 프로그램 클래스"""
def __init__(self):
self.shapes = []
def add_shape(self, shape):
"""도형 인스턴스만 그림판에 추가한다"""
if isinstance(shape, Shape):
self.shapes.append(shape)
else:
print("도형 클래스가 아닌 인스턴스는 추가할 수 없습니다!")
def total_area_of_shapes(self):
"""그림판에 있는 모든 도형의 넓이의 합을 구한다"""
return sum([shape.area() for shape in self.shapes])
def total_perimeter_of_shapes(self):
"""그림판에 있는 모든 도형의 둘레의 합을 구한다"""
return sum([shape.perimeter() for shape in self.shapes])
def __str__(self):
"""그림판에 있는 각 도형들의 정보를 문자열로 리턴한다"""
res_str = "그림판 안에 있는 도형들:\\n\\n"
for shape in self.shapes:
res_str += str(shape) + "\\n"
return res_str
먼저 예전에 작성했던 add_shape
메서드를 보자
def add_shape(self, shape):
"""도형 인스턴스만 그림판에 추가한다"""
if isinstance(shape, Shape):
self.shapes.append(shape)
else:
print("도형 클래스가 아닌 인스턴스는 추가할 수 없습니다!")
add_shape
메서드는 Shape
클래스의 인스턴스만 shapes
리스트에 추가한다.
그런데 shapes
리스트에 인스턴스를 추가하기 전에 Shape
클래스의 인스턴스가 맞는지 확인하고 있다.
이렇게 어떤 작업 전에 확인을 거치는 코딩 스타일을 LBYL
이라고 한다.
뛰기 전에 살펴보라는 뜻이다.
우리 말로는 "돌 다리도 두드려보고 건너라" 라는 말이다.
어떤 작업을 수행하기 전에 그 작업을 수행해도 괜찮을지 확인하는 것이다.
파이썬에서는 이런 LBYL
스타일과 정반대로 일단 실행하고 보는 EAFP
라는 코딩 스타일이 있다.
허락보다 용서가 쉽다 라는 뜻이다.
일단 먼저 빨리 실행하고, 문제가 생기면 그때 처리하자는 식의 사고방식이다.
지금 Paint
클래스는 LBYL
스타일로 작성이되었다.
이것을 EAFP
스타일로 바꿔보자.
먼저 add_shape
메서드를 별다른 확인 작업 없이 도형을 바로 그림판에 추가하는 방식으로 바꿀 것이다.
def add_shape(self, shape):
"""도형 인스턴스만 그림판에 추가한다"""
self.shapes.append(shape)
그 다음에는 이 shape
은 Shape
클래스의 인스턴스여야 한다는 type hinting
을 추가하겠다.
def add_shape(self, shape: Shape):
"""도형 인스턴스만 그림판에 추가한다"""
self.shapes.append(shape)
그리고 나서 주석도 바꿔줄 것이다.
def add_shape(self, shape: Shape):
"""
그림판의 도형 인스턴스 shape 을 추가한다.
단, shape 은 추상 클래스 Shape 의 인스턴스여야 한다
"""
self.shapes.append(shape)
물론 이런 type hinting
과 주석은 설명을 위한 것일 뿐, 실제로 Shape
클래스의 인스턴스만 들어오도록 강제하지는 못한다.
그러니까 Shape
클래스의 인스턴스가 아닌 인스턴스들도 들어올 수 있다는 것이다.
이제 add_shape
메서드가 깔끔해진 대신 Shape
클래스의 인스턴스가 아닌 그러니까 area
perimeter
메서드가 없는 인스턴스가 들어올 위험성도 생겼다.
이런 위험성을 대비하기 위해 total_area_of_shapes
메서드를 수정해야한다.
def total_area_of_shapes(self):
"""그림판에 있는 모든 도형의 넓이의 합을 구한다"""
total_area = 0
for shape in self.shapes:
try:
total_area += shape.area()
except (AttributeError, TypeError):
print("그림판에 area 메서드가 없거나 잘못 정의되어 있는 인스턴스 {}가 있습니다.".format(shape))
try
와 except
로 예외처리를 했다.
이런 방식이 EAFP
방식이다.
정리하자면 기존에 add_shape
메서드에서 번거롭게 Shape
클래스의 인스턴스인지 확인하는 부분을 뺐다.
그 대신 도형 인스턴스의 area
메서드를 호출하다가 에러를 발생할 때의 대비책을 마련해줬다.
그 다음에는 total_perimeter_of_shape
메서드도 이 EAFP
스타일로 바꿔주자.
def total_perimeter_of_shapes(self):
"""그림판에 있는 모든 도형의 둘레의 합을 구한다"""
total_perimeter = 0
for shape in self.shapes:
try:
total_perimeter += shape.perimeter()
except (AttributeError, TypeError):
print("그림판에 perimeter 메서드가 없거나 잘못 정의되어 있는 인스턴스 {}가 있습니다.".format(shape))
물론 항상 EAFP
스타일로만 파이썬 코드를 써야 하는 것은 아니다.
이렇게 같은 동작을 하더라도 스타일을 다르게 해서 쓸 수 있다는 것을 기억하자.