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


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のメソッドは削除した数、を返却します。