ベスパリブ

プログラミングを主とした日記・備忘録です。ベスパ持ってないです。

園芸はじめました

実家に帰ったら親が園芸してました。良いなあと思ったので室内栽培をしてみたいと思います。

買ったもの

f:id:takeg:20170821210852j:plain

  • 植木鉢x3(1つは物入れ)
  • 二十日大根の種、時なし小かぶの種
  • 野菜の土(腐葉土)x2
  • 鉢底石
  • 鉢底ネット
  • 園芸用はさみ
  • スコップ
  • 水吹き

ちなみに全部100均で買いました。100均すごいな。

ちなみにド素人なので、ネットで集めた情報と勘で適当に買ってきました。

土づくり

土づくりも何も、買ってきたものを植木鉢にぶち込むだけです。

f:id:takeg:20170821211133j:plain

植木鉢はこんな感じです。植木鉢っていうかプランター?どっちでもいい。室内でやるので下に鉢受けを敷きます。

f:id:takeg:20170821211411j:plain

鉢底ネットを植木鉢の中に入れておくと、下から虫が入ってこないそうです。室内だから関係ないですけど。でも土や石が落ちにくくなるので多分必須です。

f:id:takeg:20170821212318j:plain

鉢底ネットを敷くとこんな感じです。

f:id:takeg:20170821211330j:plain

次は鉢底石をぶち込みます。水はけを良くして根腐れを防止するそうです。あと画像でかいな。

f:id:takeg:20170821212857j:plain

鉢底石を入れました。容器の1/5程度でいいらしいです。

f:id:takeg:20170821211350j:plain

次は腐葉土を入れます。

f:id:takeg:20170821213340j:plain

はい完成です。

種まき

f:id:takeg:20170821211310j:plain

今回は赤丸20日大根と、時なし小かぶを植えます。両方共室内栽培可能で、初心者にも育てやすいそうです。本当はシソを育てたかったんですけど売ってませんでした。

20日大根の種植え時期は春と秋、時なし小かぶは栽培時期を選ばないそうです。今は8月ですが、二十日大根は季節が悪いです。でもまあ、気にしません。

f:id:takeg:20170821222139j:plain

二十日大根は点まきにしました。画像は見にくいですが、6個の穴を空けて種を5~6粒ずつ入れました。種が重ならないように蒔くと良いそうです。知らずに蒔きました。

かぶは筋まきにすることにしました。画像はなし。

f:id:takeg:20170821224525j:plain

完成。たのしかったです(小並感)。

水は土が乾いたらやるくらい、たくさんやるといいそうなのですが、平日の日中は水やりできないので心配です。

ちなみに発芽温度が15度~28度らしいので、植木鉢に向けて扇風機を常に回しています。

f:id:takeg:20170821225816j:plain

おわり。

utf-8とiso-2022-jpのencodeとdecodeのTips

半角英数字をUTF-8でencode

>>> s = "aiueo012"
>>> b = s.encode("utf-8")
>>> b
>>> b'aiueo012'

これをdecodeする

>>> b.decode("utf-8")
'aiueo012'
>>> b.decode("iso-2022-jp")
'aiueo012'

半角英数字をutf-8エンコードしたものは、iso-2022-jpでデコードしても問題ない

半角英数字 + 日本語文字をUTF-8でencode

>>> ss = "aiueo012あいうkakikukeko"
>>> bb = ss.encode("utf-8")
>>> bb
b'aiueo012\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86kakikukeko'

日本語は1文字3バイトの16進数に変換されます。
これをUTF-8iso-2022-jpでdecodeしてみます。

>>> bb.decode("utf-8")
'aiueo012あいうkakikukeko'
>>> bb.decode("iso-2022-jp")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'iso2022_jp' codec can't decode byte 0xe3 in position 8: illegal multibyte sequence

当然、UTF-8でdecodeすると元の文字列に戻ります
iso-2022-jpでdecodeしようとすると、0xe3がdecodeできなくてUnicodeDecodeErrorが発生します

>>> bb.decode("iso-2022-jp", "ignore")
'aiueo012kakikukeko'

"ignore"オプションをつけてdecodeすると、デコード不可な文字は無視して無理やりdecodeしてくれます。
今回の場合では、結果として半角英数字のみを取り出すことができました。

半角英数字 + 日本語文字をiso-2022-jpでencode

>>> bb = ss.encode("iso-2022-jp")
>>> bb
b'aiueo012\x1b$B$"$$$&\x1b(Bkakikukeko'
>>> bb.decode("iso-2022-jp")
'aiueo012あいうkakikukeko'
>>> bb.decode("utf-8")
'aiueo012\x1b$B$"$$$&\x1b(Bkakikukeko'
>>> bb.decode("utf-8", "ignore")
'aiueo012\x1b$B$"$$$&\x1b(Bkakikukeko'

iso-2022-jpでencodeしたものは、UTF-8でdecodeすると、すべて半角英数字と記号に直されるようです

python3で文字列にlower()するとidが変わるという当たり前の話

文字列比較は is ではなくて == で比較しましょう、という話です。

>>> a = "hoge"
>>> b = 'hoge'
>>> a is b
True
>>> a.lower()
'hoge'
>>> a.lower() is b
False
>>> a.lower() is a
False

…なぜ?
どうやらlower()やupper()したらidが変わってしまう様子。

>>> id(a)
1991375392
>>> id(a.lower())
1991375808
>>> id(a.lower())
1991375744        # やるたびに変わる

知りませんでした。
idはオブジェクト固有のIDなので、lower()メソッドが返す文字列が元のオブジェクトとは限らないというのは当たり前といえば当たり前の話。
そういうわけで文字列比較は == で評価しましょう。

python3でContent-Transfer-Encoding ヘッダを 変更するのに苦労した話

msg = MIMEText(
main_text.encode("utf-8", "ignore"),
'plain',
'utf-8'
)

上のようなコードでメッセージを送信したとき、Content-Transfer-Encodingヘッダは7bitでした。

Content-Type: text/plain; charset="iso-2022-jp"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: =?utf-8?b?44CQTWltYW1vcmnjgJHmuKnluqYv5rm/
4K544OI44Oh44O844Or44Gn44GZ?=
To: hogehoge@test.mail
Cc:
Bcc:
Date: Mon, 19 Jun 2017 16:12:46 +0900

[この辺に本文]

上の例ではメールの本文(main_text)をbase64に変換し忘れていたことに気づき、次のようなコードに変えました

msg = MIMEText(
base64.b64encode( main_text.encode("utf-8", "ignore") ),
'plain',
'utf-8'
)

しかし、これでもEncodingヘッダは7bitでした。

Content-Type: text/plain; charset="iso-2022-jp"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: =?utf-8?b?44CQTWltYW1vcmnjgJHmuKnluqYv5rm/
4K544OI44Oh44O844Or44Gn44GZ?=
To: hogehoge@test.mail
Cc:
Bcc:
Date: Mon, 19 Jun 2017 16:12:46 +0900

[この辺に本文]

Content-Transfer-Encoding ヘッダをbase64に変更するにはどうすればいいのか?どうやらemail.encoders.encode_base64()を使えばいいっぽいです

ペイロードbase64 形式でエンコードし、 Content-Transfer-Encoding ヘッダを base64 に変更します。これはペイロード中のデータのほとんどが印刷不可能な文字である場合に適しています。 quoted-printable 形式よりも結果としてはコンパクトなサイズになるからです。 base64 形式の欠点は、これが人間にはまったく読めないテキストになってしまうことです。

ということで、msgにemail.encoders.encode_base64()を使えば解決しそうです。

msg = MIMEText(
base64.b64encode( main_text.encode("utf-8", "ignore") ),
'plain',
'utf-8'
)
email.encoders.encode_base64(msg)

しかし、これをすると次のヘッダになりました

Content-Type: text/plain; charset="iso-2022-jp"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Transfer-Encoding: base64
Subject: =?utf-8?b?44CQTWltYW1vcmnjgJHmuKnluqYv5rm/
4K544OI44Oh44O844Or44Gn44GZ?=
To: hogehoge@test.mail
Cc:
Bcc:
Date: Mon, 19 Jun 2017 16:12:46 +0900

[この辺に本文]

" Content-Transfer-Encoding:base64 "が追加されています。変更するんじゃないのかよ。
小一時間悩みました。しかしどうということはなさそうです。文章はちゃんと読みましょう。

ペイロードbase64 形式でエンコードし、 Content-Transfer-Encoding ヘッダを base64 に変更します。

email.encoders.encode_base64()自体が、base64変換の機能を持っていました。どうやら二重でbase64変換していたのが原因のようでした。
というわけで、次のように変更。

msg = MIMEText(
main_text.encode("utf-8", "ignore"),
'plain',
'utf-8'
)
email.encoders.encode_base64(msg)

ヘッダを確認します

Content-Type: text/plain; charset="iso-2022-jp"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Transfer-Encoding: base64
Subject: =?utf-8?b?44CQTWltYW1vcmnjgJHmuKnluqYv5rm/
4K544OI44Oh44O844Or44Gn44GZ?=
To: hogehoge@test.mail
Cc:
Bcc:
Date: Mon, 19 Jun 2017 16:12:46 +0900

[この辺に本文]

は?
やっぱり、Content-Transfer-Encodingヘッダが追加されてしまいます。どうなっとんじゃ。
email.encoders.encode_base64()の元のソースコードを覗いてみます。

def encode_base64(msg):
"""Encode the message's payload in Base64.
Also, add an appropriate Content-Transfer-Encoding header.
"""
orig = msg.get_payload(decode=True)
encdata = str(_bencode(orig), 'ascii')
msg.set_payload(encdata)
msg['Content-Transfer-Encoding'] = 'base64'

ぱっと見た感じ、
(1). msgのペイロードを取り出し、ついでにデコードもする(orig)
(2). origをasciiでエンコードする(encdata)
(3). msgのペイロードにencdataをセットする
(4). msgのContent-Transfer-Encodingヘッダをbase64にする
という処理をしてそうです
どうやら、email.encoders.encode_base64()を使わなくてもmsg['Content-Transfer-Encoding'] = 'base64'とやるだけでヘッダを変更できそうです。そもそも私はヘッダ情報を変更したいだけだったんだ。これでいきましょう。

msg = MIMEText(
main_text.encode("utf-8", "ignore"),
'plain',
'utf-8'
)
msg['Content-Transfer-Encoding'] = 'base64'
msg['Content-Transfer-Encoding'] = 'base64'
msg['Content-Transfer-Encoding'] = 'base64'

ヘッダを確認します

Content-Type: text/plain; charset="iso-2022-jp"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Transfer-Encoding: base64
Content-Transfer-Encoding: base64
Content-Transfer-Encoding: base64
Subject: =?utf-8?b?44CQTWltYW1vcmnjgJHmuKnluqYv5rm/
4K544OI44Oh44O844Or44Gn44GZ?=
To: hogehoge@test.mail
Cc:
Bcc:
Date: Mon, 19 Jun 2017 16:12:46 +0900

[この辺に本文]

ここにきて気づいたのですが、どうやらmsg["hoge"] = "piyo"は、変更でなくて追加のようです。その証拠に、

msg = MIMEText(
main_text.encode("utf-8", "ignore"),
'plain',
'utf-8'
)
msg['Content-Transfer-Encoding'] = 'base64'
msg['Subject'] = 'hogehoge'
msg['Content-Transfer-Encoding'] = 'base64'
msg['Subject'] = 'hogehoge'
msg['Content-Transfer-Encoding'] = 'base64'
msg['Subject'] = 'hogehoge'
Content-Type: text/plain; charset="iso-2022-jp"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Transfer-Encoding: base64
Subject: =?utf-8?b?44CQTWltYW1vcmnjgJHmuKnluqYv5rm/
4K544OI44Oh44O844Or44Gn44GZ?=
Content-Transfer-Encoding: base64
Subject: =?utf-8?b?44CQTWltYW1vcmnjgJHmuKnluqYv5rm/
4K544OI44Oh44O844Or44Gn44GZ?=
Content-Transfer-Encoding: base64
Subject: =?utf-8?b?44CQTWltYW1vcmnjgJHmuKnluqYv5rm/
4K544OI44Oh44O844Or44Gn44GZ?=
To: hogehoge@test.mail
Cc:
Bcc:
Date: Mon, 19 Jun 2017 16:12:46 +0900

[この辺に本文]

となります。
で、どうすればいいのか。
また小一時間悩んだ結果、原因はMIMETextクラスにありました。

class email.mime.text.MIMEText(_text, _subtype='plain', _charset=None, *, policy=compat32)
(中略)
_charset 引数に明示的に None をセットしない限りは、作成される MIMEText オブジェクトは charset の付いた Content-Type ヘッダと Content-Transfer-Endcoding ヘッダの両方を持ちます。これは後続の set_payload 呼び出しが、 set_payload コマンドに charset が渡したとしてもエンコードされたペイロードにはならないことを意味します。 Content-Transfer-Encoding ヘッダを削除することでこの振る舞いを「リセット」出来ます。これにより set_payload 呼び出しが新たなペイロードを自動的にエンコード (そして新たな Content-Transfer-Encoding ヘッダを追加) します。

つまり、Content-Transfer-Encodingヘッダを変更するには、
・_charset=Noneでメッセージを作成する
または
・一度Content-Transfer-Encoding ヘッダを削除してset_payload()する
の2通りのやり方があるようです。
後者の方法にContent-Transfer-Encoding ヘッダを削除と書いてありますが、削除の方法は書いてありません。「そもそもset_payloadってなんだっけ」と検索をかけると、それっぽいのがMessageクラスに見つかりました。

class email.message.Message(policy=compat32)
(中略)
replace_header(_name, _value)

Replace a header. Replace the first header found in the message that matches _name, retaining header order and field name case. If no matching header was found, a KeyError is raised.

「Messageオブジェクトの中にある最初に見つけた_nameヘッダの値を_valueに置き換える」というメンバメソッドです。
これを使えばいいんじゃないでしょうか。

msg = MIMEText(
base64.b64encode( main_text.encode("utf-8", "ignore") ),
'plain',
'utf-8'
)
msg.replace_header('Content-Transfer-Encoding', "base64")
Content-Type: text/plain; charset="iso-2022-jp"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Subject: =?utf-8?b?44CQTWltYW1vcmnjgJHmuKnluqYv5rm/
4K544OI44Oh44O844Or44Gn44GZ?=
To: hogehoge@test.mail
Cc:
Bcc:
Date: Mon, 19 Jun 2017 16:12:46 +0900

[この辺に本文]

できました!はー長かった。

ちなみに、MIMETextだけがこんな仕様なのかと疑問に思い、MIMEBaseでも試してみましたが、同じ挙動でした。たぶん他のMIMEクラスでも同様だと思います。

csv_msg = MIMEBase("text", "csv")
s = "hoge,piyo,fuga"
csv_msg.set_payload(s.encode("utf-8", "ignore"))
print(csv_msg)
"""
Content-Type: text/csv
MIME-Version: 1.0

hoge,piyo,fuga
"""
csv_msg["Content-Transfer-Encoding"] = "7bit"
encoders.encode_base64(csv_msg)
print(csv_msg)
"""
Content-Type: text/csv
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Transfer-Encoding: base64

aG9nZSxwaXlvLGZ1Z2E=
"""
csv_msg["Content-Transfer-Encoding"] = "base64"
csv_msg["Content-Transfer-Encoding"] = "base64"
csv_msg["Content-Transfer-Encoding"] = "base64"
csv_msg["X-Moe"] = "honoka, kotori, umi"
print(csv_msg)
"""
Content-Type: text/csv
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Transfer-Encoding: base64
Content-Transfer-Encoding: base64
Content-Transfer-Encoding: base64
Content-Transfer-Encoding: base64
X-Moe: honoka, kotori, umi

aG9nZSxwaXlvLGZ1Z2E=
"""

ところで、メールのcharsetはutf-8iso-2022-jpのどっちにしたほうがいいんですかね。

python3では文字列中の部分文字列検索や辞書にキーが存在するかはin演算子を使おう

公式がそう言っているので、そうしましょう。
https://docs.python.jp/3/library/stdtypes.html

文字列中の部分文字列検索

注釈
find() メソッドは、 sub の位置を知りたいときにのみ使うべきです。 sub が部分文字列であるかどうかのみを調べるには、 in 演算子を使ってください:

>>>
>>> 'Py' in 'Python'
True

辞書にキーが存在するかを調べる

>>>
>>> dict = {}
>>> dict["A"] = 1
>>> dict["B"] = 2
>>> dict["C"] = 3
>>> "A" in dict
True
>>> "D" in dict
False

Pythonでclassの中にclassを作成する

class TemperatureHumidity:
  class Temperature:
    def __init__(self):
      self.value = None
      self.count = 0
    def print(self):
      print("Temperature:", self.value, self.count)
  class Humidity:
    def __init__(self):
    self.value = None
    self.count = 0
    def print(self):
      print("Humidity:", self.value, self.count)

  def __init__(self):
    self.t = self.Temperature()
    self.h = self.Humidity()
  def print(self):
    self.t.print()
    self.h.print()
  def increment_count(self, _obj):
    _obj.count += 1

if __name__ == "__main__":
  t_h = TemperatureHumidity()
  t_h.t.print()
  t_h.h.print()
  t_h.increment_count(t_h.t)
  t_h.t.print()
  t_h.h.print()

継承するときはselfはいらない

以下のコードはTemperatureクラスがDataクラスを継承していますが、self.Dataと書いてはいけません。

class TemperatureHumidity:
  class Data:
    def __init__(self):
      self.value = None
      self.count = 0
  class Temperature(Data):
    def __init__(self):
      super().__init__()
    def print(self):
      print("Temperature:", self.value, self.count)
  class Humidity(Data):
    def __init__(self):
      super().__init__()
    def print(self):
      print("Humidity:", self.value, self.count)

  def __init__(self):
    self.t = self.Temperature()
    self.h = self.Humidity()
  def print(self):
    self.t.print()
    self.h.print()
  def increment_count(self, _obj):
    _obj.count += 1

if __name__ == "__main__":
  t_h = TemperatureHumidity()
  t_h.t.print()
  t_h.h.print()
  t_h.increment_count(t_h.t)
  t_h.t.print()
  t_h.h.print()

pythonでfloat()やint()で変換可能かどうかを調べる関数

標準では提供されてなかったようなので、自作helper関数として作ります。
与えられた文字列が小数 or 整数かを正規表現で調べる関数を作ったのですが、作った後でテストしているときに、そんなことしなくても例外をキャッチすればいいだけだと気づきました。

###############################################################################
# str文字列がfloat()変換できるかどうかを判定する
###############################################################################
def is_float(s):
  try:
    float(s)
  except:
    return False
  return True

同様に、int(), long(),complex()変換可能かどうかも簡単に作れます。こうして見ると例外キャッチってすごい便利ですね。