회원가입
✏ 최근 포스트
💌 좋아요 많은 게시글
내가 만든 캐릭터

동글이/네모니


동글이: 안녕하세요~! 저는 동글이에요!

네모니: 안녕하세요~! 저는 네모니에요!

 

글을 작성할 때, 도와줄 캐릭터를 만들어 보았다.

NULL 2022-03-01
2 1
[Github Action] 2. Github Action 적용 (self-hosted)

Github Action 을 적용하기 위해서는 우선 Github Action을 적용할 저장소가 필요하다.

 

우선 Github Action을 적용하기 위한 프로세스를 간략하게 나열하겠다.

  1. 프로젝트를 Github 원격저장소에 올린다.
  2. 프로젝트 가장 바깥에 .github/workflows 폴더를 만들어 그 안에 작업 yaml 확장자로 코드를 작성한다.
  3. 배포할 서버에 원격저장소 프로젝트를 받는다. -> git clone "주소" (굳이 안해도 된다)
  4. Github 원격저장소에 Settings 에 들어가 Actions 탭을 클릭 후, Runner 를 클릭한다.
  5. new self-hosted runner 버튼을 클릭한다.
  6. 자신에 맞는 OS 를 선택 후, 배포할 서버에 아래쪽 코드를 전부 작성한다.

위의 프로세스로 작업을 해 보겠다.

 

1. 프로젝트를 Github 원격저장소에 올린다


필자는 이미 올렸다.

 

2. 프로젝트 가장 바깥에 .github/workflows 폴더를 만들어 그 안에 작업 yaml 확장자로 코드를 작성한다.


우선 프로젝트 폴더 Root 위치에 .github/workflows 폴더를 만들었다.

그 후, 필자는 blank.yml 이라는 파일을 생성했다.

 

blank.yaml

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 을 잡았다.

jobsruns-on 은 따로 Linux 서버를 이용하기 때문에 self-hosted 로 적용했다.

그리고 steps 에는 sudo sh /var/www/roadmap/deploy.sh 했는데, 해당 코드는 React 프로젝트를 빌드하고 DjangoBuild 된 정보를 추적할 수 있도록 설정하고, collectstatic 하고, nginx uwsgi 를 재부팅 하는 코드를 작성했다.

그래서 Action 으로 는 이 .sh 파일을 실행하도록 명령한 것이다.

 

3. 배포할 서버에 원격저장소 프로젝트를 받는다.


필자는 이미 git clone 으로 해당 프로젝트를 다운로드 받았다.

 

4. Github 원격저장소에 Settings 에 들어가 Actions 탭을 클릭 후, Runner 를 클릭한다.


 

5. new self-hosted runner 버튼을 클릭한다.


 

6. 자신에 맞는 OS 를 선택 후, 배포할 서버에 아래쪽 코드를 전부 작성한다.


필자는 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 을 수행한 모습이다.

 

CI/CD 로 엄청 많은 프로세스를 생략시켜 삶의 질이 향상 되었다!!

NULL 2021-12-16
1 0
[Github Action] 1. Github Action 적용하는 이유

Django React 프로젝트를 진행하면서 배포를 하기 위해서는 Git 레포지토리에 push 하고 서버 컴퓨터에 pull 을 받은 상태에서, 매번 React 프로젝트를 Build 한 다음 Build 된 폴더를 Django 가 가리키는 static 폴더로 옮기고 collectstatic 해야한다.

그 후, 웹서버웹서버게이트웨이 인터페이스를 재실행 해줘야 하는 불변함을 매번 겪어야 한다.

 

간단하게 이 순서를 나열하면

  1. 로컬 컴퓨터에 수정한 코드를 Github 원격 저장소에 Push 한다.
  2. 서비스를 하고 있는 서버 컴퓨터에 원격 접속하여 Github 원격 저장소에 올라온 수정된 코드를 Pull 받는다.
  3. React 같은 경우, 수정을 반영하기 위해 Build 를 한다.
  4. Build 한 폴더를 Django 가 추적할 수 있도록 static 폴더에 배치한다. (index.html 또한 따로 templates 쪽에 배치)
  5. Django 에서 collectstatic 을 한다.
  6. 웹서버와 wsgi 를 재실행 시켜준다.

벌써 재배포를 하기 위해서 여러 작업이 필요하다.

 

더 간단하게 하기 위해 CI/CD 툴을 이용했다.

CI/CD 를 도와주는 것 중에 필자가 자주 이용하는 Github 자체에 이 역할을 수행해 주는 Github Action 이라는 것이 있다고 했다.

 

Github Action 은 간단하게 설명하면 로컬에서 Github 원격저장소에 하는 일련의 행위 중 (예: push 등) 무언가를 하면 해당 Github 원격 저장소를 Github Action 으로 연결된 특정 서버에서 어떤 역할(명령어 실행) 을 할 수 있도록 도와주는 것이다.

더욱 간단하게 설명하면 아래 사진과 같다.

 

다음 장에서 직접 구현해서 프로젝트에 적용해 보자!

NULL 2021-12-16
1 0
백엔드 서버 새로 구축 작업

예전에 "달고나" 라고 하는 큰 동아리?? 말로는 이제 더이상 동아리가 아니고 연합? 이다??? 라고 하는 조직이있다.

 

이곳에서 Django 이용하여 백엔드 서버를 구축한 적이있다.

그런데, 2021년 11월 정도에 이제 만든 프로젝트를 사용하지 않고 "그누보드" 를 이용하여 한다는 말과 함께 AWS 에 올려놓은 EC2, S3, RDS 전부 사라지게 생겨서 어떻게 할까 고민을 했다.

 

그래도 나름 열심히 노력해서 만든 프로젝트여서 그래도 '남길 수 있으면 남기자!' 라는 마인드로 Oracle Cloud 를 이용하여 공짜 인스턴스를 이용해 백엔드 서버와 프론트엔드 서버를 구축하기로 마음 먹었다.

 

이 작업을 하기 위해 무엇을 해야하는지 순서대로 생각해보았다.

  1. Django 백엔드 서버 만들기
  2. 데이터베이스 복사하기
  3. S3 이미지, 파일 로컬서버로 옮기기
  4. 데이터베이스에 저장되어있는 이미지 url 주소 S3 가리키고 있는 것 로컬로 Update 시키기
  5. React Build 후, 프론트엔드 서버 만들기 (새로운 백엔드 바라보기)

정도가 있었다.

 

1. Django 백엔드 서버 만들기


이 작업을 하기 위해 우선 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 도 적용했다.

 

2. 데이터베이스 복사하기


기존 데이터베이스는 AWS 서비스 중, RDS로 이용하고 있었다.

어떻게 데이터를 가져올까... 라는 생각에 MySQL WorkBench 를 이용해 데이터베이스 스키마랑 데이터 전부 Dump 떠서 가져오는 결정을 했다.

 

그래서 RDS 주소로 WorkBench 를 연결 후, 데이터를 Export 하려고 했는데... 뜬금 없이...

mysqldump.exe: unknown variable 'column-statistics=0'

에러가 나오는 것이다...

 

문제를 찾아보니, 현재 내가 사용하고 있는 WorkBenchDump 역할을 수행해주는 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 뜬 파일 안에 몇 줄만 주석처리 했으면 됐다.

위에 사진과 같이 저 부분만 주석처리하면 문제가 해결 되었다.

 

3. S3 이미지, 파일 로컬서버로 옮기기


기존 백엔드 서버는 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 설정으로 바꾸었다.

 

4. 데이터베이스에 저장되어있는 이미지 url 주소 S3 가리키고 있는 것 로컬로 Update 시키기


게시글에 이미지를 삽입할 수 있었는데, 이때 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 문을 실행하면 잘 작동했다.

 

5. React Build 후, 프론트엔드 서버 만들기 (새로운 백엔드 바라보기)


이 부분은 프론트엔더 개발자가 따로 있는데, 그분에게 요청을 했다!!

나중에 프론트엔드 서버도 구축은 내가 하겠지만 일단 React 코드를 받아서 Build 작업이 남았기 때문에 기다리고 있다.

NULL 2021-11-26
1 0
5. [웹팩] 로더의 도입

이번에는 로더라는 것을 살펴보자

진짜 중요한 개념이다.

 

우리가 만든 애플리케이션을 보자.

index.html 파일에 CSS 파일을 넣고 싶다.

그러면 최종적으로 만들어진 파일이 위치하는 public 폴더에 style.css 라는 파일을 만든다.

style.css

body {
    background-color: powderblue;
    color: tomato;
}

 

이 CSS 를 동작시키기 위해 index.htmlhead 태그 안에 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 라는 것이 있다.

https://webpack.js.org/

asset 은 우리가 사용하는 이미지, CSS, Javascript 이런 것들을 전부 asset 이라고 한다.

 

밑으로 내려보면 CSS를 번들러로 로딩하는 방법이 나와있다.

이 작업을 하기 위해서는 npm 을 통해 style-loadercss-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.cssimport 받아온다.

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',
        ],
      },
    ]
  }
}

 

여기서 한번 정리하자.

  • css-loader : CSS 파일을 읽어와 웹팩으로 가져오는 로더
  • style-loader : 그렇게 가져온 CSS 코드를 웹페이지 안에 style 태그로 주입해 주는 로더이다.

 

실행해주자.

npx webpack

 

웹페이지를 확인하면!

짜잔!! 스타일이 적용되었다.

 

또한 네트워크 탭을 보자

보이는 것처럼 index_bundle.js 파일만 읽어왔음에도 CSS가 웹에 적용되었다.

 

Element 탭에서 head 태그에 style 태그가 생성되고 그곳에 우리가 적용한 CSS가 보인다.

 

즉, 이 로더 라고 하는 것은 입력한 asset 들을 로더를 통과시키면 계획에 가공해서 우리가 원하는 출력값을 만들어 주는 일종의 가공 공정이 로더라고 할 수 있다.

그리고 그 로더를 세팅하는 것이 modulerules 라는 부분에 로더에 해당하는 이름을 검출하는 코드를 가지고, 그 조건에 해당하는 파일이 발견된다면 로더 들을 통과시켜서 처리해라 라고 한 것이다.

이 때, 주의해야 하는 것은 뒤쪽에 있는 로더가 먼저 실행된다.

즉, 물리적인 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

 

웹팩으로 만들어진 파일

NULL 2021-10-24
1 0
16. [다형성] 파이썬 EAFP 코딩 스타일과 다형성

지금 코드에는 추상 클래스인 도형 클래스, 그리고 직사각형 클래스, 원 클래스, 원통 클래스, 정삼각형, 직각삼각형 같은 여러 도형 클래스와 그림판을 나타내는 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 (Look Before You Leap)


뛰기 전에 살펴보라는 뜻이다.

우리 말로는 "돌 다리도 두드려보고 건너라" 라는 말이다.

 

어떤 작업을 수행하기 전에 그 작업을 수행해도 괜찮을지 확인하는 것이다.

파이썬에서는 이런 LBYL 스타일과 정반대로 일단 실행하고 보는 EAFP 라는 코딩 스타일이 있다.

 

EAFP (Easier to Ask for Forgiveness than Permission)


허락보다 용서가 쉽다 라는 뜻이다.

일단 먼저 빨리 실행하고, 문제가 생기면 그때 처리하자는 식의 사고방식이다.

 

지금 Paint 클래스는 LBYL 스타일로 작성이되었다.

이것을 EAFP 스타일로 바꿔보자.

 

먼저 add_shape 메서드를 별다른 확인 작업 없이 도형을 바로 그림판에 추가하는 방식으로 바꿀 것이다.

def add_shape(self, shape):
    """도형 인스턴스만 그림판에 추가한다"""
    self.shapes.append(shape)

 

그 다음에는 이 shapeShape 클래스의 인스턴스여야 한다는 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))

tryexcept 로 예외처리를 했다.

이런 방식이 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))

 

이런 방식 Easier to Ask for Forgiveness than Permission 파이썬스러운 스타일이라고 한다.

물론 항상 EAFP 스타일로만 파이썬 코드를 써야 하는 것은 아니다.

이렇게 같은 동작을 하더라도 스타일을 다르게 해서 쓸 수 있다는 것을 기억하자.

NULL 2021-10-15
1 0
About NULL BLOG
NULL의 개인 블로그 입니다~! 개인 일상/프로젝트/자료 등 잡다한 내용이 담길 블로그 입니다! 잘 부탁드립니다~!
Yesterday: 677
Today: 184