https://heytech.tistory.com/320

https://kodorricoding.tistory.com/16

4 Powerful Python Destructuring Features

Unpacking iterable, avoid indices, omit elements and more

Anupam Chugh

Jul 12, 2020·4 min read

Photo byAlmos BechtoldonUnsplash
 
Python is the most popular programming language largely owing to its pseudo-code like syntax.
 
It has some mighty powerful features and the fairly easy learning curve makes it the first choice language for developers all over the globe.
 
While list comprehensions are still the shiniest tool, destructuring is where the power lies.
 
Destructuring assignments are a shorthand syntax for mapping one or more variables from the left-hand side to the right-hand side counterparts. It’s handy in unpacking values as we shall see shortly.

Swapping And Updating Two Or More Values

Interchanging underlying values of variables is the most basic form of destructuring and it works in the following way:

a, b = b, a

Though at a high level it may seem that both the statementsa = bandb = aare only running simultaneously, there’s actually more going on under the hood.
 
Essentially, the right-hand side is evaluated first, convertingb,ainto a tuple. Subsequently, the tuple is unpacked and assigned to the left-hand side in the order left to right.
 
Consider the following snippet that updates the values ofaandb.

a = 1  
b = 2  
a, b = a + b, a

Itwon’tgive you a value 3 for bothaandb. Instead, the value ofbafter the assignment would be 1 whileabecomes 3.
 
The destructuring assignment allows us to swap more than two variables as well:

a,b,c = c,a,b

Unpacking Tuples And Iterables

Now, from what we’ve explored in the previous section, multiple assignments are fairly straightforward in Python. We can do it in a few ways:

a = b = c = 10  
a, b = 2, 3

The latter one is a destructuring assignment that works by creating a tuple of the right-hand side.
 
So, taking a cue from it we can opt to unpack values by using a tuple on the left-hand side as shown below:

a, b = (2, 3)  
(a, b) = 2, 3

xy = 1, 2  
a, b = xy

Destructuring assignment isn’t just restricted to tuples. In fact, you could use it on lists, strings, and pretty much any iterable.

a, b = [2,3]
names = "How are you?"  
a,b,c = names.split()

Using destructuring to split a string is really powerful but you need to ensure that the number of values on the left side must exactly match the number of values on the right side to avoidValueError

Recursive unpacking to access nested elements

We can unpack nested elements from lists or tuples with the same destructuring assignment syntax.

(a, b) = ([1, 2, 3], [1, 2])
([a, b], c) = ([1, 2], 3)
[a, b] = [[1,2],[3,4]]

Trailing comma

Trailing commas are optional for two or more element tuples. In case you’re using a single element tuple or you need to unpack only one element you can use the trailing comma in the following ways:

x = (1) #prints 1
x = (1,) #prints (1,)
a, = 1,
a, = [1]
[a] = [1]
#all print a == 1
a, = [1,2]
#ValueError too many elements

While parentheses are just syntactic sugar, it’s the comma that actually tells Python that the data type is a tuple. At times trailing comma can be missed which makes[a] = myLista better bet in terms of readability.

Access Elements Without Indices

Another place where the destructuring assignment is incredibly useful is when accessing values from a list. Without destructuring, more often than not, you’d end up accessing elements using indexes which can be needless.
 
By using the destructuring syntax in theenumeratefunction below, we’re able to access elements in a way that’s more concise and easy to comprehend.

myList = ["a", "b", "c"]  

for i, element in enumerate(myList):  
print(counter, element)

By following the same approach we can destructure dictionaries into key-value pairs or a list of tuples as shown below.

listOfTuples = [(1,0),(0,1)]
for i, (p1, p2) in enumerate(listOfTuples):
      #do your magic here

Select Or Omit Certain Values

By using the * operator (introduced in Python 3), we can choose to pick an optional unknown number of elements as shown below:

*a, b = 1, 2, 3, 4
# a is [1,2,3]
#b is 4

The asterisk operator creates a new list of all but the last element and puts them ina. This is useful when you need to extract sublists in Python.
 
We can also use the asterisk operator for getting the first and last elements from a list or even packing values and pass them into a function — which helps us get rid of long parameter lists in functions.

def sum(a, b, c):
    return a + b + c

x = (1, 2, 3)
print(result(*x))

Note: Dictionary unpacking requires**.
 
On the contrary, if you’re looking to drop or ignore the value in destructuring assignments using the_syntax is the way to go.*_lets you omit an unknown number of values.

a, _ = [1, 2]
first, *_, last = "Hello"
#first is 'H'
#last is 'o'

Conclusion

So, we saw how destructuring syntax helps in swapping and multi-assignments as well as in unpacking tuples, iterable and accessing elements without indexes in a for a loop.
 
I hope you make good use of the destructuring syntax in Python.
 
That’s it for this one. Thanks for reading.

intro

This is a quick one about how to configure Python’s build in logging system using an ini file.
Our application needs to import logging and logging.config to be able to use ini files.

import logging, logging.config
    # set up logging
    logging.config.fileConfig("log.ini")
    logger = logging.getLogger('sLogger')

    # log something
    logger.debug('debug message')
    logger.info('info message')
    logger.warn('warn message')
    logger.error('error message')
    logger.critical('critical message')

log.ini

log.ini looks like this:

[loggers]
keys=root,sLogger

[handlers]
keys=consoleHandler,fileHandler

[formatters]
keys=fileFormatter,consoleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_sLogger]
level=DEBUG
handlers=consoleHandler,fileHandler
qualname=sLogger
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=WARNING
formatter=consoleFormatter
args=(sys.stdout,)

[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=fileFormatter
args=('logfile.log',)

[formatter_fileFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=

[formatter_consoleFormatter]
format=%(levelname)s - %(message)s
datefmt=

output

This config will write warning or above out to stdout, whilst writing everything to a logfile. e.g.

$ python app.py
WARNING - warn message
ERROR - error message
CRITICAL - critical message

And in the logfile:

$ cat logfile.log
2015-11-18 15:23:39,071 - sLogger - DEBUG - debug message
2015-11-18 15:23:39,071 - sLogger - INFO - info message
2015-11-18 15:23:39,071 - sLogger - WARNING - warn message
2015-11-18 15:23:39,071 - sLogger - ERROR - error message
2015-11-18 15:23:39,071 - sLogger - CRITICAL - critical message

quatation

Pythonでロギングを学ぼう

学習履歴

■はじめに

python でテスト手法について色々と学んできたが、テストと合わせてログの出し方も
覚えておくと便利だ。

ここでは、ロギングの勉強をしよう。

■参考

この記事は、現役シリコンバレーエンジニアが教えるPython 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイルを参考にしています(すごく、おすすめです!)

[その他 プログラム記事]

■環境

python3
pycharm
Anaconda

■ロギング

ロギングの方法を実際にコードを書きながら覚えていこう。

1.ログレベル

python のロギングには、5 つのレベルがある。

1. CRITICAL
2. ERROR
3. WARNING
4. INFO
5. DEBUG

デフォルトでは、INFO, DEBUG は、出力されないようになっている。

logger_lesson.py
import logging

logging.critical('critical')
logging.error('error')
logging.warning('warning')
logging.info('info')
logging.debug('debug')
logger.実行結果
CRITICAL:root:critical
ERROR:root:error
WARNING:root:warning

ちなみに、出力結果に root とあるが、これはプログラムで最初に実行される
python ファイルを指していて、ここでは logger_lesson.py が root となる。

これからは、root が出力される処理を main 処理と呼ぶ。

2.ログレベルの変更

ログレベルは、basicConfig で変更することができる。

logger_lesson.py
import logging

# ログレベルを DEBUG に変更
logging.basicConfig(level=logging.DEBUG)

logging.critical('critical')
logging.error('error')
logging.warning('warning')
logging.info('info')
logging.debug('debug')
logger.実行結果
CRITICAL:root:critical
ERROR:root:error
WARNING:root:warning
INFO:root:info
DEBUG:root:debug

3.ロギングのフォーマット

ログの出し方にひと工夫いれられると便利だ。

ロギングのフォーマットを変更してみよう。

logger_lesson.py
import logging

# ログレベルを DEBUG に変更
logging.basicConfig(level=logging.DEBUG)

# 従来の出力
logging.info('error{}'.format('outputting error'))
logging.info('warning %s %s' % ('was', 'outputted'))
# logging のみの書き方
logging.info('info %s %s', 'test', 'test')

このようにフォーマットは、自由に書ける。
ただし、最後のフォーマットだけは、ロギングのみでできるらしい。

logger.実行結果
INFO:root:erroroutputting error
INFO:root:warning was outputted
INFO:root:info test test

4.ログをファイルに出力

ログはファイルに出力して管理するものだ。

basicConfig にファイルパスを指定して、ログを出力してみよう。

logger_lesson.py
import logging

# ログレベルを DEBUG に変更
logging.basicConfig(filename='logfile/logger.log', level=logging.DEBUG)

# 従来の出力
logging.info('error{}'.format('outputting error'))
logging.info('warning %s %s' % ('was', 'outputted'))
# logging のみの書き方
logging.info('info %s %s', 'test', 'test')

指定したログファイルがない場合は、自動的に作成されるはずだ。
※フォルダは自動作成されないので、自分で作成する必要がある

logger.log
INFO:root:erroroutputting error
INFO:root:warning was outputted
INFO:root:info test test

■ロギングフォーマッタ

フォーマットの変更

さっき勉強したフォーマットだが、もう少し掘り下げてみる。

basicConfig にフォーマット情報を渡すことができるんだ。

logger_lesson.py
import logging

# フォーマットを定義
formatter = '%(levelname)s : %(asctime)s : %(message)s'

# ログレベルを DEBUG に変更
logging.basicConfig(level=logging.DEBUG, format=formatter)

logging.info('%s %s', 'test', 'test')
logger.実行結果
INFO : 2018-02-24 18:03:23,364 : test test

asctime は 時間出力できる属性値だ。

他にも色々と種類があって、公式サイトで紹介されている。

C# とか PowerShell とかは、DateTime 的なオブジェクトを取ってきて、
フォーマットで整形してから出力しないといけないから、すごく便利だ。

■ロギング ロガー

basicConfig でログレベルなどを設定できる。

ロガーはこの設定を引き継いで、特定の処理にだけログの設定を変更することが
できる仕組みだ。

1.ロガーの設定

logger_lesson.py
import logging

# ロギングの基本設定(infoレベルを指定)
logging.basicConfig(level=logging.INFO)
logging.info('info')

# 現在のロギングの情報を取得(引数はファイル名)
logger = logging.getLogger(__name__)
# ロギングの設定を DEBUG に変更
logger.setLevel(logging.DEBUG)
logger.debug('debug')
logger.実行結果
INFO:root:info
DEBUG:__main__:debug

2.別の python ファイルで、ロガーを扱う

logging.getLogger(name) の name は、処理を実行したファイル名を
出力してくれるので割と便利。

例えば、別の python ファイルを作ってみよう。

logger_lesson2.py
import logging

logger = logging.getLogger(__name__)

def do_something():
    logger.info('from logger_lesson2')

呼び出し元の logger_leesson.py から呼び出したいのでコードを修正する。

logger_lesson.py
import logging

import logger_lesson2

logging.basicConfig(level=logging.INFO)

logging.info('info')

logger_lesson2.do_something()
logger.実行結果
INFO:root:info
INFO:logger_lesson2:from logger_lesson2

2 番目のログ出力に注目するとちゃんと実行ファイルの名前が確認できる。
これが、ロガーの基本的な使い方だ。

あとは、logger_lesson2.py だけログレベルを debug にしたい場合はこうする。

logger_lesson2.py
import logging

logger = logging.getLogger(__name__)
# setLevel で変更
logger.setLevel(logging.DEBUG)


def do_something():
    logger.info('from logger_lesson2')
    logger.debug('from logger_lesson2_debug')
logger.実行結果
INFO:root:info
INFO:logger_lesson2:from logger_lesson2
DEBUG:logger_lesson2:from logger_lesson2_debug

基本的には、main 処理を記述したファイルに、basicConfig を設定し、
他のファイルは logger を設置して、個々のカスタマイズを加えていく開発が
主流のようだ。

■ロギング ハンドラ

main 処理に記述する basiConfig の filename にログのパスを記述すれば、
ログが書き込まれることについては既に説明した。

main 処理以外のログ情報をファイルに出力する為には、ハンドラを使う必要がある。

1.ハンドラの使い方

ハンドラは、main 処理以外の python ファイルに書くので、logger_lesson2.pyに
書こう。

logger_lesson2.py
import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

##ハンドラ取得
get_handler = logging.FileHandler('logfile/logger.log')
logger.addHandler(get_handler)


def do_something():
    logger.info('from logger_lesson2')
    logger.info('from logger_lesson2_debug')

FileHandler にログの出力先パスを渡した状態で変数に格納後、
ハンドラのプロパティとして追加するとログが出力できるようになるらしい。

つまり、ファイルに書き込まれる情報は、常に logger を使う必要がある。

logger.log
from logger_lesson2
from logger_lesson2_debug

2. ハンドラの種類

ハンドラにも色々と種類がある。

詳しくは、公式ページを参照してほしい。

■ログのフィルタ

出力されるログをフィルタリングしたいときがある。
(例えばパスワードなどの情報)

python では、ログのフィルタも可能だ。

フィルタの設定

フィルタの設定手順は以下のようになる。

  1. ログ専用クラスを作成し、logging.Filter の情報を継承させる
  2. フィルタ条件を設定する
  3. logger プロパティにフィルタ情報を追加する
logger_lesson.py
import logging

logging.basicConfig(level=logging.INFO)


# ログ専用クラスを作成し、logging.Filter の情報を継承させる
class NoPassFilter(logging.Filter):
    # フィルタ条件を設定する
    def filter(self, record):
        log_message = record.getMessage()
        return 'password' not in log_message

logger = logging.getLogger(__name__)
# logger プロパティにフィルタ情報を追加する
logger.addFilter(NoPassFilter())
logger.info('from main')
logger.info('from main password = "test')

password という文字列が含まれていたらログ出力しないといった設定だ。
出力結果には、'from main password = "test' が出力されないはずだ。

logger.結果
INFO:__main__:from main

■ロギングコンフィグ

ロギング設定をまとめて管理できれば、大変便利だ。

作り方は、2 種類ある。

1.ini ファイルで作成

logging.ini ファイルに logger の情報を書き出す方法だ。

説明は、コメントに書いてあるので、コピペして実際に動かしてみてほしい。

logging.ini
;---大元の設定
;--ロガーの対象一覧
[loggers]
;logging.getLogger(logger name)のlogger nameに指定する名前
;root は、 mainで実行した時に自動的に取得できる名前
;outputLoggingのように独自のロガー名を追加できる
keys=root, outputLogging

;ハンドラの設定(customStreamHandlerは任意で付けた名前)
[handlers]
keys=customStreamHandler

;フォーマットの設定(customFormatは任意で付けた名前)
[formatters]
keys=customFormat

;---詳細の設定
;logger_[loggers の key 名] とし、詳細を記述していく
[logger_root]
level=WARNING
handlers=customStreamHandler

[logger_outputLogging]
level=DEBUG
handlers=customStreamHandler
qualname=outputLogging
propagate=0

;---詳細の設定に追加するためのオプション
;handler_[handlers の 名前]
[handler_customStreamHandler]
class=StreamHandler
level=DEBUG
formatter=customFormat
args=(sys.stderr,)

;---formatter_[formatter の 名前]
[formatter_customFormat]
format=%(asctime)s %(name)-12s %(levelname)-8s %(message)s
logger_lesson.py(main処理)
import logging.config

import logger_lesson2

# main 処理なので、__name__ には、root が入る
logging.config.fileConfig('logging.ini')
logger = logging.getLogger('__name__')

logger.debug('debug message')
logger.info('info message')
logger.warning('warning message')
logger.error('error message')
logger.critical('critical message')

logger_lesson2.do_something()
logger_lesson2.py
import logging


def do_something():
    # outputLogging を入れてみる
    logger = logging.getLogger('outputLogging')
    logger.debug('debug message')
    logger.info('info message')
    logger.warning('warning message')
    logger.error('error message')
    logger.critical('critical message')
logger.実行結果
2018-02-24 23:36:08,575 __name__     WARNING  warning message
2018-02-24 23:36:08,576 __name__     ERROR    error message
2018-02-24 23:36:08,576 __name__     CRITICAL critical message
2018-02-24 23:36:08,576 outputLogging DEBUG    debug message
2018-02-24 23:36:08,576 outputLogging INFO     info message
2018-02-24 23:36:08,576 outputLogging WARNING  warning message
2018-02-24 23:36:08,576 outputLogging ERROR    error message
2018-02-24 23:36:08,576 outputLogging CRITICAL critical message

2.dictConfigを使う方法

ロガーの dictConfig関数を使って同じようなことができる。

今度は、logger_lesson.py に書いてみる。

logger_lesson.py
import logging.config

import logger_lesson2

logging.config.dictConfig({
    'version': 1,

    # フォーマットの設定
    'formatters': {
        'customFormat': {
            'format': 'format=%(asctime)s %(name)-12s %(levelname)-8s %(message)s'
        },
    },
    # ハンドラの設定
    'handlers': {
        'customStreamHandler': {
            'class': 'logging.StreamHandler',
            'formatter': 'customFormat',
            'level': logging.DEBUG
        }
    },

    # ロガーの対象一覧
    'root': {
        'handlers': ['customStreamHandler'],
        'level': logging.WARNING,
    },
    'loggers': {
        'outputLogging': {
            'handlers': ['customStreamHandler'],
            'level': logging.DEBUG,
            'propagate': 0
        }
    }
})


logger = logging.getLogger('__name__')

logger.debug('debug message')
logger.info('info message')
logger.warning('warning message')
logger.error('error message')
logger.critical('critical message')

logger_lesson2.do_something()

logger_lesson2.py は変更なしだ。

logger.実行結果
format=2018-02-24 23:58:13,602 __name__     WARNING  warning message
format=2018-02-24 23:58:13,602 __name__     ERROR    error message
format=2018-02-24 23:58:13,603 __name__     CRITICAL critical message
format=2018-02-24 23:58:13,603 outputLogging DEBUG    debug message
format=2018-02-24 23:58:13,603 outputLogging INFO     info message
format=2018-02-24 23:58:13,603 outputLogging WARNING  warning message
format=2018-02-24 23:58:13,603 outputLogging ERROR    error message
format=2018-02-24 23:58:13,603 outputLogging CRITICAL critical message

どちらかやりやすい方法で試してみるといいかもしれない。
実際の開発現場だと、dictConfig の方がよくつかわれているらしい。

■まとめ

・ロギングの使用方法について学んだ。


https://brunch.co.kr/@princox/180


이 글은 파이썬의 문법을 모르면 이해하기 어렵습니다.


python의 함수 작성 요령, 인자(argument)와 파라미터를 이해한다면 도움이 되는 내용입니다.





아니 이것은 포인터인가?!


C언어를 배울 때 가장 힘든 그것!


바로 주소값을 가지는 포인터입니다!


근데.. 사자를 피했더니 호랑이를 만났다고 ㅠㅠ


파이썬을 배우려는데 별 표시(asterisk)가?!


걱정 마세요.


파이썬에서 *, **는 주소값을 저장하는 의미가 아닙니다.


바로 여러 개의 인수를 받을 때, 키워드 인수를 받을 때 사용하는 표시입니다.







먼저 *args부터 알아보자


오늘 아주 그냥 한 방에 이걸 끝내보자고요.


*args는 *arguments의 줄임말입니다.


그러니까.. 꼭 저 단어를 쓸 필요가 없다는 말입니다.


*a 라고 써도 되고, *asdfadfads라고 적어도 되고, *myNames라고 적어도 된다는 말입니다.


이 지시어는 여러 개(복수개의)의 인자를 함수로 받고자 할 때 쓰입니다.


무슨 말인지 예시로 넘어가 보죠.



사람의 이름을 받아서 성과 이름을 분리한 후 출력하고 싶습니다.


근데 문제가 생겼습니다.


사용자가 '몇 개의' 이름을 적어 넣을지 알 수가 없는 것이죠.


이럴 때 *args를 인자로 받습니다.



다음 함수와 같이 작성했습니다.


lastName_and_FirstName 함수에서는 인자를 *Names를 받습니다.


*Names는 아무 단어로 바꿔도 상관없습니다. *a, *b, *asdaodsaos 모두 동작합니다.


args를 출력해보면 'tuple'(튜플) 형태임을 알 수 있습니다.


여러 개의 인자로 함수를 호출할 경우, 함수 내부에서는 튜플로 받은 것처럼 인식한다는 것이죠.


한 번 직접 출력해볼까요?



역시나!


튜플 형태로 들어와서 출력된다는 것을 알 수 있습니다.


이렇게 여러 개의 인자를 받기 위해서 *args의 형태로 함수 파라미터를 작성합니다.







**kwargs을 알아보자



kwargs는 keyword argument의 줄임말로 키워드를 제공합니다.


바로 예시를 볼까요?



**kwargs는 (키워드 = 특정 값) 형태로 함수를 호출할 수 있습니다.

그것은 그대로 딕셔너리 형태로 {'키워드': '특정 값'} 요렇게 함수 내부로 전달됩니다.


그렇게 전달받은 딕셔너리를 마음대로 조리하면 되겠죠?


이것을 또 다르게 사용하는 방법으로는


특정 키워드에 반응하여 함수를 작성하는 방법이 있습니다.


제 이름인 ant가 입력되었을 때는 다르게 반응하고 싶게 만들어봅시다.






키워드(딕셔너리에서 key부분의 값이)가 ant로 들어왔을 때는

"주인님 오셨군요."를 출력하여 저만의 자비스를 만들었습니다.


이제 함수를 부를 때 제 이름을 키워드로 넣으면 자비스를 부를 수 있겠네요.







우리가 사용하는 것 중에서 kwargs가 있을까요?


우리가 익숙하게 사용하는 print문에서도 특정 키워드로 다르게 동작하게 할 수 있습니다.


먼저 print의 함수의 docstring을 조회해봅시다.




print 함수를 보니 몇 가지 신기한 게 있습니다.


end='\n'가 기본값(default)으로 설정되어 있습니다.


한 번 다른 것으로 입력해볼까요?



print 함수의 설명처럼 end의 키워드 값을 바꿔줬더니 다르게 동작합니다.


원래는 하나 띄어쓰기가 매 출력문에서 붙었는데,

이제는 제가 그 값을 바꿀 수 있게 되었네요!!


이렇게 kwargs는 사용자가 원하는 방식대로 동작하게 하는 함수를 작성할 수 있습니다.





더 알아보기

조금 변형해보기

*args의 사용법은 순서도 중요합니다.


가령 이렇게 한 번 해봅시다.

여러개의 블로그 설명을 함수로 받아서 출력하고 싶습니다.


그러면 이렇게 할 수 있겠죠.

이렇게 변수를 선언한 다음에


blog_printer의 argument로 저 3개의 변수를 줘보도록 하겠습니다.


그러면 이렇게 출력되겠죠?


*args말고도 변수를 하나 더 매개변수로 추가해봅시다.



이렇게도 가능합니다. (name을 함수의 파라미터로 추가했습니다.)


이 상태에서 name과 *blogs의 순서를 바꾸면 어떻게 될까요?




엥? 순서만 바꿨는데 오류가 난다?


네 오류가 납니다.


*args는 일반 변수보다는 뒤에 위치해있어야 합니다.


파이썬은 어디서부터 어디까지를 *변수에 담을지 알 수 없습니다.


그래서 맨 앞에 특정 변수를 명시해두고, 그 뒤에는 *args로 아규먼트를 넣어줘야합니다.




*args *kwargs를 둘 다 써보자.

둘 다 써보는 것은 어떨까요?

위의 내용을 조금 변형해서 써봅시다.


함수명 바로 아래에 다중 문자열(''')을 넣어서 함수의 docstring으로 만들어 줍니다.


저렇게 함수에다가 docstring을 넣으면 help()함수로 함수의 설명을 읽을 수 있습니다.


*blogs에는 블로그1, 블로그2, 블로그3을 아규먼트로 넣었고,

**blogs_benefits는 블로그수익1, 블로그수익2, 블로그수익3 으로 각각 키워드를 넣어서 입력했습니다.


보다시피 *blogs , **blogs_benefits를 순서대로 넣은 것을 볼 수 있습니다.


순서를 바꾸면 어떻게 될까요?

작동하지 않습니다.


이를 통해서 *변수, **변수도 순서가 있다는 것을 확인할 수 있습니다.





결론 및 요약


>>> def func(*args, **kwargs):
print(type(args))
print(args)
print(type(kwargs))
print(kwargs)
>>> func()
<class 'tuple'>
()
<class 'dict'>
{}
>>> func('gaga','gaha')
<class 'tuple'>
('gaga', 'gaha')
<class 'dict'>
{}
>>> func(gaga='gaga')
<class 'tuple'>
()
<class 'dict'>
{'gaga': 'gaga'}
>>> func('gaga','gaha',gaga='haha')
<class 'tuple'>
('gaga', 'gaha')
<class 'dict'>
{'gaga': 'haha'}
>>> func('gaga','gaha',gaga='haha',haha='gewe')
<class 'tuple'>
('gaga', 'gaha')
<class 'dict'>
{'gaga': 'haha', 'haha': 'gewe'}
>>> func(gaga='haha, 'gaga')
SyntaxError: invalid syntax
함수의 파라미터 순서 : 일반 변수, *변수, **변수
*변수 -> 여러개가 아규먼트로 들어올 때, 함수 내부에서는 해당 변수를 '튜플'로 처리한다.
**변수 -> 키워드='' 로 입력할 경우에 그것을 각각 키와 값으로 가져오는 '딕셔너리'로 처리한다.
충분히 복습하면서 연습해보는 것을 권장합니다.







https://dojang.io/mod/page/view.php?id=2431

42.5 클래스로 매개변수와 반환값을 처리하는 데코레이터 만들기

지금까지 클래스로 데코레이터를 만들어보았습니다. 클래스로 만든 데코레이터도 매개변수와 반환값을 처리할 수 있습니다. 다음은 함수의 매개변수를 출력하는 데코레이터입니다(여기서는 위치 인수와 키워드 인수를 모두 처리하는 가변 인수로 만들었습니다).

decorator_class_param_return.py

class Trace:
    def __init__(self, func):    # 호출할 함수를 인스턴스의 초깃값으로 받음
        self.func = func         # 호출할 함수를 속성 func에 저장
 
    def __call__(self, *args, **kwargs):    # 호출할 함수의 매개변수를 처리
        r = self.func(*args, **kwargs) # self.func에 매개변수를 넣어서 호출하고 반환값을 변수에 저장
        print('{0}(args={1}, kwargs={2}) -> {3}'.format(self.func.__name__, args, kwargs, r))
                                            # 매개변수와 반환값 출력
        return r                            # self.func의 반환값을 반환
 
@Trace    # @데코레이터
def add(a, b):
    return a + b
 
print(add(10, 20))
print(add(a=10, b=20))

실행 결과

add(args=(10, 20), kwargs={}) -> 30
30
add(args=(), kwargs={'a': 10, 'b': 20}) -> 30
30

클래스로 매개변수와 반환값을 처리하는 데코레이터를 만들 때는 __call__ 메서드에 매개변수를 지정하고, self.func에 매개변수를 넣어서 호출한 뒤에 반환값을 반환해주면 됩니다. 여기서는 매개변수를 *args**kwargs로 지정했으므로 self.func에 넣을 때는 언패킹하여 넣어줍니다.

    def __call__(self, *args, **kwargs):    # 호출할 함수의 매개변수를 처리
        r = self.func(*args, **kwargs) # self.func에 매개변수를 넣어서 호출하고 반환값을 변수에 저장
        print('{0}(args={1}, kwargs={2}) -> {3}'.format(self.func.__name__, args, kwargs, r))
                                            # 매개변수와 반환값 출력
        return r                            # self.func의 반환값을 반환

물론 가변 인수를 사용하지 않고, 고정된 매개변수를 사용할 때는 def __call__(self, a, b):처럼 만들어도 됩니다.

42.5.1  클래스로 매개변수가 있는 데코레이터 만들기

마지막으로 매개변수가 있는 데코레이터를 만들어보겠습니다. 다음은 함수의 반환값이 특정 수의 배수인지 확인하는 데코레이터입니다.

decorator_class_parameter.py

class IsMultiple:
    def __init__(self, x):         # 데코레이터가 사용할 매개변수를 초깃값으로 받음
        self.x = x                 # 매개변수를 속성 x에 저장
 
    def __call__(self, func):      # 호출할 함수를 매개변수로 받음
        def wrapper(a, b):         # 호출할 함수의 매개변수와 똑같이 지정(가변 인수로 작성해도 됨)
            r = func(a, b)         # func를 호출하고 반환값을 변수에 저장
            if r % self.x == 0:    # func의 반환값이 self.x의 배수인지 확인
                print('{0}의 반환값은 {1}의 배수입니다.'.format(func.__name__, self.x))
            else:
                print('{0}의 반환값은 {1}의 배수가 아닙니다.'.format(func.__name__, self.x))
            return r               # func의 반환값을 반환
        return wrapper             # wrapper 함수 반환
 
@IsMultiple(3)    # 데코레이터(인수)
def add(a, b):
    return a + b
 
print(add(10, 20))
print(add(2, 5))

실행 결과

add의 반환값은 3의 배수입니다.
30
add의 반환값은 3의 배수가 아닙니다.
7

먼저 __init__ 메서드에서 데코레이터가 사용할 매개변수를 초깃값으로 받습니다. 그리고 매개변수를 __call__ 메서드에서 사용할 수 있도록 속성에 저장합니다.

    def __init__(self, x):         # 데코레이터가 사용할 매개변수를 초깃값으로 받음
        self.x = x                 # 매개변수를 속성 x에 저장

지금까지 __init__에서 호출할 함수를 매개변수로 받았는데 여기서는 데코레이터가 사용할 매개변수를 받는다는 점 꼭 기억해두세요.

이제 __call__ 메서드에서는 호출할 함수를 매개변수로 받습니다. 그리고 __call__ 메서드 안에서 wrapper 함수를 만들어줍니다. 이때 wrapper 함수의 매개변수는 호출할 함수의 매개변수와 똑같이 지정해줍니다(가변 인수로 작성해도 됨).

    def __call__(self, func):      # 호출할 함수를 매개변수로 받음
        def wrapper(a, b):         # 호출할 함수의 매개변수와 똑같이 지정(가변 인수로 작성해도 됨)

wrapper 함수 안에서는 func의 반환값이 데코레이터 매개변수 x의 배수인지 확인합니다. 이때 데코레이터 매개변수 x는 속성에 저장되어 있으므로 self.x와 같이 사용해야 합니다. 그리고 배수 확인이 끝났으면 func의 반환값을 반환합니다. 마지막으로 wrapper 함수를 다 만들었으면 return으로 wrapper 함수를 반환합니다.

    def __call__(self, func):      # 호출할 함수를 매개변수로 받음
        def wrapper(a, b):         # 호출할 함수의 매개변수와 똑같이 지정(가변 인수로 작성해도 됨)
            r = func(a, b)         # func를 호출하고 반환값을 변수에 저장
            if r % self.x == 0:    # func의 반환값이 self.x의 배수인지 확인
                print('{0}의 반환값은 {1}의 배수입니다.'.format(func.__name__, self.x))
            else:
                print('{0}의 반환값은 {1}의 배수가 아닙니다.'.format(func.__name__, self.x))
            return r               # func의 반환값을 반환
        return wrapper             # wrapper 함수 반환

데코레이터를 사용할 때는 데코레이터에 ( )(괄호)를 붙인 뒤 인수를 넣어주면 됩니다.

@데코레이터(인수)
def 함수이름():
    코드
@IsMultiple(3)    # 데코레이터(인수)
def add(a, b):
    return a + b

지금까지 데코레이터를 사용하는 방법을 배웠는데 문법이 조금 복잡했습니다. 여기서는 데코레이터가 기존 함수를 수정하지 않으면서 추가 기능을 구현할 때 사용한다는 점만 기억하면 됩니다. 보통 데코레이터는 프로그램의 버그를 찾는 디버깅, 함수의 성능 측정, 함수 실행 전에 데이터 확인 등에 활용합니다(앞에서 만든 함수의 시작과 끝을 출력하는 데코레이터, 매개변수와 반환값을 출력하는 데코레이터는 디버깅에 활용할 수 있습니다. 그리고 함수 실행 전에 데이터를 확인하는 예제는 연습문제에서 소개하겠습니다).


+ Recent posts