ベスパリブ

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

FindWindowに「タイムアウトするまで探し続ける機能」欲しい!!!!

うるさい!!!
WinAPIの話です。

FindWindowの問題点

FindWIndowでウィンドウハンドルを取得する際、PCが重かったりするとウィンドウの表示が遅れたりして取得に失敗したりするので、FindWindow()前にSleepするという方法がありますが、これもどれだけSleepすればよいかという問題を抱えており、PCが激重だった場合、Sleepして待っても結局ウィンドウハンドルが取得できないという問題も抱えています。

このような問題はFindWIndowに「タイムアウトするまで探し続ける機能」があればそれで解決する話なので、そのようなラッパー関数を作成しました。できれば標準で欲しかった。

Declare Unicode Function FindWindow Lib "user32.dll" Alias "FindWindowW" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr

Public Function FindWindow2(ByVal cnm As String, ByVal cap As String, Optional ByVal timeout As Integer = 10000) As IntPtr
    ' ウィンドウが見つからない場合、timeoutミリ秒経過するまで無限ループで取得し続ける
    ' タイムアウトしたら0を返す
    ' 関数名はもっと考えよう
    Dim hWnd As IntPtr = 0
    Dim sw As New System.Diagnostics.Stopwatch()
    sw.Start()
    While 1
        System.Windows.Forms.Application.DoEvents()
        hWnd = FindWindow(cnm, cap)
        If hWnd <> 0 Then
            Return hWnd
        End If
        If sw.ElapsedMilliseconds >= timeout Then
            ' タイムアウトした
            Return 0
        End If
        'System.Threading.Thread.Sleep(500)
    End While
    Return hWnd
End Function

CPU使用率が気になる場合は、ループ内で適当な時間Sleepさせれば良いと思います。

2018年の目標

2月の終わりですが、2018年の目標を決めました。

1. 英語
2. 競プロ
3. 機械学習系のプロダクトを1つ作成

英語を一番頑張りたいと思います。
他2つはジャストアイデアですが、興味がある分野なのでやっていきたいと思います(やらなそう)。

英語

・英検1級
・TOEIC900台
を目標とします。
たしか3年前くらいのTOEICが600台だったような気がします。
現状はReadingはまあまあできます。
Listening、Writing、Speakingがボロボロです。
なので結構強気の目標です。
なんで英検なのかっていうのは、WritingとSpeakingがあるからです(Writingは申し訳程度ですが)。
TOEFLにしろよと突っ込まれそうですが、TOEFLには怖いイメージしかないので英検にしました。

競プロ

最近競技プログラミングをはじめました。
全然問題解けないけど面白いので、趣味としてやろうと思います。
目標スコアは……よくわかりません。とりあえず一年やってみた結果どの程度になるのか見ようと思います。

機械学習系のプロダクトを1つ作成

なんかします(やらなそう)。

以上です。

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