読者です 読者をやめる 読者になる 読者になる

pikesaku’s blog

個人的なプログラム勉強メモです。記載内容について一切の責任は持ちません。

Pythonの関数デコレータについて

関数デコレータとは?

関数に機能を追加する機能

ポイント

Pythonの以下性質を利用
・関数もオブジェクト
・関数自体を引数や戻り値として利用可能

利用イメージは以下の通り
・関数Aに引数として、関数Bを指定
・戻り値として、関数Bの機能に関数Aの機能を追加した関数を返す

コードのイメージ
関数base_funcに機能を追加する場合、以下の記述が可能

base_func = add_func(base_func)


このシンタックスシュガーが関数デコレータ。以下記述が可能。

@add_func
def base_func():
    処理


サンプルコード

関数デコレータなしで記述した場合


コード

def add_func(f):
    def func():
        f()
        print("add func called")
    return func


def base_func():
    print("base func called")


print("# 機能追加前")
base_func()

print("# 機能追加後")
base_func = add_func(base_func)
base_func()


実行結果

# 機能追加前
base func called
# 機能追加後
base func called
add func called


関数デコレータで記述した場合


コード

## デコレータありの場合

def add_func(f):
    def func():
        f()
        print("add func called")
    return func


@add_func
def base_func():
    print("base func called")


base_func()


実行結果

base func called
add func called


サンプルコードの問題点


オブジェクトの属性情報(関数名等)が変わってしまう。

さっきのコードの最後の行に以下を追加すると、、、

print(base_func.__name__)


実行結果は以下になる。

base func called
add func called
func


関数名が、add_func内側で定義した関数名になっている。
これを回避するには、func関数をfunctoolsモジュールのwraps関数でデコレートする。

コード

from functools import wraps


def add_func(f):
    @wraps(f)
    def func():
        f()
        print("add func called")
    return func


@add_func
def base_func():
    print("base func called")


base_func()
print(base_func.__name__)


実行結果

base func called
add func called
base_func


引数付き関数のデコレータ


コード

rom functools import wraps

def add_func(f):
    @wraps(f)
    def func(a):
        f(a)
        print("add func called")
    return func


@add_func
def base_func(a):
    print("base func called " + a)


base_func("hoge")
print(base_func.__name__)


実行結果

base func called hoge
add func called
base_func


おぼえておくべき点


・base_funcを実行すると、add_funcの第一引数(f)としてbase_funcが渡される。
・base_func実行時の引数は、add_func内で定義した関数(func)の引数(a)にセットされる。

デコレータ記述(@add_func)に引数をつける事も可能


add_func関数内でネストする必要あり。コードは以下の通り。

コード

from functools import wraps


def add_func(b):
    def func1(f):
        @wraps(f)
        def func2(a):
            print(b)
            f(a)
            print("add func called")
        return func2
    return func1


@add_func("moge")
def base_func(a):
    print("base func called " + a)


base_func("hoge")
print(base_func.__name__)


実行結果

moge
base func called hoge
add func called
base_func


最後に


・参考URLに記載の通りビジネスロジックと管理ロジックを分けるために有用
・ただ、自分はインフラSEだし、そんな本格的なコードは書いてません。。。。。関数に渡されたオブジェクトをデバッグとしてログに出したい時などに使えるかも

サンプルコード

def deco(f):
    def func(*args):
        print("#DEBUG " + f.__name__ + " args")
        print(args)
        f(*args)
    return func

@deco
def test1(a, b):
    print("HOGE!!")

@deco
def test2(b):
    print("MOGE!!")


def main():
    test1(1,2)
    test2(3)

main()


実行結果

#DEBUG test1 args
(1, 2)
HOGE!!
#DEBUG test2 args
(3,)
MOGE!!


最後に2


参考URLの以下記述について

以下はよくない

def web_lookup(url, saved={}):
    if url in saved:
        return saved[url]
    page = urllib.urlopen(url).read()
    saved[url] = page
    return page


以下がよい

def cache(func):
    saved = {}
    @wraps(func)
    def newfunc(*args):
        if args in saved:
            return saved[args]
        result = func(*args)
        saved[args] = result
        return result
    return newfunc

@cache
def web_lookup(url):
    return urllib.urlopen(url).read()


と記述があったが、"return urllib.urlopen(url).read()"が理解できなかった。このコードは実行されないのでは?と思った。理由は、newfuncでreturnを定義している為。ただ、よくよく考えるとresult = func(*args)で実行される。すっきり!

Pythonのlambda式について

lambda式とは?


無名関数を記述する方法

無名関数とは?


・関数宣言をせずに使える関数
・一度だけしか使われない使い捨ての関数。名前をつける必要がないため無名関数と呼ばれる

使い方

lambda 引数:返り値

コロンの左側に記述されたオブジェクトは引数。
式の中のオブジェクト名はxがよく使われているが、aでもhogeでもOK

例) lambda式を使わない場合


コード

def test(a):
    return a * 2

b = test(3)
print(b)

実行結果

6

例) lambda式を使う場合


コード

b = lambda a: a * 2
print(b(3))
print(type(b))

実行結果

6
<class 'function'>

よく見る使われ方


高階関数と一緒に利用される。

高階関数は、関数を引数にしたり戻り値にする関数

例) map関数と使うケース


map関数は、引数に関数とリストを取る。
リストの各要素に対し指定された関数処理をするgeneratorオブジェクトを返す。

コード

l = map(lambda x: x ** 2, [1, 2, 3])
print(l.__next__())
print(l.__next__())
print(l.__next__())

実行結果

1
4
9

例) sorted関数のkeyパラメータとの使われ方1


sorted関数のkeyパラメータは比較を行う前にリストの各要素に対して呼び出される★関数★を指定するパラメータ
並べ替えは、関数の実行結果を使って行われる。

ソート HOW TO — Python 3.6.1 ドキュメント

コード

a = [1,2,3]
b = sorted(a)
print(b)
b = sorted(a, key=lambda x: x * -1)
print(b)

実行結果

[1, 2, 3]
[3, 2, 1]

各要素に対してlambda式で指定した無名関数(-1で掛け算)を実行した結果を使ってソートする。

例) sorted関数のkeyパラメータとの使われ方2

コード

a = [[0,10], [1,9], [2,8]]
b = sorted(a)
print(b)
b = sorted(a, key=lambda x: x[1])
print(b)

実行結果

[[0, 10], [1, 9], [2, 8]]
[[2, 8], [1, 9], [0, 10]]

各要素がリストの場合、その2個目の要素の値を使ってソートする。

Pycharm使い方メモ

他便利な使い方

最強のPython統合開発環境PyCharm - Qiita

検索機能

指定したキーワードにマッチするclass、ファイル、symbolを検索してくれる

コード実行

control + r

コード解析(超便利!)

①CMDキー+クリックで定義元にジャンプする。

②View → Tool Window → Structure選択で左部にプログラム構造が表示される。

Pythonネットワークプログラミング勉強コード

簡易tcpサーバ

動作

・受信データを16進数でクライアントに返す。
・接続開始、終了情報をサーバ側コンソールに出力する。

コード

# -*- coding: utf-8 -*-

import sys
import socket
import threading

CS          = 'utf-8'
PORT = sys.argv[1]

# PORTはstr型(Python3)
# isdigitメソッドで数字が判定
if not PORT.isdigit():
    print('invaild argment')
    exit(1)


def do_server():
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.bind(('0.0.0.0', int(PORT)))
    server.listen(5)
    while True:
        client_socket, addr = server.accept()
        # targetに受信データを処理する関数オブジェクト(client_hander)を指定。
        # 実行結果を引数にするわけではない為、client_handerの後に()はつけない。
        client_addr = str(addr[0]) + ':' + str(addr[1])
        print('Connected from: ' + client_addr)
        client_thread = threading.Thread(target=client_handler, args=(client_socket,client_addr))
        client_thread.start()


def client_handler(client_socket,client_addr):
    while True:
        # dataにクライアント送信データが格納(byte型)される。
        data = client_socket.recv(1024)

        if not data:
            # 切断時処理
            # ncコマンドで接続しCTRL+Cを送信するとクライアントはFINパケット
            # を送信しTCP接続は切れる。dataにはbyte型の空データ(0x00)が格納される。
            # TCP1接続が切れた状態で、client_socket.recvが実行されるとdataに
            # 空データが格納される。その為、LOOPを抜ける必要がある。
            print('Dosconnected from: ' + client_addr)
            break
        else:
            # 受信データを16進数にしたものをクライアントに返す
            r = 'You send following data: '
            for i in data:
                # byte型にイテレータ処理をするとint型になる。 
                # int型のiを、formatメソッドでstr型に変換
                r += 'x{0:02x}'.format(i)
             # クライアントにデータを返す時は、byte型にして送る。
            r += '\n'
            client_socket.send(r.encode(CS))

    client_socket.close()


def main():
    do_server() 


if __name__ == '__main__':
    main()

実行例

クライアント側操作
①ncコマンドでサーバに接続
②"abc"を入力してEnter
③何も入力せずEnter
④CTRL+C

サーバ側出力

$ python ./tcp_srv.py 10001
Connected from: 127.0.0.1:59653
Dosconnected from: 127.0.0.1:59653

クライアント側出力

$ nc 127.0.0.1 10001
abc
You send following data: x61x62x63x0a

You send following data: x0a
^C
$ 

ファイルを指定した行数で分割するコマンド

splitってコマンドがある

動きはこんな感じ

$ seq 100 > ./all.txt && split -l 30 -d ./all.txt bunkatsu_
$ wc -l  ./bunkatsu*
 30 ./bunkatsu_00
 30 ./bunkatsu_01
 30 ./bunkatsu_02
 10 ./bunkatsu_03
100 合計
$ 


・第一引数は入力ファイル
・第二引数は出力ファイルのファイル名のsuffix
・-lで分割する行数を指定
・-dで生成ファイルのシーケンスを数字にする。(-dなしの場合、a-z)

muttでメール送信

使い方

echo "MESSAGE" | mutt [OPTION] 宛先アドレス
mailコマンドと似た感じで使える

参考

man mutt(1)
man muttrc(5)


件名設定

$ echo test | mutt -s 'test' root@example.com


MTA指定

$ echo test | mutt -e 'set smtp_url="smtp://127.0.0.1:25"' root@example.com


送信元(Envelope from)指定

$ echo test | mutt -e 'set smtp_url="smtp://127.0.0.1:25"' -e 'set envelope_from_address="hoge@example.com"' root@example.com


添付ファイル送信

$ echo test | mutt -a /etc/hosts /bin/cp -- root@example.com

複数ファイル指定可能、MIMEエンコーディングされる。
テキストデータはcontent-typeがtext/plain、バイナリファイルはapplication/octet-stream
添付ファイルと宛先アドレスの間に、--を入れる必要あり


任意ヘッダ設定

$ echo test | mutt -e 'my_hdr x-hoge1: hoge1' -e 'my_hdr x-hoge2: hoge2'  root@example.com


指定ファイルのデータを本文に利用

$ mutt -s   "test" both@example.com < /etc/hosts

Pythonのネットワークプラグラミング

netcatもどき(作成中)

# -*- coding: utf-8 -*-

import sys
import socket
import getopt
import threading
import subprocess
import argparse
import re
import time

CS          = 'utf-8'

# Client Mode Option
HOST        = None
PORT        = 0
SRC_PORT    = 0

# Server Mode Option
LISTEN      = False
LISTEN_PORT = 0
WRITE_FILE  = None
EXEC_FILE   = None
COMMAND     = None


def do_server():
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.bind(('0.0.0.0', LISTEN_PORT))
    server.listen(5)
    while True:
        client_socket, addr = server.accept()
        client_thread = threading.Thread(target=client_handler, args=(client_socket,))
        client_thread.start()


def client_handler(client_socket):
    if WRITE_FILE:
        file_buffer = b''

        while True:
            data = client_socket.recv(1024)
            # terminates session if OS X Client send Ctrl + D
            if data == b'\x04':
                break
            else:
                file_buffer += data

        try:
            # must open file with wb mode. Because data is Byte data.
            fd = open(WRITE_FILE, 'wb')
            fd.write(file_buffer)
            fd.close()
            m = 'Success: saved data to ' + WRITE_FILE + '\r\n'
        except:
            m = 'Failed: failed to save data to ' + WRITE_FILE + '\r\n'

        # Unicode(Str) -> Byte Data(UTF-8)
        client_socket.send(m.encode(CS))
        client_socket.close()
        



#    if EXECUTE:
#        output = run_command(EXECUTE)
#        client_socket.send(output)
#
#    if SHELL:
#        prompt = '(SHELL): '
#        client_socket.send(prompt)
#
#        while True:
#           cmd_buffer = None
#           while '\n' not in cmd_buffer:
#              cmd_budder += client_socket.recv(1024)


def do_client():
    print('making')
 

def get_opt():
    def valid_target(s):
        if not re.search('^\S+:[1-65536]$', s):
           raise argparse.ArgumentTypeError('Invalid value. Value must be HOST:PORT')
        return s

    parser = argparse.ArgumentParser(description='This is tool similar to netcat by python. -l or -t option is required')

    cl_mode = parser.add_argument_group('Run on Client Mode')
    cl_mode.add_argument('-t', dest='target',      help='host:port(REQUIRED)',   type=valid_target)
    cl_mode.add_argument('-p', dest='source_port', help='source port',           type=int)

    sv_mode = parser.add_argument_group('Run on Server Mode. -w, -e, -c options is exclusive')
    sv_mode.add_argument('-l', dest='listen_port', help='listen port(REQUIRED)', type=int)
    sv_mode.add_argument('-w', dest='write_file',  help='write data to file')
    cl_mode.add_argument('-e', dest='exec_file',   help='execute file')
    cl_mode.add_argument('-c', dest='command',     help='execute command')

    args = parser.parse_args()

    if args.target:
        #---------------------------------------------------------
        # Client Mode Option Set
        #---------------------------------------------------------
        if args.listen_port or args.write_file or args.exec_file or args.command:
            parser.print_help()
        global HOST
        global PORT
        global SRC_PORT
    
        HOST     = args.target.split(':')[0]
        PORT     = int(args.target.split(':')[1])
        SRC_PORT = args.source_port
    else:
        #---------------------------------------------------------
        # Server Mode Option Set
        #---------------------------------------------------------
        if args.source_port:
            parser.print_help()
        if not args.listen_port:
            parser.print_help()

        global LISTEN
        global LISTEN_PORT
        global WRITE_FILE
        global COMMAND
        global EXEC_FILE

        LISTEN = True
        LISTEN_PORT = args.listen_port
        WRITE_FILE  = args.write_file
        EXEC_FILE   = args.exec_file
        COMMAND     = args.command


def main():
    get_opt()
    if LISTEN:
        print('Server Mode Start')
        do_server() 
    else:
        print('Client Mode Start')
        do_client() 


if __name__ == '__main__':
    main()