ベスパライフ

日記・備忘録。バイク買いました。ベスパもってないです。

Python3でラズパイのIPアドレスを取得する

python上でifconfigコマンドを実行し、返ってきた結果にIPアドレスがあればそれを表示することを考えます。
"LANG=C ifconfig"とするとコマンド結果が全て英語で返ってきて、処理しやすくなります。

import subprocess
subprocess.call(("LANG=C", "ifconfig"))  # エラー起きる

残念ながら、上記の方法だとエラーが発生します。"LANG=C"を使うときはcallではなくてPopenを使うと良いそうです。
Popenは公式によると、

17.5.1.2. Popen コンストラクター(原文) このモジュールの中で、根底のプロセス生成と管理は Popen クラスによって扱われます。簡易関数によってカバーされないあまり一般的でないケースを開発者が扱えるように、Popen クラスは多くの柔軟性を提供しています。 class subprocess.Popen(args, bufsize=-1, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=(), *, encoding=None, errors=None)
(中略)
注釈 子プロセスのために環境を変更する必要がある場合は、preexec_fn の中でそれをするのではなく env 引数を使用します。start_new_session 引数は、子プロセスの中で os.setsid() を呼ぶ過去の一般的な preexec_fn の使用方法の代わりになります。

(オプション引数多すぎ!)
結論から言うと、env引数で指定すれば良いです。LANG=Cは環境変更のコマンド(?)になるので、subprocess.callでifconfigと併用できなかったようです。

p = subprocess.Popen(
  "ifconfig",
  #stdin=subprocess.PIPE
  stdout=subprocess.PIPE,
  stderr=subprocess.PIPE, 
  env={'LANG':'C'},
  shell=True
)
out, err = p.communicate()
print(out)

shell引数をTrueにしておかないと、"ifconfig"コマンドを認識してくれません。
stdout, stderr引数がないと、outとerrを受け取ることが出来ません。
結果としてoutは以下のようなものを得られます。

b'eth0      Link encap:Ethernet  HWaddr b8:27:eb:xx:xx:xx  \n          UP BROADC
AST MULTICAST  MTU:1500  Metric:1\n          RX packets:4431131 errors:0 dropped
:9222 overruns:0 frame:0\n          TX packets:18104015 errors:0 dropped:0 overr
uns:0 carrier:0\n          collisions:0 txqueuelen:1000 \n          RX bytes:122
0313021 (1.1 GiB)  TX bytes:3488088120 (3.2 GiB)\n\nlo        Link encap:Local L
oopback  \n          inet addr:127.0.0.1  Mask:255.0.0.0\n          inet6 addr:
::1/128 Scope:Host\n          UP LOOPBACK RUNNING  MTU:65536  Metric:1\n
  RX packets:31487788 errors:0 dropped:0 overruns:0 frame:0\n          TX packet
s:31487788 errors:0 dropped:0 overruns:0 carrier:0\n          collisions:0 txque
uelen:1 \n          RX bytes:29619310733 (27.5 GiB)  TX bytes:29619310733 (27.5
GiB)\n\nwlan0     Link encap:Ethernet  HWaddr b8:27:eb:xx:xx:xx  \n          ine
t addr:192.168.5.129  Bcast:192.168.5.255  Mask:255.255.255.0\n          inet6 a
ddr: fe80::9e6e:73a1:2f3a:e0b1/64 Scope:Link\n          UP BROADCAST RUNNING MUL
TICAST  MTU:1500  Metric:1\n          RX packets:19745725 errors:0 dropped:30867
91 overruns:0 frame:0\n          TX packets:1280736 errors:0 dropped:0 overruns:
0 carrier:0\n          collisions:0 txqueuelen:1000 \n          RX bytes:1023118
1 (9.7 MiB)  TX bytes:1462394071 (1.3 GiB)\n\n'

結果はバイナリで返ってきていますので、適当にdecodeして文字列に直してから処理をします。

  str_out = out.decode("ascii", "ignore")
  str_out_lines = str_out.splitlines()
  ip_addr = {"eth0":"", "wlan0":"", "lo":"", "":""}
  now_if  = ""
  for line in str_out_lines:
    line = line.lower()
    if line.startswith("eth0"):
      now_if = "eth0"
      continue
    elif line.startswith("lo"):
      now_if = "lo"
      continue
    elif line.startswith("wlan0"):
      now_if = "wlan0"
      continue
    if "inet addr:" in line:
      blocks = line.split()
      # blocks[1] = "addr:xxx.xxx.xxx.xxx"
      ip_addr[now_if] = blocks[1][5:]

    # 確認のため標準出力する
    for key in ip_addr:
      print("ip_addr[{}] = {}".format(key, ip_addr[key]))

標準出力の結果は以下のようになります。

ip_addr[lo] = 127.0.0.1
ip_addr[] =
ip_addr[eth0] = 192.168.5.128
ip_addr[wlan0] = 192.168.5.129

eth0が有線LAN、wlan0が無線LANIPアドレスです。

Pythonのopen関数のencoding引数は必須でもいいんじゃない

たまにファイルの読み込み時(後?)にDecodeErrorする謎現象に遭いました。
どうやらファイルがShift-JISで書き込まれていたのをUTF-8で読み込もうとしていたのが原因。そういえばファイルをAtomで編集したときSJISで保存したかもしれない。うちのAtomは気を抜いたらなぜかSJISで書き込もうとする。

調べたらopen関数でencoding引数があったので、それを指定すれば確実にUTF-8で読み書き可能です。

f = open("hoge.txt", "w", encoding="utf-8")
f.write("あいうえお")
f.close()

encoding引数を指定しない場合のデフォルト値はNoneで、どのエンコーディングになるかはプラットフォーム依存らしいです。

encoding はファイルのエンコードやデコードに使われる text encoding の名前です。このオプションはテキストモードでのみ使用してください。デフォルトエンコーディングはプラットフォーム依存 (locale.getpreferredencoding() が返すもの) ですが、Pythonでサポートされているエンコーディングはどれでも使えます。詳しくは codecs モジュール内のサポートしているエンコーディングのリストを参照してください。

参考URL:
https://docs.python.jp/3/library/functions.html#open

PHPのexplode()の引数の順番がむかつく

split系の関数は第一引数が文字列で、第二引数がデリミタの印象があり、PHPのexplode()を使うときに毎回間違えるのでちょっとむかつきます。
そこで各言語のexplode()にあたる関数の引数の順番を調べてみました。

PHP

explode(delimiter, string)

Python

string.split(delimiter)

C(C++)

strtok(char* string, char* delimiter)

C#

string.Split(delimiter)

Java

string.split(delimiter)

Javascript

string.split(delimiter)

R

split(x, f)
x: vector or data frame
f: factor

Golang

Split(string, delimiter)

Swift2

string.componentsSeparatedByString(delimiter)

Swift3

string.components(separatedBy: delimiter)

Swift4

string.split(separator: delimiter)

Arduino

strtok(char* string, char* delimiter)

Ruby

string.split(delimiter)

Perl

split(delimiter, string)
(!)

Scala

string split delimiter

Matlab

split(string, delimiter)

VB6

Split(string, delimiter)

VB.NET

string.Split(delimiter)

VBA

Split(string, delimiter)

まとめ

PerlPHPと同じ形でした。
他は型の違いこそあれ、split(string, delimiter)の形かstring.split(delimiter)の形になっています。ほらーやっぱり。PHPくんはPerlくんと仲が良いのはいいんだけど、でももっと周りに合わせることを覚えようね(同調圧力

python3.4.2のSSLContextのデフォルト引数はなんなのか

SSLContextのデフォルト引数

公式のリファレンスでは、3系ではpython3.5.3以降の情報しか掲載されていません。
ということで実際のソースを見ることにします。

私の環境では以下のパスにありました。
/usr/lib/python3.4/ssl.py

class SSLContext(_SSLContext):
    """An SSLContext holds various SSL-related configuration options and
    data, such as certificates and possibly a private key."""

_SSLContextとは何か。

import _ssl             # if we can't import it, let the error propagate
(中略)
from _ssl import _SSLContext

_sslモジュールがなんなのか正確には不明ですが、おそらくdevelop環境か昔のモジュールのどちらかでしょう。将来修正する予定のモジュールを読み込んでいると思われます。

_sslはどこにあるのか。こういう場合は、コンソールを起動して読み込んで見たらパスがわかります。

>>> import _ssl
>>> _ssl
<module '_ssl' from '/usr/lib/python3.4/lib-dynload/_ssl.cpython-34m-arm-linux-gnueabihf.so'>

.so拡張子はLinuxで共有ライブラリ(Shared Object)を指すそうです。初めて知りました。
とりあえず_ssl.cpython-34m-arm-linux-gnueabihf.soを開いてみましたが、コンパイルされていて読めませんでした(cpythonて書いてあるし)。コメントっぽいのは読めますが、残念ながらよくわかりません。行き詰まりました。

ここで気づいたのですが、普通に呼び出してみたらどうなるのでしょうか。

>> ssl.SSLContext()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __new__() missing 1 required positional argument: 'protocol'

あっ。デフォルト引数ないんですね。そうですか。protocol引数に必ず値を指定しなくてはいけないようです。

create_default_context()を使用したとき

そもそもなぜSSLContextのデフォルト引数を調べようかと思ったかというと、create_default_context()を使用して呼び出していたからです。なので本来ソースを見るべきはこちらの関数だったというオチです。

def create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None,
                           capath=None, cadata=None):
    """Create a SSLContext object with default settings.

    NOTE: The protocol and settings may change anytime without prior
          deprecation. The values represent a fair balance between maximum
          compatibility and security.
    """
    if not isinstance(purpose, _ASN1Object):
        raise TypeError(purpose)

    context = SSLContext(PROTOCOL_SSLv23)

    # SSLv2 considered harmful.
    context.options |= OP_NO_SSLv2

    # SSLv3 has problematic security and is only required for really old
    # clients such as IE6 on Windows XP
    context.options |= OP_NO_SSLv3

    # disable compression to prevent CRIME attacks (OpenSSL 1.0+)
    context.options |= getattr(_ssl, "OP_NO_COMPRESSION", 0)

PROTOCOL_SSLv23をセットしています。それにOP_NO_SSLv2やOP_NO_SSLv3のフラグを立てて、脆弱性のあるSSLv2等でネゴシエーションしないようにしています。
ではPROTOCOL_SSLv23はなんなのか。

ssl.PROTOCOL_SSLv23(原文) data:PROTOCOL_TLSエイリアスです。 バージョン 3.6 で撤廃: 代わりに PROTOCOL_TLS を使用してください。

ssl.PROTOCOL_TLS¶(原文) チャンネル暗号化プロトコルとして、クライアントとサーバの両方がサポートする中の、プロトコルバージョンが最も大きなものを選択します。その名前にも関わらず、このオプションは “SSL” とともに “TLSプロトコルも選択できます。 バージョン 2.7.13 で追加.

ssl.PROTOCOL_TLS(原文) チャンネル暗号化プロトコルとして、クライアントとサーバの両方がサポートする中の、プロトコルバージョンが最も大きなものを選択します。その名前にも関わらず、このオプションは “SSL” とともに “TLSプロトコルも選択できます。 バージョン 3.5.3 で追加.

ssl.PROTOCOL_TLSはバージョン3.5.3で追加されていますが、バージョン2.7.13でもすでに追加されています。バージョン2.7.13で追加されているということは、python3.4.2でも同じ挙動するはずと考えるのは甘いでしょうか。たぶん甘いでしょう。わざわざ3.5.3で追加と書いてあるのは、2系から3系に移植する際に実装を後回しにしている可能性があります。

これ以上正確な挙動を知ろうと思うなら、実際の挙動を見る他ないと思います。
たとえばgmailとSTARTTLSネゴシエーションをしたときの通信の様子は次のようになります(2017/10)

>> smtplib.SMTP("smtp.gmail.com", 587)
<smtplib.SMTP object at 0x7671df70>
>> M = smtplib.SMTP("smtp.gmail.com", 587)
>> M.set_debuglevel(True)
>> context = ssl.create_default_context()
>> M.starttls(context=context)
send: 'ehlo [127.0.1.1]\r\n'
reply: b'250-smtp.gmail.com at your service, [114.147.68.118]\r\n'
reply: b'250-SIZE 35882577\r\n'
reply: b'250-8BITMIME\r\n'
reply: b'250-STARTTLS\r\n'
reply: b'250-ENHANCEDSTATUSCODES\r\n'
reply: b'250-PIPELINING\r\n'
reply: b'250-CHUNKING\r\n'
reply: b'250 SMTPUTF8\r\n'
reply: retcode (250); Msg: b'smtp.gmail.com at your service, [114.147.68.118]\nS
IZE 35882577\n8BITMIME\nSTARTTLS\nENHANCEDSTATUSCODES\nPIPELINING\nCHUNKING\nSMT
PUTF8'
send: 'STARTTLS\r\n'
reply: b'220 2.0.0 Ready to start TLS\r\n'
reply: retcode (220); Msg: b'2.0.0 Ready to start TLS'
(220, b'2.0.0 Ready to start TLS')

芽が出た

f:id:takeg:20170824230608j:plain

f:id:takeg:20170824230626j:plain

朝起きたら芽がにょきにょき生えてて気持ち悪かったうれしかったです。たのし~。

園芸はじめました

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

買ったもの

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すると、すべて半角英数字と記号に直されるようです