ベスパリブ

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

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