Pythonの配列・リストの指定要素を削除する方法。OKパターンとNGパターンを紹介。

phtyonロゴ

Pythonでも、一般の他プログラミング言語と同様に配列やリストを扱うことができます。

今回は、配列やリストに対して指定要素を削除する際の方法を紹介していきます。


環境情報


  • Python 3.4.1

配列


配列に対しての要素削除方法について説明していきます。


全削除


配列の全削除は、clearを使用します。


プログラムとしては以下になります。

array = ["A", "B", "C", "D", "E"]
print(array)
array.clear()
print(array)

以下のように、配列の要素は全削除されます。

['A', 'B', 'C', 'D', 'E']
[]

指定位置要素を削除


削除する要素の位置(インデックス)が分かっている場合は、popを使います。
popを使うと、指定位置の要素が削除され、削除した要素の取得もおこなうことができます。


array = ["A", "B", "C", "D", "E"]
print(array)
val = array.pop(2)
print(val)
print(array)

以下のように、指定位置の要素を取得した上で、削除をおこなっています。

['A', 'B', 'C', 'D', 'E']
C
['A', 'B', 'D', 'E']

指定要素と一致する要素を削除


削除する要素の中身が分かっている場合は、removeを使います。


array = ["A", "B", "C", "D", "E"]
print(array)
array.remove("C")
print(array)

以下のように、指定した要素のみが削除されます。

['A', 'B', 'C', 'D', 'E']
['A', 'B', 'D', 'E']

 


繰り返し中に削除/NGパターン


配列の要素を繰り返しで検索していき、削除する要素であった場合は削除する、というパターンはよくあるかと思います。
この場合、配列を先頭から検索して削除する方法はNGになります。


array = ["A", "B", "C", "D", "E"]
lc = 0
for val in array:
    print(val)
    if val == "C":
        array.remove(val)
    print(array)
    lc = lc + 1
print(array)

NGなのは、要素の「C」を削除するときで要素がずれてしまい、次の検索要素が「E」になってしまっているという点です。
本来であれば、「C」の次は「D」が正解になります。


A
['A', 'B', 'C', 'D', 'E']
B
['A', 'B', 'C', 'D', 'E']
C
['A', 'B', 'D', 'E']
E
['A', 'B', 'D', 'E']
['A', 'B', 'D', 'E']

繰り返し中に削除/OKパターン


繰り返し中に削除する方式のOKパターンとしては、末尾から先頭へ繰り返しをおこなう方法です。
末尾から繰り返しをおこなうためには、「reversed」を使います。


array = ["A", "B", "C", "D", "E"]
lc = 0
for val in reversed(array):
    print(val)
    if val == "C":
        array.remove(val)
    print(array)
    lc = lc + 1
print(array)

末尾からループして指定要素を削除しているので、削除した「C」の後に「B」を検索しています。
この方法が正解であることがわかります。


E
['A', 'B', 'C', 'D', 'E']
D
['A', 'B', 'C', 'D', 'E']
C
['A', 'B', 'D', 'E']
B
['A', 'B', 'D', 'E']
A
['A', 'B', 'D', 'E']
['A', 'B', 'D', 'E']

リスト


リストに対しても配列と同じ方法で削除操作をおこなうことができます。


全削除


リストの全削除も、配列と同様にclearを使用します。


array = [["A", "B", "C"], ["D", "E", "F"], ["G", "H", "I"]]
print(array)
array.clear()
print(array)

[['A', 'B', 'C'], ['D', 'E', 'F'], ['G', 'H', 'I']]
[]

指定位置要素を削除


削除する要素の位置(インデックス)が分かっている場合も、配列と同様にpopを使います。


array = [["A", "B", "C"], ["D", "E", "F"], ["G", "H", "I"]]
print(array)
val = array.pop(1)
print(val)
print(array)

[['A', 'B', 'C'], ['D', 'E', 'F'], ['G', 'H', 'I']]
['D', 'E', 'F']
[['A', 'B', 'C'], ['G', 'H', 'I']]

指定要素と一致する要素を削除


削除する要素の中身が分かっている場合も、配列と同様にremoveを使います。


array = [["A", "B", "C"], ["D", "E", "F"], ["G", "H", "I"]]
print(array)
array.remove(["D", "E", "F"])
print(array)

[['A', 'B', 'C'], ['D', 'E', 'F'], ['G', 'H', 'I']]
[['A', 'B', 'C'], ['G', 'H', 'I']]

繰り返し中に削除


配列と同様に、リストの要素を繰り返しして削除をおこなう方法は、先頭から繰り返しをおこなうのではなく、末尾から繰り返しをおこなって削除をおこなう方法が正解となります。 配列と同様にreversedを使います。


array = [["A", "B", "C"], ["D", "E", "F"], ["G", "H", "I"]]
lc = 0
for val in reversed(array):
    print(val)
    if val == ["D", "E", "F"]:
        array.remove(val)
    print(array)
    lc = lc + 1
print(array)

['G', 'H', 'I']
[['A', 'B', 'C'], ['D', 'E', 'F'], ['G', 'H', 'I']]
['D', 'E', 'F']
[['A', 'B', 'C'], ['G', 'H', 'I']]
['A', 'B', 'C']
[['A', 'B', 'C'], ['G', 'H', 'I']]
[['A', 'B', 'C'], ['G', 'H', 'I']]


Javaの起動オプションにGCログ設定とヒープダンプを設定する

Java

Javaを用いたプログラムで、運用開始後に原因不明の障害が発生する場合は多々あります。


原因不明のメモリリーク、CPUの一時的な極端な上昇といった現象は、アプリケーションのログからはなかなか原因が突き止められない場合があります。
こういった事象に対応するために、GCログの出力と、OutOfMemory発生時のヒープダンプ出力をあらかじめ仕込んでおく必要があります。


今回は、Javaアプリケーションの起動設定について、GCログとヒープダンプの出力設定をおこなう方法について紹介します。


環境情報


  • Java 1.8.0_281

GCログの出力設定


GCログの出力設定は、Javaの起動オプションとして設定します。
設定オプションは以下になります。


 

-verbose:gc

GCログを出力する。

-Xloggc

GCログの出力先を設定する。

例)-Xloggc:./gc.log-%t

 

ファイル名に設定できる変数要素としては以下の2つがあります。

%t=ログファイルを作成した日時

%p=JavaプロセスのID

-XX:+UseGCLogFileRotation

GCログのローテーションをおこないます。

-XX:+PrintGCDateStamps

GCログに時間のタイムスタンプを表示します。

-XX:+PrintGCDetails

GCログの詳細を出力します。

-XX:NumberOfGCLogFiles

GCログのローテーションをおこなう際、ローテーションするファイルの数を定義します。

-XX:GCLogFileSize

1つのログファイルのファイルサイズを定義します。

定義したファイルサイズに到達したタイミングで、ローテーションがおこなわれます。

定義したファイルサイズに到達したタイミングで、ローテーションがおこなわれます。

 

M=メガ

k=キロ(最小は7K

 


設定例としては以下になります。
実行するプログラムは「GcSample」となり、10キロのGCログファイルを最大5ファイルでローテーションをおこなっています。
GCログファイルにはログファイルを作成した日時を設定するために「%t」オプションを定義していますが、以下はWindowsのバッチファイル例なので、”%%”と記載してエスケープしています。


java -Xms256m -Xmx256m -verbose:gc -Xloggc:./gc.log-%%t -XX:+UseGCLogFileRotation -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=10k GcSample 

この定義でログファイルを出力した場合は、以下のような定義となります。


gc.log-2021-02-11_15-52-26.0
gc.log-2021-02-11_15-52-26.1
gc.log-2021-02-11_15-52-26.2
gc.log-2021-02-11_15-52-26.3
gc.log-2021-02-11_15-52-26.4.current ← 出力中ファイル

5ファイルでローテーションされています。


出力されたファイルはGCVierなどのビューアで参照するのが一般的になります。


ヒープダンプの出力設定


GCログを設定しておくことも大事なのですが、GCログだけではOutOfMemoryが発生した場合の、リーク箇所などについては詳細を判断することはできません。

ヒープダンプを設定しておくと、OutOfMemory発生時にヒープダンプが出力されるので、詳細の原因調査が可能となります。


-XX:+HeapDumpOnOutOfMemoryError

OutOfMemoryが発生した場合にヒープダンプを出力します。

-XX:HeapDumpPath

ヒープダンプの出力先フォルダ、および、ファイル名を指定します。

ファイル名を指定しない場合は、「java_pidxxx.hprof」といったファイル名になります。

xxxはプロセスIDです。

-XX:OnOutOfMemoryError

OutOfMemoryが発生した場合に実行するコマンドを定義することができます。

実行するコマンドは、シングルクォーテーションで囲んで定義します。

 

例)-XX:OnOutOfMemoryError=”cmd string.”


OutOfMemoryが発生してヒープダンプを出力した際、指定したフォルダにダンプファイルを出力するのですが、同名ファイルが存在する場合は上書きしてくれません。
つまり、同じプロセスIDのJavaプロセスで複数回のOutOfMemoryが発生した場合、一番最初のヒープダンプだけが残ります。


一番最初のヒープダンプを残すのではなく、一番最後のヒープダンプを残したい場合は、OnOutOfMemoryErrorオプションを使います。
以下の例では、ヒープダンプが発生したタイミングで出力したダンプファイルをバックアップフォルダに移動をおこないます。
この定義をおこなうことで、ヒープダンプ出力先フォルダにはダンプファイルは残らず、バックアップフォルダに最後のヒープダンプが残ることになります。


java -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./ -XX:OnOutOfMemoryError="D:/gc/gc-move.bat" GcSample 

OnOutOfMemoryErrorオプションに定義したgc-move.batの中身は以下になります。
backというフォルダに、出力したヒープダンプファイルを強制上書きコピーしています。

move /Y java*.hprof ./back/

OnOutOfMemoryErrorオプションに定義したコマンドは、ヒープダンプが出力された後に実行されるようです。


①:OutOfMemoryが発生

②:ヒープダンプを出力

③:OnOutOfMemoryErrorオプションに定義したコマンドを実行


このような動きであることを考慮して、OnOutOfMemoryErrorオプションに定義したコマンドを検討する必要があります。


仮に、最初のヒープダンプファイル・最後のヒープダンプファイル、という事ではなく、ヒープダンプの履歴を取りたい(全てのヒープダンプファイルを残しておきたい)場合は、上記例の「gc-move.bat」を修正すれば、様々な定義をおこなうことができます。
しかし、ヒープダンプファイルはプロセスのヒープ情報をそのままダンプしているので、かなりサイズが大きいファイルとなります。
そのため、ダンプファイルの履歴をとることはおすすめはしません。



ロリポップレンタルサーバで、クーロンを使ってバッチを実行して結果を必ずメールで受信

JavaScript

今回は、ロリポップレンタルサーバでクーロン設定をおこなう方法、および、シェル実行結果をメールで受け取る方法について紹介します。


ロリポップは、crontabを直接編集してクーロンの設定をおこなうことはできないですが、クーロン設定画面で細かい起動設定をおこなう事ができます。
このクーロン設定画面では実行結果を受け取るメールアドレスの設定も可能であり、実行結果のメール受信が可能です。


メールでの受信は、バッチの実行結果によってメール本文の内容を変更する方法についても紹介していきます。


環境情報


バッチから実行するバッチは、Pythonで実装されていることを前提とします。


  • レンタルサーバ:ロリポップ
  • シェルから呼び出すバッチ:Python

WEB画面でクーロン設定をおこなう


ロリポップでのクーロン設定はWEB画面でおこないます。


ロリポップのユーザー専用ページにログインし、左メニューで「サーバーの管理」から「cron設定」を選択します。


ロリポップでのクーロン設定

cron設定画面がひらきます。
クーロンの設定、および、設定済クーロンの確認は、本画面でおこないます。



画面上にあるように、日付(月)・日付(日)・曜日・時間(時)・時間(分)をプルダウンで形式で選択する形で設定をおこないます。
筆者の契約しているプランはエコノミープランですが、以下の設定となります。
プランによって異なるのは最小実行間隔のみ(1分毎、5分毎)となります。


日付(月)

毎月、1月~12月

日付(日)

毎日、1日~31日

曜日

毎日、日曜日~土曜日

時間(時)

毎時、0時~23時

時間(分)

1分毎~10分毎、0分~59分


このように設定項目は豊富なので、ほとんどの設定のニーズに対応することができます。
しかし、crontabを直接編集すれば可能な設定はおこなうことはできません。
例えば、毎週、月曜日と金曜日だけ実行するという設定は、1つの設定ではおこなうことができません。
毎週、月曜日と金曜日に実行するといった設定をおこなう場合は、月曜日に実行するという設定と、金曜日に実行するという設定の2つの設定が必要となります。


複数の設定をおこなえばcrontabを直接編集することと同じ事ができるのであれば問題ないと思われますが、クーロンの設定には上限があるので注意が必要です。
クーロンの設定上限は、契約しているプランによって異なります。


 

cron登録数

最小実行間隔

エコノミー

1

5分毎

ライト

5

5分毎

スタンダード

10

1分毎

ハイスピード

10

1分毎

エンタープライズ

10

1分毎


メールでシェル実行結果を受け取る


クーロンの実行結果をメールで受け取ることができます。
設定は簡単で、メールアドレスをWEB画面で設定するだけです。



この設定をおこなうだけで、クーロンの実行結果が登録したアドレス宛にメールが送信されます。


しかし、メールが送信される条件は、クーロンの実行結果に出力がある場合、のみとなります。
つまり、標準出力やエラー出力といった出力がおこなわれない場合は、メールは送信されません。


メールでバッチが動作した結果を受信するためには、バッチを実行するシェルファイルに標準出力をおこなうように設定をおこなえばよいです。


#!/bin/bash
python cronSample.py
echo "バッチ処理が終了しました。" >&1

WEB画面で設定したメールアドレスには、標準出力した”クーロン設定での処理完了”とだけ記載されたメールが送信されます。


上記の設定では、クーロンが実行されたタイミングで必ずメールが送信されます。
本来であれば、クーロンの実行が失敗した時、といった一定の条件と一致した場合についてのみメールを送信したい場合がほとんどです。


その場合は、プログラムでの実行結果をシェルで受け取り、受け取った実行結果によって標準出力有無を判断する必要があります。


#!/bin/bash
ret=$(python cronSample.py)
if [ $ret = 0 ]; then
  echo "バッチ処理で異常が発生しました。" >&1
fi
#!/usr/local/bin/python3.4
# coding: utf-8
import sys

sys.stdout.write("0")

シェルの中身はシンプルで、Pythonで実行した結果が失敗(0)であった場合に標準出力をおこなっています。
結果、失敗したらメールが送信される、ということになります。


Pythonのプログラムでは、0を標準出力しているのみです。
これはサンプルプログラムでなので0を標準出力しているのみなのですが、実際のプラグラムでは、この部分を修正すればよいです。



Linuxでサーバ容量・フォルダサイズ・ファイルサイズを調べるコマンド

Linux

Linuxサーバ上で、サーバ全体のディスク使用率や、フォルダサイズ・ファイルサイズを調査する時に便利なコマンドについて紹介していきます。


サーバ容量を確認する(dfコマンド)


サーバ全体の容量について確認する際に使用するのはdfコマンドになります。


[@users]$ df
ファイルシス   1K-ブロック     使用    使用可 使用% マウント位置
devtmpfs           3981472        0   3981472    0% /dev
tmpfs              3999296      260   3999036    1% /dev/shm
/dev/sda1           613184     6908    606276    2% /boot/efi

以下のオプションがあります。


-a

すべてのディスクを表示。

隠しファイルシステムなど、全てのディスクを表示します。

-H

単位を付けて結果を表示。

容量の表示は数字が大きくなってしまうので、k(キロ)、M(メガ)、G(ギガ)の単位を付けて表示するのが一般的です。


フォルダサイズを確認する(duコマンド)


フォルダのサイズを確認する時はduコマンドを使用します。


[@users]$ du
28      ./numberAnalyze/module
1088    ./numberAnalyze/logs
1124    ./numberAnalyze
16      ./winningAnalyze/module
916     ./winningAnalyze/logs
940     ./winningAnalyze
16      ./winningExpected/module
26044   ./winningExpected/logs
26068   ./winningExpected
12      ./winningCollection/module/__pycache__
24      ./winningCollection/module
12      ./winningCollection/logs
44      ./winningCollection
28184   .

duコマンドではフォルダサイズだけではなく、ファイルサイズも確認することができます。
以下のコマンドでは、コマンドを発行しているフォルダ直下ファイルのファイルサイズも表示します。


[users]$ du ./*
4       ./loto.sh                       ★
28      ./numberAnalyze/module
1088    ./numberAnalyze/logs
1124    ./numberAnalyze
16      ./winningAnalyze/module
916     ./winningAnalyze/logs
940     ./winningAnalyze
12      ./winningCollection/module/__pycache__
24      ./winningCollection/module
12      ./winningCollection/logs
44      ./winningCollection
16      ./winningExpected/module
26044   ./winningExpected/logs
26068   ./winningExpected

★=直下のファイル


以下のコマンドでは、もう1階層下フォルダのファイルサイズを表示します。


[users]$ du ./*/*
1088    ./numberAnalyze/logs
28      ./numberAnalyze/module
4       ./numberAnalyze/numberAnalyze.sh         ★
916     ./winningAnalyze/logs
16      ./winningAnalyze/module
4       ./winningAnalyze/winningAnalyze.sh       ★
12      ./winningCollection/logs
12      ./winningCollection/module/__pycache__
24      ./winningCollection/module
4       ./winningCollection/winningCollection.sh ★
26044   ./winningExpected/logs
16      ./winningExpected/module
4       ./winningExpected/winningExpected.sh     ★

★=1階層下のファイル


指定した階層のフォルダサイズを確認したい場合は「–max-depth」オプションを使用します。
以下のコマンドでは、コマンドを実行したフォルダ直下について、フォルダ毎のサイズを表示しています。


[users]$ du --max-depth=1
1124    ./numberAnalyze
940     ./winningAnalyze
26068   ./winningExpected
44      ./winningCollection
28184   .

サイズを調べるフォルダを深くしたい場合は、「–max-depth」オプションの数字を大きくしていけばよいです。


[users]$ du --max-depth=2
28      ./numberAnalyze/module
1088    ./numberAnalyze/logs
1124    ./numberAnalyze
16      ./winningAnalyze/module
916     ./winningAnalyze/logs
940     ./winningAnalyze
16      ./winningExpected/module
26044   ./winningExpected/logs
26068   ./winningExpected
24      ./winningCollection/module
12      ./winningCollection/logs
44      ./winningCollection
28184   .

上記以外のよく使うオプションも紹介しておきます。


-h

読みやすい単位で表示する。

-H

読みやすい単位で表示する。

ただし、1024単位ではなく1000単位の値を使用。

-c

合計のサイズも表示

-k

キロ単位で表示

-m

メガ単位で表示



Pythonのloggingを使って、用途別に複数のログファイルにログ出力する

phtyonロゴ

Pythonでは、ログ出力については標準機能として「logging」が準備されています。
実際のシステムでログ出力をおこなう場合、ログファイルが一種類であることは少なく、用途によってログファイルを分ける事が通常であると思います。


今回は、loggingを使って用途別に複数のログファイルにログ出力する方法を紹介します。


環境情報


  • Python 3.4.1

ログ管理クラス


ログファイルは以下の3種類とします。

  • アプリケーションログ
  • SQLログ
  • エラーログ

この3つのログファイルを出力するログ管理クラスを作成します。


#!/usr/local/bin/python3.4
# coding: utf-8
import logging
from logging import getLogger, FileHandler

#
# ログクラス
#
class Log:

    # -------------------------------------
    # コンストラクタ
    # -------------------------------------
    def __init__(self):

        # 1.アプリケーションログ
        l = logging.getLogger('APL')
        formatter = logging.Formatter(
            '%(asctime)s <%(levelname)s> : %(message)s')
        fileHandler = 
            logging.FileHandler('./logs/APL.log', mode='a')
        fileHandler.setFormatter(formatter)
        l.setLevel(logging.INFO)
        l.addHandler(fileHandler)

        # 2.SQLログ
        l = logging.getLogger('SQL')
        formatter = logging.Formatter(
            '%(asctime)s <%(levelname)s> : %(message)s')
        fileHandler = 
            logging.FileHandler('./logs/SQL.log', mode='a')
        fileHandler.setFormatter(formatter)
        l.setLevel(logging.INFO)
        l.addHandler(fileHandler)

        # 3.エラーログ
        l = logging.getLogger('ERROR')
        formatter = logging.Formatter(
            '%(asctime)s <%(levelname)s> : %(message)s')
        fileHandler = 
            logging.FileHandler('./logs/ERROR.log', mode='a')
        fileHandler.setFormatter(formatter)
        l.setLevel(logging.INFO)
        l.addHandler(fileHandler)

        Log.__instance = self

    # -------------------------------------
    # 4.アプリケーションログにINFOでログ出力
    # -------------------------------------
    def aplInfo(self, msg):
        log = logging.getLogger('APL')
        log.info(msg)

    # -------------------------------------
    # 5.SQLログにINFOでログ出力
    # -------------------------------------
    def sqlInfo(self, msg):
        log = logging.getLogger('SQL')
        log.info(msg)

    # -------------------------------------
    # 6.エラーログにERRORでログ出力
    # -------------------------------------
    def errorError(self, msg):
        log = logging.getLogger('ERROR')
        log.error(msg)

順番に説明していきます。
まずは「1.アプリケーションログ」です。


‘APL’という名前のロガーを作成し、ログファイルの出力先パスや、ログファイルの出力フォーマット、ログ出力のモードを指定して、ログファイル出力のためのファイルハンドラを作成します。


l = logging.getLogger('APL')
formatter = logging.Formatter('%(asctime)s <%(levelname)s> : %(message)s')
fileHandler = logging.FileHandler('./logs/APL.log', mode='a')
fileHandler.setFormatter(formatter)

次に、ロガーのログレベルを設定し作成したファイルハンドラもロガーに設定すれば完了です。


l.setLevel(logging.INFO)
l.addHandler(fileHandler)

「2.SQLログ」「3.エラーログ」も、設定するロガーが「SQL」「ERROR」といったように異なるだけで、「1.アプリケーションログ」と同様の作りです


4~6はログ出力のメソッドになります。


「4.アプリケーションログにINFOでログ出力」のメソッドである「aplInfo」はAPLロガーを使っています。
つまり、APL.logにログレベルINFOでログ出力をおこないます。


「5.SQLログにINFOでログ出力」のメソッドである「sqlInfo」も「aplInfo」と同様です。
異なる部分はSQLロガーを使っているということのみであり、SQL.logにログレベルINFOでログ出力をおこないます。


「6.エラーログにERRORでログ出力」は、ERRORロガーを使い、ERROR.logにログレベルERRORでログ出力をおこないます。


今回のサンプルでは3つのログ出力メソッドしか用意していませんが、必要であれば”アプリケーションログにERRORでログ出力””エラーログにINFOでログ出力”といったログ出力も可能な事がわかると思います。


ログを出力する


ログクラスを呼び出せば、ログクラスのコンストラクタでログ出力に必要なインスタンスを使ってくれるので、ログ出力をおこなう方の実装は極めてシンプルです。
以下のログ出力処理を、ログ出力したい箇所に実装すればログ出力がおこなわれます。


# インスタンス作成
log = Log()

# アプリケーションログをINFOで出力
log.aplInfo("アプリケーションログ")

# SQLログをINFOで出力
log.aplInfo(SQLログ")

# エラーログをERRORで出力
log.aplInfo(エラーログ")


ロリポップでPythonを使ってスクレイピング。モジュールを指定フォルダにインストールして環境構築。

phtyonロゴ

筆者は、レンタルサーバはロリポップを使っているのですが、Pythonでスクレイピングをするための環境構築で、ちょっと手こずりました。
原因がわかってしまえば単純なことだったので、記事として残しておきます。


環境情報


  • ロリポップレンタルサーバ
  • Python 3.4.1

モジュールのインストールコマンド


ロリポップでPythonのモジュールをインストールする方法は以下になります。
TeraTermにSSHで接続し、以下のコマンドを実行します。


/usr/local/bin/python -m pip install ※モジュール名

Pythonでスクレイピングをおこなうためには「beautifulsoup3」というモジュールが必要です。
しかし、上記のコマンドで「beautifulsoup3」をインストールしようとするとエラーが発生し、インストールすることができません。


$ /usr/local/bin/python -m pip install beautifulsoup3
Downloading/unpacking beautifulsoup3
  Real name of requirement beautifulsoup3 is beautifulsoup4
  Could not find any downloads that satisfy the requirement beautifulsoup3
Cleaning up...
No distributions at all found for beautifulsoup3
Storing debug log for failure in /home/users/0/oops.jp-sakusaku/.pip/pip.log
[oops.jp-sakusaku@users443 winningCollection]$ /usr/local/bin/python -m pip install beautifulsoup
Downloading/unpacking beautifulsoup
  Downloading BeautifulSoup-3.2.2.tar.gz
  Running setup.py (path:/tmp/pip_build_oops.jp-sakusaku/beautifulsoup/setup.py) egg_info for package beautifulsoup
    Traceback (most recent call last):
      File "<string>", line 17, in <module>
      File "/tmp/pip_build_oops.jp-sakusaku/beautifulsoup/setup.py", line 3
        "You're trying to run a very old release of Beautiful Soup under Python 3. This will not work."<>"Please use Beautiful Soup 4, available through the pip package 'beautifulsoup4'."
                                                                                                        ^
    SyntaxError: invalid syntax
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):

  File "<string>", line 17, in <module>

  File "/tmp/pip_build_oops.jp-sakusaku/beautifulsoup/setup.py", line 3

    "You're trying to run a very old release of Beautiful Soup under Python 3. This will not work."<>"Please use Beautiful Soup 4, available through the pip package 'beautifulsoup4'."

                                                                                                    ^

SyntaxError: invalid syntax

----------------------------------------
Cleaning up...
Command python setup.py egg_info failed with error code 1 in /tmp/pip_build_oops.jp-sakusaku/beautifulsoup
Storing debug log for failure in /home/users/0/oops.jp-sakusaku/.pip/pip.log

インストールとパス設定


インストール、および、Python実行時のパス設定にコツがあります。
前提として、以下のフォルダ構成を前提とします。


------
    |
    |---python
    |    |
    |    |---vendor         ・・・  Pythonの個別モジュールインストール先
    |
    |---test
      |
      |---scriping
           |
           |---scriping.sh  ・・・  作成したプログラムを実行するシェル
           |
           |---module       ・・・  作成プログラムの格納フォルダ

まず、インストールしたいモジュールである「beautifulsoup3」は個別モジュールインストール先である「python/vendor」にインストールする必要があります。
詳しい理由はわからないですが、「mysql-connector-python-rf」はインストール先を選択しなくても正常にインストールできたのですが、どうやら「beautifulsoup3」は自身で作成するフォルダにインストールする必要があるようです。


コマンドは以下になります。
「python」フォルダで以下のコマンドを実行します。


/usr/local/bin/python -m pip install BeautifulSoup --target ./vendor

このコマンドを実行することで、vendorフォルダ配下に「beautifulsoup3」のモジュールがインストールされます。


次にパス設定です。
実行するPythonのプログラムに「python/vendor」へのパスを設定する必要があります。
実行するPythonのプログラムは、「test/scriping/module」に格納されています。


#!/usr/local/bin/python3.4
# coding: utf-8
import os
import sys
sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '../../../python/vendor'))
from bs4 import BeautifulSoup 

これで、Pythonでスクレイピングのプログラムを実行する環境構築は完了です。



PythonでMySQLに接続するクラスを共通クラスとして部品化

phtyonロゴ

何かしらのシステムを構築しようとした場合、DBにデータを登録する、更新する、取得する、といった機能は必須になります。
Pythonも例外ではないのですが、いろいろなサイトでDBアクセスの一般的な方法は紹介しているのですが、実システムに沿ったような部品化についてはきちんとまとまっているサイトがありませんでした。


今回は、Pythonを使ってDBアクセス、および、DBアクセスクラスの部品化について紹介します。


環境情報


  • Python 3.4.1
  • MySQL 5.6.23

MySQLのデータベースには、以下のテーブルが存在することを前提とします。


CREATE TABLE test (
 test_id INT NOT NULL AUTO_INCREMENT,
 value INT NOT NULL,
 register_name VARCHAR(40) NOT NULL,
 register_date DATETIME NOT NULL,
 del_flg CHAR(1) NOT NULL,
 PRIMARY KEY (test_id)
) ENGINE = InnoDB DEFAULT CHARACTER SET utf8;

メインクラスとDBアクセスクラス


今回のクラス構成としては、メインクラスとDBアクセスクラスの2つとなります。
DBアクセスクラスは、DBアクセスに必要な最低限の機能を保持する事します。
メインクラスは、DBアクセスが必要な際にDBアクセスクラスを使います。


メインクラス

#!/usr/local/bin/python3.4
# coding: utf-8
from dbAccessor import dbAccessor

#
# メインクラス
#
class Main:

    # -----------------------------------
    # メインクラス
    #
    # DBアクセスクラスを呼び出すメイン処理
    # -----------------------------------
    def excecuteMain():
        obj = dbAccessor(
            '※DB名', '※ホスト名', '※ユーザ名', '※パスワード');

        # SELECT実行
        rows = obj.excecuteQuery('select test_id, value from test;')
        for row in rows:
            print("%d %s" % (row[0], row[1]))

        # INSERT実行
        num = obj.excecuteInsert("INSERT INTO test (
            value, register_name, register_date, del_flg) 
            VALUES (100,'管理者', '2020-12-20 00:00:00','管理者', 
            '0')")

        # UPDATE実行
        num = obj.excecuteUpdate(
            "UPDATE test SET value = 100 where test_id = 10")

        # DELETE実行
        num = obj.excecuteDelete(
            "DELETE FROM test WHERE test_id = 10")

# メイン処理を実行
Main.excecuteMain()

DBアクセスクラス

#!/usr/local/bin/python3.4
# coding: utf-8
from urllib.parse import urlparse
import mysql.connector

#
# DBアクセス管理クラス
#
class dbAccessor:

    # -----------------------------------
    # コンストラクタ
    #
    # コネクションを取得し、クラス変数にカーソルを保持する。
    # -----------------------------------
    def __init__(self, dbName, hostName, id, password):
        print("start:__init__")

        # DB接続URLを作成してパース
        url = 'mysql://' + id + ':' + password + '@' + 
            hostName + '/' + dbName
        parse = urlparse(url)

        try:
            # DBに接続する
            self.conn = mysql.connector.connect(
                host = parse.hostname,
                port = parse.port,
                user = parse.username,
                password = parse.password,
                database = parse.path[1:],
            )

            # コネクションの設定
            self.conn.autocommit = False

            # カーソル情報をクラス変数に格納
            self.conn.is_connected()
            self.cur = self.conn.cursor()
        except (mysql.connector.errors.ProgrammingError) as e:
            print(e)

        print("end:__init__")

    # -----------------------------------
    # クエリの実行
    #
    # クエリを実行し、取得結果を呼び出し元に通知する。
    # -----------------------------------
    def excecuteQuery(self, sql):
        print("start:excecuteQuery")

        try:
            self.cur.execute(sql)
            rows = self.cur.fetchall()
            return rows
        except (mysql.connector.errors.ProgrammingError) as e:
            print(e)

        print("end:excecuteQuery")

    # -----------------------------------
    # インサートの実行
    #
    # インサートを実行する。
    # -----------------------------------
    def excecuteInsert(self, sql):
        print("start:excecuteInsert")

        try:
            self.cur.execute(sql)
            self.conn.commit()
            return self.cur.rowcount
        except (mysql.connector.errors.ProgrammingError) as e:
            self.conn.rollback()
            print(e)

        print("end:excecuteInsert")

    # -----------------------------------
    # アップデートの実行
    #
    # アップデートを実行する。
    # -----------------------------------
    def excecuteUpdate(self, sql):
        print("start:excecuteUpdate")

        try:
            self.cur.execute(sql)
            self.conn.commit()
            return self.cur.rowcount
        except (mysql.connector.errors.ProgrammingError) as e:
            self.conn.rollback()
            print(e)

        print("end:excecuteUpdate")

    # -----------------------------------
    # デリートの実行
    #
    # デリートを実行する。
    # -----------------------------------
    def excecuteDelete(self, sql):
        print("start:excecuteDelete")

        try:
            self.cur.execute(sql)
            self.conn.commit()
            return self.cur.rowcount
        except (mysql.connector.errors.ProgrammingError) as e:
            self.conn.rollback()
            print(e)

        print("end:excecuteDelete")

    # -----------------------------------
    # デストラクタ
    #
    # コネクションを解放する。
    # -----------------------------------
    def __del__(self):
        print("start:__del__")
        try:
            self.conn.close()
        except (mysql.connector.errors.ProgrammingError) as e:
            print(e)
        print("end:__del__")

DBアクセスクラスの各メソッド


上記に紹介したサンプルプログラムについて説明していきます。


DBコネクションの取得と解放


DBコネクションの取得はコンストラクタで、解放はデストラクタで実施しています。


    def __init__(self, dbName, hostName, id, password):
        print("start:__init__")

        # DB接続URLを作成してパース
        url = 'mysql://' + id + ':' + password + '@' + 
            hostName + '/' + dbName
        parse = urlparse(url)

        try:
            # DBに接続する
            self.conn = mysql.connector.connect(
                host = parse.hostname,
                port = parse.port,
                user = parse.username,
                password = parse.password,
                database = parse.path[1:],
            )

            # コネクションの設定
            self.conn.autocommit = False

            # カーソル情報をクラス変数に格納
            self.conn.is_connected()
            self.cur = self.conn.cursor()
        except (mysql.connector.errors.ProgrammingError) as e:
            print(e)

        print("end:__init__")

    def __del__(self):
        print("start:__del__")
        try:
            self.conn.close()
        except (mysql.connector.errors.ProgrammingError) as e:
            print(e)
        print("end:__del__")

コンストラクタで通知されたパラメータを使って、MySQLに接続するURLを作成しています。
実際に作成されるURLは以下になります。


url = urlparse('mysql://※ユーザ名:※パスワード@※ホスト名/※DB名')

また、オートコミットをオフ(False)に設定しています。
オートコミットをオフにすることにより、トランザクション管理が容易になります。
これも、RDB(リレーションナルデータベース)を使ったシステムでは必須の機能となります。


こういって取得したDBコネクションとカーソル定義を、DBアクセスクラスのクラス変数として保持しています。
クラス変数として保持することにより、オブジェクト内でコネクションの使いまわしが可能となっています。


最後にデストラクタです。
デストラクタでコネクションを解放することにより、オブジェクト解放時に同時にコネクションも解放されます。


SELECT


SELECTを実行するメソッドは、実行するSQLを呼び出し側(メインクラス)から文字列として通知される形としています。
SQL文を通知して、SELECTした結果をそのまま返却しています。
非常にシンプルです。


    def excecuteQuery(self, sql):
        print("start:excecuteQuery")

        try:
            self.cur.execute(sql)
            rows = self.cur.fetchall()
            return rows
        except (mysql.connector.errors.ProgrammingError) as e:
            print(e)

        print("end:excecuteQuery")

INSERT


INSERT文もSELECTと同様で、呼び出し側から通知されたSQLを発行しているのみです。


SELECTと違うのは返り値となり、「rowcount」を返却しているのがポイントです。
「rowcount」は、INSERTした数になります。


    def excecuteInsert(self, sql):
        print("start:excecuteInsert")

        try:
            self.cur.execute(sql)
            self.conn.commit()
            return self.cur.rowcount
        except (mysql.connector.errors.ProgrammingError) as e:
            self.conn.rollback()
            print(e)

        print("end:excecuteInsert")

UPDATEとDELETEのメソッドも、UPDATEと同じメソッドになります。
UPDATEのメソッドは更新した数、DELETEのメソッドは削除した数、を返却します。



Apache2.4で、ポート毎にドキュメントルールをわける方法

apache

自分のパソコンを使って開発をおこなう際、実態としては複数のPJの開発環境を共存させる必要があります。
その際、ポート毎にドキュメントルートをわけて、ドキュメントルートそれぞれに対して開発環境を準備する方法がやり易いと思います。


今回は、Apache2.4を使って、ポート毎にドキュメントルールをわけるためのApacheの設定方法について紹介します。


環境情報


WindowsのマシンにApacheをインストールしてる状態を想定します。


  • OS:Windows10
  • HTTPサーバ:Apache2.4

Apacheのインストールと環境構築方法は以下の記事を参考にしてください。



筆者のパソコンには既にApache2.4がインストールされていて稼働しているのですが、ちょっと訳あって「80、81」ポートを使用することができないので、今回は「82、83」ポートそれぞれにドキュメントルールを設定して動作させるようにしてみます。


VirtualHostの設定


まずはVirtualHostの設定をおこないます。
82ポートと83ポートを使用できるようにし、それぞれに対してドキュメントルートを設定します。



# 1.Listenポートを設定
Listen 82
Listen 83

# 2.82ポートに対するドキュメントルートを設定
<VirtualHost *:82>
    DocumentRoot c:/PORT82/htdocs
    ServerName sakusaku-techs.com:82
    ErrorLog logs/PORT82/error_log
    CustomLog logs/PORT82/access_log common
</VirtualHost>

# 3.83ポートに対するドキュメントルートを設定
<VirtualHost *:83>
    DocumentRoot c:/PORT83/htdocs
    ServerName sakusaku-techs.com:83
    ErrorLog logs/PORT83/error_log
    CustomLog logs/PORT83/access_log common
</VirtualHost>

「1.Listenポートを設定」で、解放するポートを設定します。
今回は、82と83の2ポートを解放します。


「2.82ポートに対するドキュメントルートを設定」で、各ポートでアクセスするドキュメントルートを設定します。
ドキュメントルートの設定もおこなっていますが、ログの設定(アクセスログとエラーログ)もおこなっています。
これだけで、ポートとドキュメントの紐付けは完了です


「3.83ポートに対するドキュメントルートを設定」は、82ポートと同様の設定です。


ここまでの設定をおこなえばドキュメントルートの設定は完了ですが、このままではアクセスはできません。
次に説明する、アクセス制限の設定をおこなう必要があります。


アクセス制限の設定


VirtualHostの設定をおこなっただけではアクセス権限が設定されておらず、対象URLにブラウザでアクセスしても権限エラーで画面は表示することができません。


VirtualHostの設定に、以下のようにアクセス制御の設定をおこないます。


<VirtualHost *:82>
    DocumentRoot c:/PORT82/htdocs
    ServerName sakusaku-techs.com:82
    ErrorLog logs/PORT82/error_log
    CustomLog logs/PORT82/access_log common

    <Directory c:/PORT82/htdocs>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

<VirtualHost *:83>
    DocumentRoot c:/PORT83/htdocs
    ServerName sakusaku-techs.com:83
    ErrorLog logs/PORT83/error_log
    CustomLog logs/PORT83/access_log common

    <Directory c:/PORT83/htdocs>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

各バーチャルホストの設定に、以下の権限設定を入れています。


<Directory xxx>
    AllowOverride All
    Require all granted
</Directory>

Apache2.4では上記の設定が正解となります。
「Require all granted」がポイントです。
この設定を入れなかった場合、Apacheはデフォルトで「Require all denied」の設定となります。


そのため、「Require all granted」の権限設定をおこない、アクセスを有効にする必要があります。


ここまでの設定をおこなえば、82ポートと83ポートそれぞれでドキュメントルートをわけることができています。

確認として、82ポートに設定したドキュメントルートである「c:/PORT82/htdocs」と、83ポートに設定したドキュメントルートである「c:/PORT83/htdocs」下にindex.htmlを格納してみます。

その状態で、ブラウザを使って「http://localhost:82/index.html」「http://localhost:83/index.html」にアクセスすると、それぞれのドキュメントルートに配置したhtmlが表示されます。



MySQLのDBコネクション関連でトラブルが発生した際にやること

Linux

稼働中のシステムにおいて発生するトラブルのひとつに、MySQLのコネクションに関するトラブルがあります。
“アプリケーションからDBコネクションを取得できない!””想定以上にアプリケーションがコネクションを取得している”といったとトラブルになります。


今回は、MySQLのコネクションに関するトラブルが発生した場合に調査するポイントと、調査のために実行するコマンドについて紹介していきます。


現在のコネクション数を確認


現在、MySQLに接続しているコネクション数を確認する方法は以下になります。
MySQLにログインしてコマンドを実行します。


mysql> show status like 'Threads_connected';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 29    |
+-------------------+-------+
1 row in set (0.00 sec)

上記コマンドで現在接続されているコネクション数を確認して、MySQLで許可している最大コネクション数を超えていないかの確認が可能です。


最大コネクション数を確認


MySQLが許可している最大コネクション数を確認します。
このコネクション数が接続される可能性がある数を超えてしまうので、MySQLに接続できない状態になってしまいます。


mysql> SHOW GLOBAL VARIABLES like 'max_connections';
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| max_connections | 512   |
+-----------------+-------+
1 row in set (0.01 sec)

接続元のIPを確認


現在接続しているコネクションが、どのサーバから接続されているかの確認も可能です。


mysql> SHOW processlist;
+------------+------------+------------------------------+
| Id | User | Host | db | Command | Time | State | Info  |
+------------+------------+---------------------+--------+
  ・
  ・
  ・

Hostカラムが接続元サーバのIPとPORTです。


MySQLを起動してからの最大接続数を確認


MySQLを起動してから接続された最大コネクション数も確認することができます。
トラブルが発生して調査を開始したタイミングではコネクション数は問題ない場合、このコマンドを実行して過去の最大コネクション数を確認するとよいです。


mysql> show global status like 'Max_used_connections';
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| Max_used_connections | 513   |
+----------------------+-------+
1 row in set (0.00 sec)

DBサーバのKeepAliveを確認


TomcatなどのWEBアプリケーションがコネクションプールを取得している状態で、Tomcatが起動しているサーバが落ちてしまうと、MySQLのコネクションは残ってしまいます。
このコネクションは不要なコネクションとなるのですが、一定時間が過ぎると自動的にコネクションは解放されます。


この「一定時間」は、MySQLが起動しているサーバのKeepAliveパケット送信の設定に依存します。
システム設定値となり、以下のコマンドで格納可能です。


以下の設定例では、60秒で不要なコネクションは解放されます。


$>sysctl -a
 ・
 ・
 ・
net.ipv4.tcp_keepalive_intvl=5
net.ipv4.tcp_keepalive_probes=6
net.ipv4.tcp_keepalive_time=30
 ・
 ・
 ・

30秒間隔でKeepAliveパケットを6回送信する。
その6回は5秒間隔、という設定になります。


各項目の説明を一応記載しておきます。


net.ipv4.tcp_keepalive_intvl

KeepAliveパケットを送信する間隔

net.ipv4.tcp_keepalive_probes

KeepAliveパケットを送信する回数

net.ipv4.tcp_keepalive_time

KeepAliveパケットを送信するまでの時間


JavaのSupplierを使って汎用的なリトライ処理を実装する

Java

アプリケーションの機能を実装していく中で、様々なリトライ処理が必要になってきます。
DBアクセスに失敗した場合のリトライ、HTTP通信(APIなど)に失敗した場合のリトライ、など様々かと思います。


Javaでこういったリトライ処理を実装しようとすると、普通にやるとfor文やwhile文を使ってしまいがちです。
それでもいいですが、Javaのユーティリティクラス群であるSupplierを使えば、もうちょっと汎用的に実装することができます。


今回は、Javaのユーティリティクラスである「java.util.function.Supplier」を使って、汎用的なリトライ処理を実装する方法を紹介していきます。


環境情報


  • OS:Windows10
  • Java:Java1.8

ユーティリティクラス群で使用されている関数型インターフェースは、Java8から導入された機能となります。
Java1.7以前は使用できないので注意してください。


java.util.functionは関数型インターフェース


java.util.function配下の機能は全て、関数型インターフェースで実装されています。
イメージとしては、実行するメソッドをパラメータとして渡す形です。


java.util.functionのAPI仕様をみると複雑なインターフェースが沢山あるようにみえるのですが、大きくわけて4つに分類されます。
今回のリトライ処理では「Supplier」を使うのですが、他3つのインターフェースは以下になります。


種類 実装するメソッド 概要
Function<T,R> R apply(T t) 実装するメソッドは、引数としてTを受け取り、結果としてRを返すものになる
Consumer<T> void accept(T t) 実装するメソッドは、引数としてTを受け取り、結果を返さず終了するものになる
Predicate<T> boolean test(T t) 実装するメソッドは、引数としてTを受け取り、boolean値を結果として返すものになる
Supplier<T> T get() 実装するメソッドは、何も引数として受け取らず、結果としてTを返すものになる

関数型インターフェースの簡単なサンプルは以下になります。
先ほど説明した通り、関数をパラメータとして渡して渡された側で関数を実行しています。


return new RetryUtil()
    .retryFunc(() -> {
        ※実行する機能
    );
public class RetryUtil {
    private <T> T retryFunc(Supplier<T> func) {
        return func.get();
    }
}

リトライ処理


リトライ処理は、java.util.function配下の「Supplier」を使用します。
「Supplier」を使用し、実施する処理自体は呼び出し元で定義し、リトライの仕組み自体は共通クラス(ユーティリティクラス)に実装しています。


まずはリトライ処理を実施するメイン処理です。

import java.util.function.Supplier;

/**
    リトライ処理サンプル
*/
public class RetrySample {

    /**
        リトライ処理メイン
    */
    public static void main(String[] args) 
        throws InterruptedException {
        System.out.print("start: RetrySample\r\n");

        // 処理結果を失敗で初期化
        boolean isDone = false;

        // リトライ処理
        try {
            isDone = retryExec();
        } catch (Exception e) {
            System.out.print("RetrySample: main is failer\r\n");
        } finally {
            System.out.print(
                "RetrySample: result is " + isDone + "\r\n");
        }

        System.out.print("end: RetrySample\r\n");
    }

    /**
        リトライ処理の実施
    */
    private static boolean retryExec () 
        throws InterruptedException {

        // 1.リトライ回数とスリープ時間を設定
        int retryNum = 3;
        long sleepMilSec = 1000;

        // 2.ユーティリティを使って〇〇〇処理を実施する
        return new RetryUtil(retryNum, sleepMilSec)
            .retryFunc(() -> {
                try {
                    System.out.print("retryExec\r\n");
                    boolean isSucess = 〇〇〇処理を実施
                    if (isSucess) {
                        return true;
                    } else {
                        throw new RetryException();
                    }
                } catch (Exception e) {
                    throw new RetryException();
                }
            }, "RetrySample");
    }
}

「1.リトライ回数とスリープ時間を設定」で、リトライユーティリティのオブジェクトに設定する初期値を定義しています。
今回はリトライ処理ということで、ありがちなリトライ回数とスリープ時間はユーティリティクラスのコンストラクタで設定できようにしています。
その他、初期値として設定したい値があるのであれば、同様にコンストラクタで定義するのがよいかと思います。


「2.ユーティリティを使って〇〇〇処理を実施する」の機能が、実際に関数型インフェースで定義しているリトライ処理する中身です。
サンプルなので「〇〇〇処理を実施」としていますが、実際にはここにリトライ制御したい機能を実装していくのがよいでしょう。
リトライする契機としては、独自例外クラスである「RetryException」が発生した場合になります。
失敗したら(isDoneがfalse)「RetryException」をスローすることで、リトライ処理がはしるようにユーティリティクラスを準備しています。


次にリトライユーティリティクラスです。

import java.text.MessageFormat;
import java.util.function.Function;
import java.util.function.Supplier;

/**
    リトライユーティリティ
*/
public class RetryUtil {

    // リトライ回数
    private final int retryNum;

    // リトライ時のスリープ時間
    private final long retryWait;

    /**
     * コンストラクタ
     */
    public RetryUtil(int retryNum, long retryWait) {

        // 1.リトライ回数とスリープ時間を設定
        this.retryNum = retryNum;
        this.retryWait = retryWait;
    }

    /**
     * リトライ処理
     */
    public <T> T retryFunc(Supplier<T> func, String funcName)
            throws RetryException, InterruptedException {
        return retryFunc(func, funcName, 1, null);
    }

    /**
     * リトライユーティリティ
     */
    private <T> T retryFunc(Supplier<T> func, 
        String funcName, int count, Exception befEx)
        throws RetryException, InterruptedException {

        // 2.Supplierで提供されたfuncを実行
        try {
            return func.get();

        // 3.例外が発生した場合はリトライ回数に
        //     達するまで自身を再度呼び出す
        } catch (RetryException e) {
            System.out.print("RetryException\r\n");
            if (count > this.retryNum) {
                System.out.print("retry end\r\n");
                throw new RetryException(e);
            }
            count++;
            Thread.sleep(this.retryWait);
            System.out.print("next retry " + 
                Integer.toString(count) + "回目の処理開始\r\n");
            return retryFunc(func, funcName, count, null);
        }
    }
}

「1.リトライ回数とスリープ時間を設定」で、リトライ回数とスリープ時間をクラス変数として格納しています。
実際のリトライ処理時に、この値を参照しています。


「2.Supplierで提供されたfuncを実行」で、実際に関数型インターフェースで渡されたメソッドを実行しています。
実行方法は、パラメータ名.get() で実行されます。


「3.例外が発生した場合はリトライ回数に達するまで自身を再度呼び出す」がリトライ処理のメインとなる部分ですね。
個別例外である「RetryException」が発生するとリトライ処理を実施します。
リトライ回数が上限に達すると上位に例外をスローしてリトライ処理を中断します。
上限に達していない場合は再度自身を呼び出して同じ処理を実行しています。


最後に個別の例外クラスです。
リトライ処理を制御しやすくするために、個別例外を準備しています。


/**
    リトライユーティリティ固有例外
*/
public class RetryException extends RuntimeException {

    /**
     * コンストラクタ
     */
    public RetryException() {
        super();
    }

    /**
     * コンストラクタ(例外指定)
     * @param cause 例外
     */
    public RetryException(Throwable cause) {
        super(cause);
    }
}