동글이: 안녕하세요~! 저는 동글이에요!
네모니: 안녕하세요~! 저는 네모니에요!
글을 작성할 때, 도와줄 캐릭터를 만들어 보았다.
Github Action 을 적용하기 위해서는 우선 Github 에 Action을 적용할 저장소가 필요하다.
우선 Github Action을 적용하기 위한 프로세스를 간략하게 나열하겠다.
위의 프로세스로 작업을 해 보겠다.
필자는 이미 올렸다.
우선 프로젝트 폴더 Root 위치에 .github/workflows 폴더를 만들었다.
그 후, 필자는 blank.yml 이라는 파일을 생성했다.
name: CI/CD
on:
push:
branches: [ master ]
jobs:
build:
runs-on: self-hosted
steps:
- name: Pull Master Branch
run: |
cd /var/www/roadmap/ && sudo git pull origin master
- name: Deploy with React Build
run: |
sudo sh /var/www/roadmap/deploy.sh
필자는 master 브랜치에 push 를 할 경우 action 을 잡았다.
jobs 에 runs-on 은 따로 Linux 서버를 이용하기 때문에 self-hosted 로 적용했다.
그리고 steps 에는 sudo sh /var/www/roadmap/deploy.sh 했는데, 해당 코드는 React 프로젝트를 빌드하고 Django 가 Build 된 정보를 추적할 수 있도록 설정하고, collectstatic 하고, nginx 및 uwsgi 를 재부팅 하는 코드를 작성했다.
그래서 Action 으로 는 이 .sh 파일을 실행하도록 명령한 것이다.
필자는 이미 git clone 으로 해당 프로젝트를 다운로드 받았다.
필자는 Linux 인데 sudo 로 ./config.sh --url ... 를 작성하면 sudo 로는 못한다는 에러가 난다... 이때 앞에 RUNNER_ALLOW_RUNASROOT="1" ./config.sh --url... 같이 하면 된다.
해당 작업을 전부 하고, service 로 올리기 위해 sudo ./svc.sh install 작업을 한다.
그 후, 서비스를 실행한다. sudo ./svc.sh start
서비스를 종료하고 싶으면 sudo ./svc.sh uninstall 한다.
이제 모든 작업이 완료 되었다!!!
프로젝트를 수정해서 master 에 push 하면 이제 자동으로 배포 작업을 할 것이다!!
아주 편리해졌다!!
아래 사진은 Action 을 수행한 모습이다.
Django 와 React 프로젝트를 진행하면서 배포를 하기 위해서는 Git 레포지토리에 push 하고 서버 컴퓨터에 pull 을 받은 상태에서, 매번 React 프로젝트를 Build 한 다음 Build 된 폴더를 Django 가 가리키는 static 폴더로 옮기고 collectstatic 해야한다.
그 후, 웹서버와 웹서버게이트웨이 인터페이스를 재실행 해줘야 하는 불변함을 매번 겪어야 한다.
간단하게 이 순서를 나열하면
벌써 재배포를 하기 위해서 여러 작업이 필요하다.
더 간단하게 하기 위해 CI/CD 툴을 이용했다.
CI/CD 를 도와주는 것 중에 필자가 자주 이용하는 Github 자체에 이 역할을 수행해 주는 Github Action 이라는 것이 있다고 했다.
Github Action 은 간단하게 설명하면 로컬에서 Github 원격저장소에 하는 일련의 행위 중 (예: push 등) 무언가를 하면 해당 Github 원격 저장소를 Github Action 으로 연결된 특정 서버에서 어떤 역할(명령어 실행) 을 할 수 있도록 도와주는 것이다.
더욱 간단하게 설명하면 아래 사진과 같다.
다음 장에서 직접 구현해서 프로젝트에 적용해 보자!
예전에 "달고나" 라고 하는 큰 동아리?? 말로는 이제 더이상 동아리가 아니고 연합? 이다??? 라고 하는 조직이있다.
이곳에서 Django 이용하여 백엔드 서버를 구축한 적이있다.
그런데, 2021년 11월 정도에 이제 만든 프로젝트를 사용하지 않고 "그누보드" 를 이용하여 한다는 말과 함께 AWS 에 올려놓은 EC2, S3, RDS 전부 사라지게 생겨서 어떻게 할까 고민을 했다.
그래도 나름 열심히 노력해서 만든 프로젝트여서 그래도 '남길 수 있으면 남기자!' 라는 마인드로 Oracle Cloud 를 이용하여 공짜 인스턴스를 이용해 백엔드 서버와 프론트엔드 서버를 구축하기로 마음 먹었다.
이 작업을 하기 위해 무엇을 해야하는지 순서대로 생각해보았다.
정도가 있었다.
이 작업을 하기 위해 우선 Oracle Cloud 를 가입했다.
Oracle Cloud 같은 경우, 총 2개의 인스턴스를 무료로 계속 사용할 수 있었다.
그렇기 때문에 인스턴스 1개는 백엔드 서버용, 다른 1개는 프론트엔드 서버용으로 이용하려고 인스턴스를 생성했다.
인스턴스 생성 후, VPC 에 Subnet 규칙 중에 포트 80(HTTP), 443(HTTPS), 3306(MySQL) 가 제한되어 있어 규칙을 추가했다.
그런데, ssh 로 접속하려고 하는데 안되던 것이다...
분명 22 포트는 열려져있는데.. '왜 이러지??' 생각을 하면서 다시 인스턴스를 껏다 켰다...
이 과정에 인스턴스 재부팅 시간이 너무 오래 걸려 '일단 다른 인스턴스에 접속해보자...' 라는 생각으로 다른 인스턴스에 접속했을 때는 잘 되었다...
ssh 원격 접속 후, 내 github 주소에 Private 로 올려놓았던 프로젝트를 clone 받았다.
그리고, WAS Django 배포를 위한 Nginx, uWSGI, MySQL 등을 설치하고, 구성을 완료했다.
HTTPS 도 적용했다.
기존 데이터베이스는 AWS 서비스 중, RDS로 이용하고 있었다.
어떻게 데이터를 가져올까... 라는 생각에 MySQL WorkBench 를 이용해 데이터베이스 스키마랑 데이터 전부 Dump 떠서 가져오는 결정을 했다.
그래서 RDS 주소로 WorkBench 를 연결 후, 데이터를 Export 하려고 했는데... 뜬금 없이...
mysqldump.exe: unknown variable 'column-statistics=0'
에러가 나오는 것이다...
문제를 찾아보니, 현재 내가 사용하고 있는 WorkBench의 Dump 역할을 수행해주는 mysqldump Tool 버전이 맞지 않아 문제가 되는 것이었다.
아래 사진과 같이 해결 방법을 찾아 아래 사진에 보이는 링크에 들어가 다운로드 받았다.
그리고 Edit -> Preference -> Administration 에 Path to mysqldump Tool 경로를 다운로드 받은 압축파일을 풀고, bin 폴더에 있는 mysqldump.exe 을 새로 연결시켜주었다.
이제 잘 데이터를 Dump 할 수 있었다.
이제는 WorkBench 를 이용해 새로운 백엔드 서버에 설치한 MySQL 서버에 연결하여 이 Dump 뜬 데이터를 집어넣어야 했다.
이 또한 WorkBench 기능 Import 로 쉽게 해결될 줄 알았지만... 어림도 없이 에러가 발생했다.
Access denied; you need (at least one of) the SUPER privilege(s) for this operation
이런 에러가 나왔다....
이 해결 방안은 dump 뜬 파일 안에 몇 줄만 주석처리 했으면 됐다.
위에 사진과 같이 저 부분만 주석처리하면 문제가 해결 되었다.
기존 백엔드 서버는 media 파일을 전부 S3 에 업로드 하여 관리했다.
그렇지만 S3 조차 돈을 낭비하기 싫기 때문에 Oracle Cloud 에서 생성한 인스턴스로 제어하고 싶었다.
그래서 모든 S3 이미지를 로컬서버로 옮기는 작업을 해야 했다.
S3 에 들어가보니 폴더로 파일들을 다운로드할 수 없었다.
그래서 찾아보니 AWS CLI 를 이용하여 폴더 기준으로 다운로드 하는 방법을 찾았다.
sudo apt install awscli
우선 AWS CLI 를 백엔드 서버에 설치하고, aws configure 명령어로 기본 AWS 설정을 잡아줬다.
aws configure
설정을 마치고 aws s3 명령어로 모든 파일을 원하는 위치에 다운로드 했다.
aws s3 cp s3://{버켓이름}/media ./media --recursive
그러고 Django 설정에 S3 설정을 기존 로컬 static 과 media 설정으로 바꾸었다.
게시글에 이미지를 삽입할 수 있었는데, 이때 url 경로를 이용해서 이미지를 띄우고 있었다.
하지만 기존 이미지는 S3 url 을 가지고 있어서 변경이 필요했다.
그래서 데이터베이스에 전에 사용하고 있는 S3 url 을 전부 현재 로컬서버 url 주소로 변환이 필요했다.
SELECT REPLACE(body, '이전주소', '현재주소') AS body
FROM dalgona.board_board_post
WHERE body LIKE '%이전주소%';
UPDATE 데이터베이스.board_board_post SET body = REPLACE(body, '이전주소', 'https://dalgonabackend.shop') WHERE body LIKE '%이전주소%';
위에는 잘 됐는지 테스트 SQL 문이고, 아래가 UPDATE 하는 문이다.
당연히 될 줄 알았지만!! 어림도 없이 WorkBench 에서 "너 정말로 UPDATE 다 칠꺼야?" 라는 에러와 함께 나를 힘들게 했다....
이 작업을 수행하기 위해서 Edit 에 있는 Preference 를 건들어야 했다.
위에 사진에 보이는 SQL Editor 부분을 클릭하고, Safe Updates (rejects UPDATEs and DELETEs with no restrictions) 체크를 해제한 후, 다시 데이터베이스를 들어와야 했다.
그러고 다시 SQL 문을 실행하면 잘 작동했다.
이 부분은 프론트엔더 개발자가 따로 있는데, 그분에게 요청을 했다!!
나중에 프론트엔드 서버도 구축은 내가 하겠지만 일단 React 코드를 받아서 Build 작업이 남았기 때문에 기다리고 있다.
이번에는 로더
라는 것을 살펴보자
진짜 중요한 개념이다.
우리가 만든 애플리케이션을 보자.
index.html
파일에 CSS 파일을 넣고 싶다.
그러면 최종적으로 만들어진 파일이 위치하는 public
폴더에 style.css
라는 파일을 만든다.
style.css
body {
background-color: powderblue;
color: tomato;
}
이 CSS 를 동작시키기 위해 index.html
에 head
태그 안에 CSS 를 로드하는 link
태그로 public
폴더 안에 있는 style.css
를 불러올 것이다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="./public/style.css">
</head>
<body>
<h1>Hello, Webpack</h1>
<div id="root"></div>
<script src="./public/index_bundle.js"></script>
</body>
</html>
잘 나오는 것을 볼 수 있다.
그런데, 네트워크 탭을 살펴보면 아쉬운 것이있다.
Javascript 는 번들링
을 잘 했는데, style.css
는 별도로 다운로드 되는 것이다.
그래서 Javascript 안에 style.css
파일까지 넣으면 얼마나 좋을까? 그런 생각을 할 수도 있다.
그런 환상적인 작업을 해줄 수 있는 게 웹팩
이다.
아래는 웹팩
이 어떤 방식으로 동작하는지를 설명하는 간단한 그림이다.
이 그림을 보면 여러가지 Javascript, 이미지, 스타일파일 등 있다.
이런 여러가지 데이터들을 번들링
해서 아주 간단한 파일로 만들어 준다는 것이 번들러
가 하는 일이다.
웹팩
은 Javasciprt 가 아닌 png나 css 또한 번들링
해주는 아주 신기한 친구다.
그리고 그것을 하는 것이 로더
라는 것이다.
웹팩
을 잘 다룬다는 것은 "로더
를 얼마나 많이 알고 있는가? 로더
를 얼마나 자유롭게 옵션을 줘서 우리가 원하는 방식대로 로더
를 움직이게 하는가?" 라는 것이 핵심이다.
웹팩
Docs 주소에 Guide 로 가면 Asset Management
라는 것이 있다.
asset
은 우리가 사용하는 이미지, CSS, Javascript 이런 것들을 전부 asset
이라고 한다.
밑으로 내려보면 CSS를 번들러
로 로딩하는 방법이 나와있다.
이 작업을 하기 위해서는 npm
을 통해 style-loader
와 css-loader
라는 프로그램을 설치해야 된다고 나와있다.
설치를 진행하자.
npm install --save-dev style-loader css-loader
--save-dev
는 -D
와 같다.
그 후, 내용으로는 webpack.config.js
파일을 어떻게 하면 되는지를 우리에게 알려주는 내용을 보자.
하는 내용은 추가적으로 module
이라는 키를 가진 객체를 추가한다..
webpack.config.js
const path = require('path');
module.exports = {
mode: "development",
entry: "./source/index.js",
output: {
// __dirname 현재 파일의 위치하고 있는 경로로 약속된 변수
path: path.resolve(__dirname, "public"),
filename: "index_bundle.js"
},
module: {
rules: [
{
// 정규 표현식 .css 로 끝나는 것들 ($ -> 마지막 의미)
test: /\\.css$/,
// 웹팩을 동작시켰을 때, 확장자가 css 인 파일을 만나면
// 알아서 그 css 인 파일을 웹팩 안으로 로드 시켜주는 특수한 명령이 css-loader 이다
use: [
// 나중에 추가
// 'style-loader',
'css-loader',
],
},
]
}
}
이전에는 CSS 를 적용하기 위해서는 link
라는 태그 안에 정의해줬는데
이제 이렇게 사용하지 않고, 엔트리파일
인 index.js
파일에 가서 아래 코드 처럼 style.css
를 import
받아온다.
index.js
import hello_word from "./hello.js";
import world_word from "./world.js";
import css from "./style.css";
document.querySelector('#root').innerHTML = hello_word + " " + world_word;
console.log("css", css);
또한 style.css
파일을 source
폴더 하위에 이동 시킨다.
이유는 번들링
하기 위해서 이다.
과정을 보자, 번들링
을 하기 위해 npx webpack
을 실행하면, 웹팩
은 webpack.config.js
파일이 있다면 안에 있는 내용을 살펴본다.
엔트리파일
index.js
를 쭉 살펴보다가 확장자가 .css
인 부분이 있다.
import css from "./style.css";
이때 module
에 확장자가 .css
로 끝나는 것을 발견하면 css-loader
라고 하는 것에게 맡겨라 라고 정의했다.
그러면 css-loader
가 해당 파일을 읽어서 그것을 css
라는 변수 안에 세팅해준다.
console.log
에 찍히는 것을 보면 css
는 이것이다 처럼 나온다.
자세히 보면 css
에 관한 내용이 Javascript의 형태로 웹팩
으로 주입 된 것을 볼 수 있다.
즉, Javascript 를 이용해서 웹페이지에 스타일을 놓으면 되겠지만, 이것 조차도 자동으로 해주는 또 다른 로더
가 있다.
그 로더
는 style-loader
라는 것이다.
webpack.config.js
파일에 가서 style-loader
를 적용해 주자.
webpack.config.js
const path = require('path');
module.exports = {
mode: "production",
entry: "./source/index.js",
output: {
// __dirname 현재 파일의 위치하고 있는 경로로 약속된 변수
path: path.resolve(__dirname, "public"),
filename: "index_bundle.js"
},
module: {
rules: [
{
// 정규 표현식 .css 로 끝나는 것들 ($ -> 마지막 의미)
test: /\\.css$/,
// 웹팩을 동작시켰을 때, 확장자가 css 인 파일을 만나면
// 알아서 그 css 인 파일을 웹팩 안으로 로드 시켜주는 특수한 명령이 css-loader 이다
use: [
'style-loader',
'css-loader',
],
},
]
}
}
여기서 한번 정리하자.
웹팩
으로 가져오는 로더
로더
이다.
실행해주자.
npx webpack
웹페이지를 확인하면!
짜잔!! 스타일이 적용되었다.
또한 네트워크 탭을 보자
보이는 것처럼 index_bundle.js
파일만 읽어왔음에도 CSS가 웹에 적용되었다.
Element 탭에서 head
태그에 style
태그가 생성되고 그곳에 우리가 적용한 CSS가 보인다.
즉, 이 로더
라고 하는 것은 입력한 asset
들을 로더
를 통과시키면 계획에 가공해서 우리가 원하는 출력값을 만들어 주는 일종의 가공 공정이 로더
라고 할 수 있다.
그리고 그 로더
를 세팅하는 것이 module
의 rules
라는 부분에 로더
에 해당하는 이름을 검출하는 코드를 가지고, 그 조건에 해당하는 파일이 발견된다면 로더
들을 통과시켜서 처리해라 라고 한 것이다.
이 때, 주의해야 하는 것은 뒤쪽에 있는 로더
가 먼저 실행된다.
즉, 물리적인 CSS 파일을 읽어온 다음 그것을 받아서 웹브라우저의 웹페이지에 style
태그로 낑겨 넣는 로더
가 그 다음에 실행되는 것이다.
체인이 걸려있는 것처럼 작동을 한다.
뿐만 아니라 이미지, 폰트 등을 읽을 수 있다!!
webpack.config.js
const path = require('path');
module.exports = {
mode: "production",
entry: "./source/index.js",
output: {
// __dirname 현재 파일의 위치하고 있는 경로로 약속된 변수
path: path.resolve(__dirname, "public"),
filename: "index_bundle.js"
},
module: {
rules: [
{
// 정규 표현식 .css 로 끝나는 것들 ($ -> 마지막 의미)
test: /\\.css$/,
// 웹팩을 동작시켰을 때, 확장자가 css 인 파일을 만나면
// 알아서 그 css 인 파일을 웹팩 안으로 로드 시켜주는 특수한 명령이 css-loader 이다
use: [
'style-loader',
'css-loader',
],
},
{
test: /\\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
]
}
}
style.css
body {
background: url('./test_image.png');
color: tomato;
}
index.js
import hello_word from "./hello.js";
import world_word from "./world.js";
import css from "./style.css";
import test from "./test_image.png";
document.querySelector('#root').innerHTML = hello_word + " " + world_word;
const test_image = document.querySelector('#test');
test_image.src = test;
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Hello, Webpack</h1>
<div id="root"></div>
<img id="test" />
<script src="./public/index_bundle.js"></script>
</body>
</html>
실행
npx webpack
웹팩
으로 만들어진 파일
지금 코드에는 추상 클래스
인 도형 클래스, 그리고 직사각형 클래스, 원 클래스, 원통 클래스, 정삼각형, 직각삼각형 같은 여러 도형 클래스와 그림판을 나타내는 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
스타일로만 파이썬 코드를 써야 하는 것은 아니다.
이렇게 같은 동작을 하더라도 스타일을 다르게 해서 쓸 수 있다는 것을 기억하자.