Hard skills/Python (32)

Ubuntu 에 Python 새로운 버전 설치하기


sudo apt update
sudo apt install build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev wget

필요한 패키지를 받습니다. 


cd /tmp wget https://www.python.org/ftp/python/3.7.2/Python-3.7.2.tar.xz


설치파일을 다운로드 받습니다. 


tar -xf Python-3.7.2.tar.xz cd Python-3.7.2 ./configure --enable-optimizations


설치 파일의 압축을 풀고, 인스톨 준비를 합니다. 


make -j 1
sudo make altinstall

-j 1 은 1 개의 CPU 를 이용해서 build 하겠다는 것입니다.포인트는 sudo make altinstall 을 통해 버전을 따로 관리하는 것입니다. sudo make install 을 하면, 기존 파이썬을 덮어써버리게 되므로 주의해야합니다. 이후, 커맨드 창에 python3.7 을 입력해 잘 설치되었는지 확인합니다. 


특정 버전에 pip 를 통해 패키지 설치하는 법 

예를 들어, beatifulsoup4 패키지를 설치하려면 아래와 같이 합니다. pip 자체가 파이썬 코드이기 때문에 이런식으로 원하는 파이썬 버전을 통해 pip 를 실행시켜주면 됩니다. 

python3.7 -m pip install beautifulsoup4

References
https://websiteforstudents.com/installing-the-latest-python-3-7-on-ubuntu-16-04-18-04/





Cookiecutter 패키지는 프로젝트 템플릿을 쉽게 생성해주는 파이썬 패키지입니다. 1) 자신이 만든 템플릿을 저장해서 reproducible 하게 사용할 수 있고, 2) 이미 만들어져 있는 템플릿을 불러와서 거기서부터 새로 프로젝트를 생성할 수 있습니다. 특히, cookiecutter-pypackage/ 를 많이 사용하는듯합니다. 이 템플릿은 PyPI 등록을 위한 파이썬 패키지를 위한 템플릿을 제공합니다. 기본적인 PyPI 등록을 위한 패키지 구조와 기본 파일 (setup.py 등), nox, tox, Click, travis 등과 같은 파이썬 패키지 관련 코드를 담은 파일이 탑재되어 있기 때문에 패키지 개발의 start point 로서 유용하고 실제로 많이 사용하고 있는 패키지입니다. 


homepage

https://cookiecutter.readthedocs.io


tutorial

https://cookiecutter.readthedocs.io/en/latest/tutorial1.html

https://cookiecutter-pypackage.readthedocs.io/en/latest/tutorial.html


다른 컴퓨터로 conda 가상환경 옮기는 방법


참고

conda-cheatsheet.pdf


기존에 사용하던 컴퓨터 A 에서 컴퓨터 B 로 conda 가상환경을 옮겨야할 때가 있다. 


컴퓨터 A 에서 해야할 일 


1. 가상환경 켜기


source activate [이름]


2. 가상환경이 켜진 상태에서 아래 명령어로 dependency 를 export 할수 있다.


conda env export > environment.yaml

environment.yaml 파일을 열어보면 아래와 같이 잘 export 되었다는 것을 확인할 수 있다. 



3. 현재 환경의 python 버전 체크


현재 사용하고 있는 가상환경에서 사용하고 있는 python version 을 체크한다. 


python --version
Python 3.6.6


컴퓨터 B 에서 해야할 일 


Requirements! 

  • 컴퓨터 B 에서는 컴퓨터 A 에서와 같은 anaconda (python 2 또는 python3) 를 사용해야한다. 만약 anaconda 버전이 하위버전이면 잘 안돌아갈 수도 있을듯 하다. 

4. 가상환경 생성 


conda create --name [이름] python=3.6


5. prefix 변경


environment.yaml 을 열고 원하는 경우, 가상 환경의 이름을 바꾸고, prefix 를 경로에 맞게 바꾸어준다. 예를 들어, 


prefix: C:\Users\[사용자이름] \Anaconda3\envs\[가상환경이름]


5. yaml 파일을 통한 가상환경 생성 


conda env create --file environment.yaml


이 때, 


Solving environment: failed

ResolvePackageNotFound:


에러가 뜰 수 있다. 이것은 A 컴퓨터에서 설치된 라이브러리가 B 컴퓨터에서 설치가 불가능한 것인데, 컴퓨터의 운영체제가 다른 경우에 발생하는 것으로 보인다. 해결 방법은 수동으로 ResoevePackageNotFound 에서 출력된 리스트를 environment.yaml 파일에서 지운 후, 다시 시도하면 된다. (참고)



6. 주피터를 사용하는 경우, 커널에 가상환경 등록


source activate myenv python -m ipykernel install --user --name myenv --display-name "Python (myenv)"



Jupyter 유용한 확장기능 - lab_black


설치


pip install nb_black


사용법


notebook 사용자의 경우, 첫번째 셀에 아래 코드 실행 


%load_ext nb_black


lab 사용자의 경우, 첫번째 셀에 아래 코드 실행 


%load_ext lab_black


이후 코드를 실행하면, 자동으로 black format 으로 포매팅이 되는 것을 볼 수 있다. 따로 command 를 이용해서 formatting 을 하지 않아도 실행하는 즉시 formatting 이 되기 때문에 매우 유용하다!


링크 

https://github.com/dnanhkhoa/nb_black?source=your_stories_page

Pandas 에서 반복을 효율적으로 처리하는 방법


Pandas 를 통해 데이터 프로세싱을 할 때 종종 해야할일은 행에 반복적으로 접근을 하면서 그 값을 조작하는 일이다. 예를 들어, missing value 가 0 으로 코딩이 되어있는데, 이를 다른 값으로 바꾸고 싶을 경우 또는 A 컬럼의 값이 missing 일 때, B 컬럼의 값을 수정하고 싶은 경우 등이 있다. 이러한 작업을 하기 위해서는 모든 행을 조회 하면서 값을 조회하고 수정하는 일이 필요하다. 이번 포스팅에서는 이러한 반복작업이 필요한 상황에서 어떤 방법이 가장 효율적일지에 대해 정리해보려고한다.


사용할 데이터

diabetes.csv


1) pd.iterrows()


가장 기본적이고 많이 사용하는 방법이 iterrows 함수를 이용하는 것이다. 하지만 iterrows 함수는 다른 방법에 비해 느린 편이다. 

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

diabetes = pd.read_csv("diabetes.csv")
diabetes.head()


PregnanciesGlucoseBloodPressureSkinThicknessInsulinBMIDiabetesPedigreeFunctionAgeOutcome
061487235033.60.627501
11856629026.60.351310
28183640023.30.672321
318966239428.10.167210
40137403516843.12.288331


missing value 가 0 으로 코딩이 되어있는데, 이를 nan 으로 바꾸는 코드를 iterrows 를 이용해서 짜보자. 

def fix_missing(df, col):
    for i, row in df.iterrows():
        val = row[col]
        if val == 0:
            df.loc[i, col] = np.nan

%timeit fix_missing(diabetes, "SkinThickness")


33.9 ms ± 1.76 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)



2) pd.loc[]/pd.iloc[]


두 번째 방법은 index 를 통해 for 문을 돌면서, loc 또는 iloc 함수를 이용해 dataframe의 row에 접근하는 방법이다. 

def fix_missing2(df, col):
    for i in df.index:
        val = df.loc[i, col]
        if val == 0 :
            df.loc[i, col] = np.nan

%timeit fix_missing2(diabetes, "Insulin")


9.54 ms ± 130 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)


1) iterrow 방법에 비해 약 3배 빨라졌다는 것을 알 수 있다. 따라서 iterrows 가 익숙하다고 하더라도 다른 방법으로 바꾸는 것이 같은 작업을 더 빠르게 실행할 수 있어 효율적이다.  


3) pd.get_value()/pd.set_value()


다음은 위 방법과 마찬가지로 index를 통해 for 문을 돌면서 get_value 와 set_value 함수를 이용하는 방법이다. 2) 방법이 내부적으로 get_value, set_value를 호출하는 것이기 때문에 3) 이를 직접적으로 호출하는 방법이므로 더욱 빠르다. 

def fix_missing3(df, col):
    for i in df.index:
        val = df.get_value(i, col)
        if val == 0:
            df.set_value(i, col, np.nan)

%timeit fix_missing3(diabetes, "BMI")


3.65 ms ± 31.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)


2)에 비해 3배 정도 빨라졌으며, 1)에 비해서는 거의 10배 정도의 속도차이가 난다. 


4) pd.apply()


네 번째 방법은 apply 를 이용하는 것이다. apply 를 이용하는 것은 특별한 형태의 function 을 필요로 하는데 (이를 helper function 이라고도 한다), 이것은 Series 혹은 Dataframe의 각 원소마다 적용시킬 함수이다. 

def fix_missing4(x):
    if x == 0 : 
        return -999
    else: return x
    
%timeit diabetes.Age.apply(fix_missing4)
483 µs ± 3.89 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

3) 방법과 비교하여 3650/484 = 7.5 배 속도가 증가했다. apply 함수를 이용하는 것의 장점은 작업이 "비교적 간단할 때" 유용하다. 만약, 여러개의 column 에서 if 문을 적용하서 값을 다이나믹하게 바꾸어야하는 작업에 있어서는 apply 함수보다 for 문을 이용하는 방법이 더 적절할 수 있다. 

정리 
  • Default 방법으로 index 를 돌면서 set_value 와 get_value 를 호출하는 방법을 추천
  • 비교적 큰 데이터에셋에서 비교적 간단한 작업을 할 때, apply 함수가 가장 효율적


참고

https://medium.com/@rtjeannier/pandas-101-cont-9d061cb73bfc

  • 죠옹 2020.04.24 04:02 신고

    유용한 글 잘 읽고 갑니다!

  • 티비다시보기 2020.06.11 16:11

    많은 것을 배워갑니다

  • kann 2020.09.17 22:43

    좋은 글 감사드립니다.
    글 중 pd.get_value() 는 안되길래 찾아봤더니 pd.at()으로 대체되었더군요.
    따라서 하시다 막히시는 분들은 참고하세요.

    • 슨년 2020.10.12 14:02

      지나가다 댓글 남깁니다. 저도 막혀서 찾아보니 get_value()가 deprecated라고 하네요. at[]이나 iat[]사용하라고 합니다. 출처 남깁니다.
      https://pandas.pydata.org/pandas-docs/version/0.23.4/generated/pandas.DataFrame.get_value.html



Jupyter Lab – 단축키와 매직 기능

단축키 (Shortcut) 을 숙지하고 잘 사용할 수 있다면 더욱 생산적인 작업을 가능하게 할 것입니다. 본 포스팅에서는 자주 사용하는 Jupyter lab 의 단축키를 정리해보고자 합니다. 예를 들어, 주피터 랩에서는 새로운 cell 을 만들거나 작업이 끝난 cell 을 삭제할 일이 많습니다. 단축키를 사용하면, 단축키를 사용하지 않고 키보드를 통해 새로운 cell 을 추가하고 삭제하는 것보다 훨씬 빠르게 작업을 할 수 있습니다. 그리고 외우는데 시간이 오래걸리지도 않습니다!

Jupyter Lab의 단축키

  1. ESC  를 눌러 커맨드 모드로 진입하여 ENTER 를 통해 cell 을 수정할 수 있습니다. 아래 커맨드는 커맨드 모드에서 동작합니다. 
  2. A 는 현재 cell 위에 새로운 cell 을 추가합니다.
  3. B 는 현재 cell 밑에 새로운 cell 을 추가합니다.
  4. D + D D를 연속해서 두번 누르면 현재 cell 을 삭제합니다. 
  5. M 은 Markdown 셀로 변환하며, Y 는 Code 셀로 변환하고  R 은 Raw Cell 로 변환합니다.
  6. CTRL + B 화면을 더 크게 사용할 수 있습니다. 왼쪽 파일 탐색기가 사라집니다.
  7. SHIFT + M 두 개의 셀을 한개의 셀로 Merge 합니다.
  8. CTRL + SHIFT + – 현재 커서 위치를 기준으로 두 개의 셀로 구분합니다. 
  9. SHIFT+J or SHIFT + DOWN 현재 셀에서 아래쪽 위치로 새로운 셀을 같이 선택합니다. 
  10. SHIFT + K or SHIFT + UP 현재 셀에서 위쪽 위치로 새로운 셀을 같이 선택합니다. 
  11. CTRL + / 선택한 코드를 주석처리합니다.

기타 잘 알려지지 않은 단축키

  1. CTRL+D: 한줄 삭제하는 단축키입니다. 
  2. CTRL+[ or CTRL+]: 단체 indentation / Tab과 Shift+Tab 으로도 가능하지만, text editor 에서는 Shift+tab 이 안먹혀서 CTRL+[ 을 쓰면 유용합니다.  

Jupyter Lab 매직 기능 (Magic function)

매직 기능은 Ipython kernel 에서 제공하는 것이며, Jupyter lab 과 Jupyhter notebook 모두에서 작동합니다. 이는 어떤 언어를 선택하든 (예를 들어, Jupyter lab 에서 R을 사용하든 Python 을 사용하든) 동작합니다.

  1. %matplotlib inline 플롯을 화면 안에서 보여준다.
  2. %lsmagic 매직 기능에 어떤것들이 있는지 출력해준다. 
  3. %env 

    • %env 모든 환경변수를 출력한다.
    • %env var 해당 이름의 환경변수를 출력한다.
    • (%env var val) or (%env var=val) 환경변수를 설정한다.
  4. %run 

    • %run file_name 해당 이름의 .py 파일 또는 .ipynb 파일을 셀 안에서 실행한다. 
  5. %load 

    • %load source 해당 파일을 셀 안에 로드한다.
  6. %who = will list all variables that exist in the global scope. It can be used to see what all data_frames or any other variable is there in memory. 
    • %who: 현재 전역 환경의 모든 변수를 리스트한다. (메모리에 어떤 변수들이 올라와 있나 확인할 수 있다.)
    • %who df: 현재 선언된 dataframe 을 볼 수 있다. 
    • %whos: %who 와 비슷하지만 각 변수들에 대해 상세한 설명을 볼 수 있다. 
  7. %time 한 셀이 실행된 시간을 볼 수 있다. 
  8. %timeit 10만 번 실행하여 평균 시간을 잰다. 
  9. %writefile 

    • %writefile file_name 해당 파일의 셀의 아웃풋을 쓴다.
    • %writefile -a file_name 해당 파일의 셀의 아웃풋을 덧붙인다.

Jupyter configuration file (환경설정) 변경하기

주피터 랩에서는 print 문을 쓰지 않고 어떤 변수를 cell 에 입력했을 때, 가장 마지막 줄만 실행해서 결과로 내보냅니다. 이것이 불편하다면 아래 문장을 실행하면 됩니다. 개인적으로 유용하다고 생각합니다. 이렇게 하면 일일히 print 문으로 감싸줄 필요가 없게 됩니다.   

from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"

하지만 위 코드를 실행하는 것은 영구적으로 설정을 변경하지는 않는데, 영구적으로 설정을 변경하기 위해서는 configuration file 을 변경하면 됩니다. 이에 대한 설명은 해당 링크를 참고하시면 됩니다. configuration file 을 찾은 후에 아래 코드를 삽입해줍니다.

c.InteractiveShell.ast_node_interactivity = "all"

이외에도 주피터 랩에서는 더 많은 단축키와 매직 기능이 있습니다.



  • 표향검우 2020.06.04 10:57

    도움이 많이 되었습니다

  • 어쩌다쥬피터 2021.04.28 10:38

    감사합니다!~!~


PEP 은 무엇인가?


PEP 8 -- Style Guide for Python Code

PEP:8
Title:Style Guide for Python Code
Author:Guido van Rossum <guido at python.org>, Barry Warsaw <barry at python.org>, Nick Coghlan <ncoghlan at gmail.com>
Status:Active
Type:Process
Created:05-Jul-2001
Post-History:05-Jul-2001, 01-Aug-2013

PEP8은  Python Enhancement Proposals (PEP) 의 8 번째 내용으로, 더 나은 파이썬 코드를 작성하기 위한 하나의 코딩 규약 (Style Guide for Python Code)입니다. (https://www.python.org/dev/peps/pep-0008/)


Python PEP8 code convention cheat sheet


아래 파이썬 코드는 PEP8 style 을 활용한 코딩 예입니다. 일종의 cheat sheet로 이 코드만 알면 PEP8 에서 지향하는 파이썬 코딩 스타일의 대부분을 알 수 있습니다!


내용을 간단하게 요약하면 아래와 같습니다.


1. 모듈과 패키지 이름은 짧고 lower_case_with_underscore 이다. 

2. 다른 모듈에서 import 할 때 보일 필요가 없는 함수, 변수는 변수명 앞에 _를 추가한다. 

3. 상수는 A_CONSTANT 

4. 함수는 naming_convention

5. 클래스는 NamingConvention

6. 한 라인의 길이가 79 길이가 넘지 않도록 한다. (이것은 대략적인 A4 용지 사이즈이다.)


#! /usr/bin/env python # -*- coding: utf-8 -*- """This module's docstring summary line. This is a multi-line docstring. Paragraphs are separated with blank lines. Lines conform to 79-column limit. Module and packages names should be short, lower_case_with_underscores. Notice that this in not PEP8-cheatsheet.py Seriously, use flake8. Atom.io with https://atom.io/packages/linter-flake8 is awesome! See http://www.python.org/dev/peps/pep-0008/ for more PEP-8 details """ import os # STD lib imports first import sys # alphabetical import some_third_party_lib # 3rd party stuff next import some_third_party_other_lib # alphabetical import local_stuff # local stuff last import more_local_stuff import dont_import_two, modules_in_one_line # IMPORTANT! from pyflakes_cannot_handle import * # and there are other reasons it should be avoided # noqa # Using # noqa in the line above avoids flake8 warnings about line length! _a_global_var = 2 # so it won't get imported by 'from foo import *' _b_global_var = 3 A_CONSTANT = 'ugh.' # 2 empty lines between top-level funcs + classes def naming_convention(): """Write docstrings for ALL public classes, funcs and methods.     Functions use snake_case.     """ if x == 4: # x is blue <== USEFUL 1-liner comment (2 spaces before #) x, y = y, x # inverse x and y <== USELESS COMMENT (1 space after #) c = (a + b) * (a - b) # operator spacing should improve readability. dict['key'] = dict[0] = {'x': 2, 'cat': 'not a dog'} class NamingConvention(object): """First line of a docstring is short and next to the quotes.     Class and exception names are CapWords.     Closing quotes are on their own line     """ a = 2 b = 4 _internal_variable = 3 class_ = 'foo' # trailing underscore to avoid conflict with builtin # this will trigger name mangling to further discourage use from outside # this is also very useful if you intend your class to be subclassed, and # the children might also use the same var name for something else; e.g. # for simple variables like 'a' above. Name mangling will ensure that # *your* a and the children's a will not collide. __internal_var = 4 # NEVER use double leading and trailing underscores for your own names __nooooooodontdoit__ = 0 # don't call anything (because some fonts are hard to distiguish): l = 1 O = 2 I = 3 # some examples of how to wrap code to conform to 79-columns limit: def __init__(self, width, height, color='black', emphasis=None, highlight=0): if width == 0 and height == 0 and \ color == 'red' and emphasis == 'strong' or \ highlight > 100: raise ValueError('sorry, you lose') if width == 0 and height == 0 and (color == 'red' or emphasis is None): raise ValueError("I don't think so -- values are %s, %s" % (width, height)) Blob.__init__(self, width, height, color, emphasis, highlight) # empty lines within method to enhance readability; no set rule short_foo_dict = {'loooooooooooooooooooong_element_name': 'cat', 'other_element': 'dog'} long_foo_dict_with_many_elements = { 'foo': 'cat', 'bar': 'dog' } # 1 empty line between in-class def'ns def foo_method(self, x, y=None): """Method and function names are lower_case_with_underscores.         Always use self as first arg.         """ pass @classmethod def bar(cls): """Use cls!""" pass # a 79-char ruler: # 34567891123456789212345678931234567894123456789512345678961234567897123456789 """ Common naming convention names: snake_case MACRO_CASE camelCase CapWords """ # Newline at end of file


출처 - https://gist.github.com/RichardBronosky/454964087739a449da04



  • sinho3148 2020.11.17 17:35

    도움되는 글 잘 보고 가요


Python PDPbox 패키지 설치시 문제 해결


문제


 PDPbox 패키지를 설치하던 도중 아래 에러가 발생하면서 설치가 완료되지 않음


/usr/bin/ld: cannot find -lpython3.5m

 collect2: error: ld returned 1 exit status

 error: command 'gcc' failed with exit status 1


해결


sudo apt install python3.5-dev



구글링해도 안나와서 올립니다.


도움을 얻은 페이지

https://github.com/giampaolo/psutil/issues/1143



Python 중고급 속성 정리 (3) 가변길이 인수목록 받기(*args, **kargs)


이번 포스팅에서는 알아두면 정말 유용한 가변길이 인수목록 받기를 정리해보겠습니다. 파이썬 코드를 보다보면 가끔 *args, **kargs를 보실 수 있습니다. 이것은 함수의 인자(parameter 또는 arguments)가 가변 길이일 때 사용합니다. args, kargs는 원하는 이름대로 쓸 수 있고, *, **는 각각 non-keworded arguments, keworded arguments를 뜻합니다. 


예를 들어 아래 파이썬 코드를 보시면 쉽게 이해하실 수 있습니다. 


*args


def test_var_args(f_arg, *args):
    print("first normal arg:", f_arg)
    for arg in args:
        print("another arg through *argv:", arg)

test_var_args('yasoob', 'python', 'eggs', 'test')

first normal arg: yasoob

another arg through *argv: python

another arg through *argv: eggs

another arg through *argv: test


f_arg를 통해 하나의 인자를 전달 받고, *args를 통해 가변길이 인자를 전달 받습니다. 전달받은 인자들은 함수내에서 list처럼 다룰 수 있습니다. 


또한 아래 코드처럼, 인자들을 미리 정의한 후 전달할 수도 있습니다. 이를 통해 파라미터 정의부함수 실행부를 분리할 수 있다는 장점이 있습니다. 조금 더 깔끔하게 파이썬 코드를 관리할 수 있겠죠.


param = ['yasoob', 'python', 'eggs', 'test']
test_var_args('hi', *param)


*kargs


다음으로는 **, 별표가 2개 붙은 keworded argments 사용법입니다. **kwargs로 전달받은 인자는 함수 내에서 dictionary 처럼 다룰 수 있습니다.

def greet_me(**kwargs):
    print(kwargs.items())
    for key, value in kwargs.items():
        print("{0} = {1}".format(key, value))
        
greet_me(name="yasoob", school="snu")

dict_items([('name', 'yasoob'), ('school', 'snu')])

name = yasoob

school = snu


kwargs = {"name": 'yasoob', 'school' :"snu"}
greet_me(**kwargs)

이런식으로 dict로 미리 정의한 후에 ** 를 통해 함수의 인자로 넘길 수 있습니다. 매우 유용하죠.



Decorator와 결합하여 사용하기


*args와 **kargs는 decorator와 결합하여 사용하면 더 유용하게 사용할 수 있습니다. 예를 들어, 함수를 실행할 때, 그 함수의 이름을 출력하고 함수를 실행하는 decorator를 만든다고 해봅시다. 근데, decorator 함수에서는 함수의 인자를 지정해줘야하기 때문에 decoration하는 함수의 인자의 수가 모두 같아야합니다. 이럴 때, *args와 **kargs를 이용하면 decoration 하는 함수의 인자의 길이에 상관없이 decorator를 만들 수 있습니다. 


logging_decorator는 함수를 실행할 때, 그 함수의 이름을 출력하는 기능을 갖는 decorator입니다. 이 decorator 함수에 *args, **kargs를 지정하고 이것을 그대로 원래 함수로 넘겨주면 인자의 길이에 상관없이 decorator를 구현할 수 있습니다.


Decorator와 *args, **kargs 사용 예제


이 예제에서 squared_number와 add_number는 인자의 수가 다른데 이를 *args를 이용하여 해결하였습니다. 만약 *args를 활용하지 않는다면, 각각에 대하여 decorator를 만들어야하죠. 또한 마지막 함수(add_numbers) 의 경우 함수 안에 keword를 지정하고 있습니다. 또 함수 실행 시 **kargs 를 통하여 인자를 넘기는데, 이 때 decorator에서 **kargs를 정의해 놓으면 keworded arguments 들을 그대로 decoration 함수를 전달할 수 있기 때문에 가변길이 인자를 처리할 수 있게 됩니다.

from functools import wraps

def logging_decorator(f) : 
    @wraps(f)
    def wrapper_function(*args, **kargs) : 
        print(f.__name__ + " was called") 
        print("결과 : ", f(*args, **kargs))
    return wrapper_function

@logging_decorator
def square_number(x) :
    return x**2

square_number(2)

@logging_decorator
def add_number(x,y,z=0) :
    return x+y

add_number(2,3)

@logging_decorator
def add_numbers(x=3, y=4) : 
    return x+y

add_numbers()

kwargs = {"x": 3, "y": 5}

add_numbers(**kwargs)

square_number was called

결과 :  4

add_number was called

결과 :  5

add_numbers was called

결과 :  7

add_numbers was called

결과 :  8


Decorator는 무엇인가? 


- 다른 function의 기능을 조작하여 새로운 function을 만드는 것.

- 이 방법은 코드를 더욱 간결하게 만들며, 더욱 Pythonic 한 코드를 만들 수 있다

- 이러한 형태의 일종의 코드 Refactoring 및 중복 줄이기는 소프트웨어 공학에서 매우 중요하다!


Decoration을 안 한 초보 파이썬 코더의 코드


# 기존의 코드를 사용 안 하고, b_function()을 새롭게 정의함.
# 이렇게 하면 문제가, my foul smell을 삭제하고 싶으면, 함수 2개에서 모두 삭제해야한다. 
# 코드의 중복이 생김.
 
def a_function_requiring_decoration(): 
    print("I am the function which needs some decoration to remove my foul smell") 
 
def b_function(): 
    print("I am doing some boring work before executing a_func()") 
    print("I am the function which needs some decoration to remove my foul smell")   
    print("I am doing some boring work after executing a_func()")
 
b_function()

- 이 방법의 문제점은 코드의 중복이 생겨 수정이 필요할 시에 두 함수 모두를 수정해야한다는 것이다. 


간단한 Decoration의 구현 


# 아래 함수를 기능을 추가해서 decoration 해주는 함수 def a_new_decorator(a_func):


# 함수 안에 함수를 정의하고 함수를 리턴한다 def wrapTheFunction(): print("I am doing some boring work before executing a_func()") a_func() print("I am doing some boring work after executing a_func()")   return wrapTheFunction   # 이 함수를 decoration (기능을 추가) 하고 싶음 def a_function_requiring_decoration(): print("I am the function which needs some decoration to remove my foul smell")

a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration) a_function_requiring_decoration()

결과


I am doing some boring work before executing a_func()

I am the function which needs some decoration to remove my foul smell

I am doing some boring work after executing a_func()


- 이를 해결하는 방법이 바로 decoration 이다. 

- a_new_decorator 함수에 a_function_requiring_decoration 함수를 넘기는 방법을 통해 내용이 한 번만 쓰이게 된다. 

- 이를 통해 코드의 중복을 줄일 수 있다.



@ 키워드를 통한 decoration


# @를 붙임으로써 a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration) 이걸 안 해도 된다. @a_new_decorator def a_function_requiring_decoration(): """Hey you! Decorate me!""" print("I am the function which needs some decoration to " "remove my foul smell")   a_function_requiring_decoration()   # 근데 함수명이 이상하게 나옴. wrapTheFunction print(a_function_requiring_decoration.__name__)

- @ 키워드를 통해 a_function_requiring_decoration를 재정의 하지 않아도 된다.

- @ [함수명] 을 decoration 하고 싶은 함수 위에 붙여주면 된다. 

- 근데 함수 명이 wrapTheFunction 으로 decoration 한 함수의 이름이 그대로 나오게 된다. 

- 이를 해결하기 위해 wraps 를 이용한다.


# wraps를 이용해 함수명이 제대로 나오게 할 수 있음
from functools import wraps
# 최종적인 decorator의 일반적인 형태
# a_function_requiring_decoration을 a_new_decorator로 decorating 한다는 것이다. 이 때 decorate 할 함수는 a_func에 지정하고 이를 wraps로 받아서 그 아래 함수로 decoration 함
 
def a_new_decorator(a_func):
    @wraps(a_func)
    def wrapTheFunction(): 
        print("I am doing some boring work before executing a_func()") 
        a_func() 
        print("I am doing some boring work after executing a_func()")
 
    return wrapTheFunction
 
@a_new_decorator 
def a_function_requiring_decoration(): 
    """Hey you! Decorate me!"""
    print("I am the function which needs some decoration to " "remove my foul smell")
 
a_function_requiring_decoration() 
 
print(a_function_requiring_decoration.__name__)  # a_function_requiring_decoration


- 이렇게 decorator에 wraps를 붙여주면, 그 함수를 decoration 해주는 함수로 인식을 하게 된다. 

- 함수명도 기존의 함수 명인 a_function_requiring_decoration 을 따르게 된다.


Decoration 활용의 좋은 예 - Authentication


authentication_check라는 함수를 만들고 이곳에서는 웹어플리케이션서의 사용자 인증을 체크한다고 하자. 만약 다른 함수를 실행할 때, 그 함수의 위에다가 위에다가 @authentication_check 만 붙이면, authentication을 알아서 해주게 된다. 즉, Authentication - function 실행 순으로 알아서 만들어 준다.  이것이 좋은 점은 각 함수마다 authentication check를 안해도되고, authentication check logic을 딱 한 번만 쓰면 된다. 이런건 Java에서는 보통 상속을 이용해서 하는데, python에서는 decorator로 할 수 있다.


""" Use case : Authorization Now let’s take a look at the areas where decorators really shine and their usage makes something really easy to manage. Decorators can help to check whether someone is authorized to use an endpoint in a web application. They are extensively used in Flask web framework and Django. Here is an example to employ decorator based authentication:   """   # decorator 함수 def requires_auth(f): @wraps(f) def decorated(*args, **kwargs): auth = request.authorization if not auth or not check_auth(auth.username, auth.password): authenticate() return f(*args, **kwargs) return decorated

- 위 require_auth 함수는 어떤 함수 f를 받아서 그 전에 authorization 과정을 수행해주는 decorator이다. 



참고 - Intermediate Python