Cy-Pi

こうすれば取り敢えず出来ます。

pythonでgmail送信する(その5:プログラムの構造を見直した)

同じテーマについて、ほぼ毎日更新で第5段まで続いております。 そもそもそんなに短時間に更新を重ねるなら全部終わってから公開すればよかったなと後悔。

そうしてバタバタと更新をしてしまった結果、自分でも手を加える気にならなくなるくらいキタナイコードになっていました。

手遅れになる前に、メンテンナンス性を考慮してプログラムを再構築してみました。

github.com

我流ですが、まぁ良くなったかな。

pythonでリストの要素をforで回すとき少しだけコードがスッキリするenumerate

リストをforで回すときに、複数のリストを対応付けて処理することがあります。 そんなときはリストのインデックスを使ってforを回していました。

例えばこんなふうに。

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

letters = ["A", "B", "C", "D", "E"]
codes = ["Alfa", "Bravo", "Charlie", "Delta", "Echo"]

for i in range(len(letters)):
    print(letters[i] + " : " + codes[i])

rangeにlenて、、、趣味グラマでもダサいってわかります。

そこでこれ↓

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

letters = ["A", "B", "C", "D", "E"]
codes = ["Alfa", "Bravo", "Charlie", "Delta", "Echo"]

for i, letter in enumerate(letters):
    print(letter + " : " + codes[i])

enumerateを使うと、リストのインデックスと要素の両方を取得できます。 括弧のネストがなくなるので目に優しい感じがします。 ただ、printの部分がちぐはぐでイマイチです。

それならということで2つの良いところを合わせてみたのがこちらです。

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

letters = ["A", "B", "C", "D", "E"]
codes = ["Alfa", "Bravo", "Charlie", "Delta", "Echo"]

for i, _ in enumerate(letters):
    print(letters[i] + " : " + codes[i])

少しpython知ってる感が出たと思います。

pythonでgmail送信する(その4:添付ファイルにpdfをつける編)

前回の続きで、添付ファイルを付けて送れるようにしたという話です。 sandhawk79.hatenablog.com

まだまだ実験的なので、ファイル名固定だし、必ず添付しなければならないという頑固仕様です。

前回との差分は次の2点。

  • MIMEMultipart でテキスト部分と添付ファイル部分を束ねる。
  • MIMEApplication で添付ファイル部分のヘッダを作成する。

コードはこちら。

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import smtplib
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart

from os.path import basename

import csv
import textwrap

from getpass import getpass

# # windowsの場合はコレ↓しとかないとエラー出るか?
# import locale
# locale.setlocale(locale.LC_ALL, '')


class Person(object):
    def __init__(self, row):
        self.name = row[0]
        self.mail = row[1]

    def mailbody(self):
        # メール雛形
        body = textwrap.dedent('''\
            ##TO_NAME## 様

            3月も半ばを過ぎ、次第に春めいてまいりました。

            本日はpythonからテストメールを送信してみましたが、
            皆様におかれましてはいかがお過ごしでしょうか。
            ''')

        # ##で挟まれた文字列を置換して個別のメールにする
        body = body.replace("##TO_NAME##", self.name)
        print(str(body))

        return body


def get_sender_account():
    with open('mailaccount.txt') as f:
        lines = f.readlines()

    myaddr = lines[0].split(":")[1].rstrip()

    mypass = getpass('gmailパスワードを入力してください: ')

    return myaddr, mypass


def send_mail(myaddr="",
              mypass="",
              from_addr="",
              to_addrs=[],
              cc_addrs=[],
              bcc_addrs=[],
              subject="default_subject",
              mailbody="default mailbody"):

    msg = MIMEMultipart()

    # メール本体を作成する
    msg["Subject"] = subject
    msg["From"] = from_addr
    msg["To"] = ",".join(to_addrs)
    print(msg["To"])

    if cc_addrs != []:
        msg["Cc"] = ",".join(cc_addrs)
    if bcc_addrs != []:
        msg["Bcc"] = ",".join(bcc_addrs)
    msg.attach(MIMEText(mailbody))

    # ファイルを添付する
    attach_filename = 'attach.pdf'
    with open(attach_filename, 'rb') as f:
        attach_file = MIMEApplication(f.read(), _subtype="pdf")

    attach_file.add_header('Content-Disposition',
                           'attachment', filename=attach_filename)

    msg.attach(attach_file)

    # 送信する
    smtp = smtplib.SMTP("smtp.gmail.com", 587)
    smtp.ehlo()
    smtp.starttls()
    smtp.login(myaddr, mypass)
    smtp.send_message(msg)
    smtp.close()


def main():

    # メール送信者のgmailアカウント情報を読み取る
    myaddr, mypass = get_sender_account()

    # 送付先のリストをcsvファイルから読み取る
    with open('sendlist.csv', 'r') as f:
        csvlist = list(csv.reader(f))

        # リストの2行目以降に個別にメールを送付する
        for row in csvlist[1:]:
            person = Person(row)

            # 送付先とメール本文を指定してメールを送る
            # デフォルトではccに送信元のメールアドレスが設定されます。
            send_mail(myaddr=myaddr,
                      mypass=mypass,
                      from_addr=myaddr,
                      to_addrs=[person.mail],
                      cc_addrs=[myaddr],
                      bcc_addrs=[],
                      subject="これはpythonによるメール自動送信テストです",
                      mailbody=person.mailbody())


if __name__ == '__main__':

    main()

webを読み漁りましたが、ファイル形式の指定を省略してもっと簡素にしている方もいました。 確かにそれでも送れるのは確認できましたが、 結局最後は、emailモジュールのドキュメントを参考にしつつ、今の形に落ち着きました。

pythonでgmail送信する(その3:csvリストに記載した複数の宛先に送る編)

先日作ったgmail送信スクリプトを早速アップデートしました。

sandhawk79.hatenablog.com

複数の宛先をcsvリストから読み取ってメールを作成します。

それから細かいですが、その2で紹介したコードではメールアカウントのパスワードを平文で書かなければいけないというイマイチな点があったのを、毎回手入力する方式に変更しました。 多少不便になりますが、安全性と利便性とは常にトレードオフなので仕方ありません。

コードの紹介ですが今回から付属品が増えてきたのでgithubのリンクを貼っておきます。そちらをご覧ください。

github.com

次は添付ファイルをつけて送る編になると思います。

pythonでgmail送信する(その2:複数の宛先に送る編)

前回書いたgmail送信の続きです。 今回は複数のto/cc/bccアドレスに対応したという話です。 sandhawk79.hatenablog.com

MIMEの規格ではto/cc/bccに複数のアドレスを指定する場合は、カンマ区切りで渡してやれば良いことになっています。

で、わざわざpythonでmailを送るって場合は、別のデータから大量の宛先をリストでもらったりするのだろうから、宛先はリストになっているのでしょう。

ということはやることは簡単で、リストをもらってカンマ区切りの文字列に変換すればいいのです。そうjoin()でね。

ということでコードはこんな感じになります。 実行すると、自分から自分宛てに自分をccとbccに入れてテストメールが飛びます。

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import smtplib
from email.mime.text import MIMEText

MYADDR = "MY G-MAIL ADDRESS"
MYPASS = "MY G-MAIL PASSWORD"

def main(from_addr=MYADDR,
         to_addrs=[MYADDR],
         cc_addrs=[MYADDR],
         bcc_addrs=[MYADDR],
         subject="default_subject",
         mailbody="default body"):


    msg = MIMEText(mailbody)

    msg["Subject"] = subject
    msg["From"] = from_addr
    msg["To"] = ",".join(to_addrs)

    if cc_addrs != []:
        msg["Cc"] = ",".join(cc_addrs)
    if bcc_addrs != []:
        msg["Bcc"] = ",".join(bcc_addrs)

    smtp = smtplib.SMTP("smtp.gmail.com", 587)

    smtp.ehlo()
    smtp.starttls()
    smtp.ehlo()
    smtp.login(MYADDR, MYPASS)
    smtp.send_message(msg)
    smtp.close()


if __name__ == '__main__':

    main()

pythonでgmail送信する

とりあえずgmailからメールを送信する方法のメモです。

必要なことは2つ。

  1. googleのアカウント設定で「安全性の低いアプリのアクセス」をオンにする。
    利便性を手に入れるのと引き換えに、セキュリティレベルを下げなければならない。

  2. 次のコードを実行する。

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import smtplib
from email.mime.text import MIMEText

MYADDR = "mymailaddress@gmail.com"
MYPASS = "mypassword"

body = "testbody"
msg = MIMEText(body)

msg["Subject"] = "testmail"
msg["From"] = MYADDR
msg["To"] = "toaddress"
msg["Cc"] = "ccaddress"

smtp = smtplib.SMTP("smtp.gmail.com",587)
smtp.ehlo()

smtp.starttls()

smtp.login(MYADDR,MYPASS)
smtp.send_message(msg)

smtp.close()

複数の宛先に送ったり、添付ファイルをつけたりなど、追々バージョンアップしていこうと思います。

pythonでエクセルの管理台帳をつかった棚卸を自動化する

先日の記事の実践編的な内容です。 sandhawk79.hatenablog.com

ひょんなことから、プログラミングとは縁遠い方にpythonでこんなことできるよという紹介をすることになりまして、 githubに関連ファイルを上げたので、よかったら使ってください。 追々更新して記事と内容がズレるかもしれませんがあしからず。 github.com

ツールの説明はgithubのreadmeの方に書いたので、そちらを読んでください。

ここからは今回の思わぬ収穫となった「classのありがたさを実感できたよー」という話です。

私はプログラマではないし、これまでに作ってきたツールといえば自分の体に腕をもう一本生やす程度のものだったので、正直なところ「classなんて使わなくてもよくね?」と思ってました。

いろんなpythonの書籍を見ても「例えばゲームを作るとき!同じような要素を持った大量のアイテムや敵モンスターを作るときに便利です!」とか書いてあるし。特に入門書の類でね。ゲーム作りたいわけじゃないし。 、、、pythonやる人はみんなゲーム作るんでしょうか。

ですが、ここ最近csvとかエクセルとかのリストをチェックするプログラムをいくつか作成してみて、今までのスタイルだとなんかやりにくいなと思い、なんとなくclassを利用してみたら見通しが良くなりました。

boring_routine_inventoryのtoukenChecker1.pyのほうが今までのスタイルです。 一部抜粋してきました。

    for i in range(2,sheet.max_row):
        cell = list(sheet.columns)[5][i]
        if not cell.value == "済":
            yet_bushos.append(cell.offset(0,-3).value)
            yet_katanas.append(cell.offset(0,-5).value)

このコードでは次のことを前提にしています。

  • 6列目に毎月の棚卸し結果がある
  • 棚卸し結果の3列前は武将名
  • 棚卸し結果の5列前は刀剣名

こんなコードになっているのは、人間がやっていることをそのままコード化したからでしょう。 やっていることとは、今月の棚卸の列を見て未完了者を見つけ、管理物と一緒にリストアップするということです。 ほぼそのまんまコード化しています。

これでも動くには動くんですが、1年後にこのコードを見てもそれがすぐにわかる自信はありません。 もし台帳の形式が変わってしまったらとてもメンドクサイ。

そこで今回classを利用したバージョンを作ってみました。 こちらは全文掲載で。

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import datetime
import openpyxl
from copy import copy

# windowsの場合はコレ↓しとかないとエラー出る
import locale
locale.setlocale(locale.LC_ALL, '')

class Katana(object):
    def __init__(self, row):
        self.name = row[0].value
        self.yomi = row[1].value
        self.owner = row[2].value
        self.category = row[3].value
        self.note = row[4].value
        self.inventory_this_month = row[5].value
        self.inventory_last_month = row[6].value


def main():
    # ファイルを開く
    input_file = 'toukenDaicho.xlsx'
    wb = openpyxl.load_workbook(input_file)
    sheet = wb["Sheet1"]

    # 今月の棚卸状況が"済"でない刀とその所有者をリストアップする
    yet_katanas = []
    yet_bushos = []

    for i in range(1,sheet.max_row):
        katana = Katana(list(sheet.rows)[i])

        if not(katana.inventory_this_month == "済"):
            yet_katanas.append(katana.name)
            yet_bushos.append(katana.owner)

    # まだ棚卸されていない刀とその所有者を表示する
    print("【まだ棚卸されていない刀】")
    for i in range(len(yet_katanas)):
        print(yet_katanas[i] + " -- " + yet_bushos[i])

    # リスト書き出し
    with open("out.csv", "w") as f:
        for yet_katana in yet_katanas:
            f.write(yet_katana + "\n")


if __name__ == '__main__':
    main()

余計なライブラリを読み込んでたりしますが、それは置いといて、 classを使ったバージョンではリストを行ごとにひとつひとつ確認していくようにしました。 (class使わないバージョンでも今月の棚卸し状況は全行見ていましたが、それしか見ていません。)

人間は全セルを見るなんてメンドクサイのでやりませんが、 pythonは真面目なので文句も言わず全部のセルを見てくれます。

多分処理にかかる時間は増えるような気がしますが、体感できない程度なので全く問題ではありません。 それよりもclassを使ったことによって次のようなメリットがあるように思います。

  • リストの何列目に何の要素があるのかわかりやすい
  • 処理をするコードも列番号じゃなくて、わかりやすい変数名にできる
  • リストに変更があっても、どこを修正すればよいかわかりやすい

あとは、エクセルの列構成が変化してもいい感じに解釈して読み取るとか、本筋とは関係ないけど痒いところに手が届くような機能を追加したいとき、classに機能を切り分けて実装できるので見通しが良いというのもあるかな。

というわけで、私のようなノンプログラマでもclassの恩恵はあるかもしれないよというお話でした。