2016年11月19日

[py]スコープはモジュールと関数しかないそうだ

pythonのスコープが全然理解できていない。
初出場の変数でエラーになるかと思えば、別の場所で使っている同じ変数名で「また使ってますよ」みたいな警告が出たりする。

なんなのだ、いったい!

 

Community Blog - Python の名前空間とスコープ

python 2.7.2の記事だが、スコープはモジュールと関数しかない、と書かれている。
それって、ブロック内の変数という見方ができない、ということか。。。
if文の中で使った変数が、他のところで同じ名前を使うと警告される理由が分かった。

変数をもっと局所的にしたいときは、関数の中に関数を書くことができるから、それで分離するとよいらしい。

val1 = 10


def func1():
    val2 = 20
    print 'val1=', val1
    print 'val2=', val2

    def func1in(v):
        if v == val1 * 2:
            return val1 * 4
        else:
            return -1
    val2 = func1in(val2)
    print 'val2=', val2
func1()

結果

val1= 10
val2= 20
val2= 40

func1in()の引数をなくして、これでもいける。

val1 = 10


def func1():
    val2 = 20
    print 'val1=', val1
    print 'val2=', val2

    def func1in():
        if val2 == val1 * 2:
            return val1 * 4
        else:
            return -1
    val2 = func1in()
    print 'val2=', val2
func1()

ただ、func1in()の中でval2への代入を行うと、ifで使っているval2がエラーになる。

val1 = 10


def func1():
    val2 = 20
    print 'val1=', val1
    print 'val2=', val2

    def func1in():
        if val2 == val1 * 2:
            val2 = val1 * 4
        else:
            val2 = -1
    func1in()
    print 'val2=', val2
func1()

じゃあ、func1in()の中に「global val2」を書けばよいかというと、これは実行時エラーになってしまう。
もしかして「globalじゃなくてlocalでは?」とやってみたが、そんなキーワードはなかった。。。

できなさそうな感じがするけれども、根拠の情報は出てこなかった。
まあ、初出場のグローバルじゃない変数は代入した時点でローカル変数になるらしいから、これも同じことか。

2016年11月18日

[py]モジュールがあるフォルダのファイルを指定したい

SQLiteを使ってデータ管理をしている。
単独で使いたいこともあるので、ファイルを別にして、関数にして呼べるようにした。

そこまではよかったのだが、SQLiteへはファイル名しか指定していないので、pyファイルが別のフォルダにあると、そのフォルダでDBファイルを作ってしまうのだ。
これではDBにしている意味が無い。。

 

どこか固定のフォルダにした方がよいのだろうけど、フォルダを設定するのも面倒なので、SQLiteの処理を関数化したファイル(モジュール、でよいのか?)がある場所にDBファイルを作ることにした。

FULLPATH = os.path.realpath(__file__)
PATH, FNAME = os.path.split(FULLPATH)
DBFILE = PATH + os.sep + 'txlist.db'

os.sepは、パスのデリミタらしい。
「/」とか「\」とか。
Windowsもスラッシュだったらいろいろ楽だったのにねぇ。

 

pythonは、const/final/readonlyのような定数がないので、慣れないといろいろ気を遣ってしまう。
スコープも私が思っているような範囲と違うので、スクリプト言語ならではなのかもしれん。

2016年11月17日

[py]外部変数と同じ名前のglobalではない変数がある場合、書込みを行うとローカル変数ができる

まだまだpythonはわからないことだらけ。。。

いま、pyファイルにべたっと処理を書いているので、少しずつ関数化させている。
ライブラリのような形ではなく、ファイルをモジュールと見なして、中で値を持つような作りにしている。
後で不都合が出そうだが、そういうのも含めて勉強だ。

 

DB_NAME = 'db_all.db'
sql = 'CREATE TABLE IF NOT EXISTS clients(filename TEXT, owner TEXT)'


def create_db():
    conn = sqlite3.connect(DB_NAME)
    cur = conn.cursor()
    cur.execute(sql)     ...

SQL文を変数にする理由はないし、関数外に置く理由はさらにないのだが、ちょっと外に置いてみた。
そうすると、一番最後の「sql」でpyCharmがエラーを出している。
Unsolved referenceとのこと。

私の理解では、

  • 関数の外に置いた変数は、グローバル変数
  • 関数内で値を変更したかったらglobal指示するけど、参照だけなら気にせずできる

だったのだが、そうではないのか?
pyCharmが強い警告の意味で表示させているのかと思ったが、実行させるとエラーだった。

でも、ファイル名のDB_NAMEはエラーになっていない。。
試しにsqlをSQLにすると、これだけでエラーにならない。
慣習じゃなくて、言語として大文字かどうかを判定しているのか??

 

が、実は上のコードには続きがもう少しある。

DB_NAME = 'db_all.db'
sql = 'CREATE TABLE IF NOT EXISTS clients(filename TEXT, owner TEXT)'


def create_db():
    conn = sqlite3.connect(DB_NAME)
    cur = conn.cursor()
    cur.execute(sql)

    sql = 'SELECT * FROM clients WHERE filename=?'
    cur.execute(sql, ('file1.txt',))     ...

この後ろに、さらに同じ名前の変数に代入する処理があったのだ。
そちらがあるために、その前にあるsqlはグローバル側のsqlを参照できなくなったようなのだ。
試しに、グローバル側をsql2にして、さっきエラーになったところもsql2にすると、エラーが出なくなった。
そういうものなんだ。。。

 

9. クラス — Python 2.7.x ドキュメント

ここに記載があった。
今回の場合では、グローバル側に同じ変数名sqlがあって読み出し専用になるのだけど、その後ろにsqlへの書込みがあるため内側のスコープで新しいローカル変数sqlを作ることになり、結果としてその前にあるsqlもローカル変数のsqlとして見られるようになり、初出場で参照しようとすることになってエラー、という流れだろう。

インタプリタだから上から順に見ていくのかと思ったけど、この動作からするとファイルを一度読んで構築するか、少なくとも関数レベルでは一度に構築しているのだろう。
順番は大切、ということだ。

2016年11月16日

[py]連想配列ではなく「辞書」

やりたいのは、構造体だったのだ。
classで実現するのだろう、きっと。

それをグローバル変数でいくつか持っておいて、MQTTでメッセージを受信したとき、受信内容からグローバル変数の内容を参照する、というようなことをやろうとしている。
構造体は後回しにするとして、グローバル変数の中から期待するデータを取り出す、という検索のしくみが必要になるのだ。

データをどのくらいの期間つかうことになるのか見積もりできてないのだけど、短期間でよかったり、データ量が少ないのであれば連想配列がよいのかな、と思った。
C++でいうところの、std::mapだ。

 

pythonだと、辞書、というそうだ。
そして、オブジェクトでデータを扱うからか、数値だろうと文字列だろうと扱えるようだ。

ary = {'val1': 2, 'mon': 'ロンドン橋'}
print ary['val1']
print ary['mon']

 

ただ、キーは文字列限定のようである。
まあ、数字をキーにしたかったら、数字を文字列に変換させればよいだけのことか。

 

渡す値は、

{ キー : 値 }

というようにコロンで分けられている。
これを、間違えてコンマにしてしまうと、「dictionary update sequence element ~」というエラーが出て悩むことになる(pyCharmでもwarningが出ない)ので、注意しよう(10分くらい悩んでいた)。

2016年11月15日

[py]sqlite3でテーブルがあるときに出る例外がうっとうしい

pythonで、データを保持する処理をしたくなった。
最初はファイルに保存することを考えていたのだが、SQLiteが使えるようなので、それを使っている。

 

が、C APIではエラーで済むところで例外が返ってくるので、「成功したらそれでよいし、失敗してもそれはそれでよい」というような場合の処理を書くのが面倒だ。
特に、今はきっちりした処理を作っているわけではないので、その気持ちが強い。

たとえば、こんなコードを書いたとしよう。

conn = sqlite3.connect(DB_NAME)
cur = conn.cursor()
cur.execute('CREATE TABLE clients(filename TEXT, owner TEXT)')

DBファイルがないときは通るのだが、既にテーブルがあると例外が起こる。

Traceback (most recent call last):
  File "sqlitest.py", line xx, in 
    cur.execute('CREATE TABLE clients(filename TEXT, owner TEXT)')
sqlite3.OperationalError: table clients already exists

じゃあ、こう書くか?

exists = os.path.exists(DB_NAME)
conn = sqlite3.connect(DB_NAME)
cur = conn.cursor()
if not exists:
    print 'create new TABLE'
    cur.execute('CREATE TABLE clients(filename TEXT, owner TEXT)')

回避はできるのだけど、わざわざこれやるの?という気もしてしまう。
それに、SQLiteのことをやるのにファイルの有無で確認する、というところがすっきりしない。

もちろん、tryで囲んでしまえばよいのだけど、CREATEのところだけ囲むのも格好がよくない気がする。
うーーん・・・

 

と、探していると、こういうのがあった。
Python sqlite3.OperationalError Examples

IF NOT EXISTS ?
そういうのがあるんだ!

conn = sqlite3.connect(DB_NAME)
cur = conn.cursor()
cur.execute('CREATE TABLE IF NOT EXISTS clients(filename TEXT, owner TEXT)')

うん、例外が出なくなった。
そうね、SQLのことはSQLのことで済ませられるということなのね。。。

2016年11月14日

[esp8266]RTOS版がgithub移行?

GO TO GITHUB for LATEST SDK|请至 Github 下载最新 SDK - ESP8266 Developer Zone

いつものEspressifの人じゃないのだけど、githubがespressifだから本家なのかな?

 

https://github.com/espressif/ESP8266_RTOS_SDK

今日(2016/11/14)のところ、1.5.0が上がっている。
11/11に上がったようだ。

[Latest Release/最新发布] ESP8266 SDK-20161111 - ESP8266 Developer Zone

 

以前、ノーマルのSDK版を使って、受信バッファに1回では収まらないデータを処理するのがめんどうだったのだ。
Linuxみたいに、同期で受信待ちにできないので、「○○受信中状態」みたいなのを作り、一度コールバックを抜けてまた受信したら継続する、なんてことをやっていた。

RTOS版だったら、楽にできたのだろうか。
気になるが、気力がなかなかわかないのよねぇ。。。

 

Windowsでコンパイルする準備もできているようなので、そちらは準備してみたいところだ。
コンパイラは、こちらのようだ。
pfalcon/esp-open-sdk: Free and open (as much as possible) integrated SDK for ESP8266/ESP8285 chips

思い出した。
この前、Ubuntu on Windowsで64bit環境を作ろうとして、結局あれこれやってやめたやつだ。
micropythonというやつも気になるし、うーーーん。。。

2016年11月13日

[py]未使用の引数を黙らせたい

pythonでMQTTしてみようと、pahoというライブラリを使っている。
接続したときにコールバックしてもらいたいのだが、そのコールバック関数の内容(signatureというのか)はこうなっている。

on_connect(client, userdata, flags, respons_code)

userdataやflagsなどは使わないのだが、使わないとpyCharmが「未使用ですよ」と言ってくる。
GCCだとvoidでキャストして黙らせることもあるのだが、pythonというかpyCharmだとどうやるのだろうか?

 

python - How can I denote unused function arguments? - Stack Overflow

1番目の回答は、ここから後ろ全部不要、という場合だろう。
(あまり調べてない)

2番目の回答で、delというキーワードを使うようになっていた。
リストの要素を消すときなどに使うようなのだが、オブジェクトの削除の意味でもあるようだ。

 

今回はdelで消すことにした。

[py]グローバル変数は前方参照じゃないんだ

こんなコードを書いた。

fname = ''

def change_fname(name):
    global fname
    fname = name

fname = 'default'
print fname
change_fname('kuma')
print fname

動くのだが、pyCharmが「default」を代入しているfnameのところでwarningを出している。

image

 

グローバル変数は最初に定義しないとダメだと思っていたのだが、まさか・・・。

def change_fname(name):
    global fname
    fname = name

fname = 'default'
print fname
change_fname('kuma')
print fname

warningが消えた。
それに、ちゃんと動く。
動くんだ。。。

 

いやぁ、スクリプト言語って、難しいわあ。

[py]sqlite3のfetchall()は吸い取る

眠たいので、起きている現象だけ書いておく。

pythonでデータを保存しようとして、SQLiteを使うことにした。
ファイルがないときは、connect()してcursor()で取得したら、CREATE TABLEして、INSERT INTOさせるようにした。
そして、そのあと確認のため、select文を実行させるようにした。

 

ネットで調べると、cursorに対してSELECTのexecute()したあと、fetchall()で件数分の処理しているようだった。
が、まねしても件数が0になっている。。
なぜだ・・・。

sql = 'SELECT * FROM clients'
ret = cur_all.execute(sql)
print 'len all=', len(cur_all.fetchall())
lists = cur_all.fetchall()
print 'lists all=', len(lists)
これで試したのだが、'len all='のところは数字が出てきて、'lists all='のところは0のままだった。
 
順番を入れ替えてわかったが「fetchall」だから、データを吸い取ってしまうようだ。
だから、fetchall()した後で見に行くと、0になっているのだ。
 
そうか、そうなのか。。。

2016年11月11日

[py]pydocはHTMLに出せばUTF-8日本語も見える

使い方が面倒なpythonライブラリを作っていたので、後から思い出せないだろうと思い、コメントを書いている。
ライブラリを作っているというよりは、コードを書いて、ここは他でも使えるかも、と思ったら関数化しているだけなので、かなり行き当たりばったりだ。

 

こんな感じのソースを作っている。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
test library
"""

##
# @file    test.py
# @brief   test library

##
# テストデータ
TEST_VALUE = '\x12'


##
# テスト関数
# @param[in]        msg         メッセージ(str)
# @return           bytearray値
def testfunc(msg):
    # テスト関数
    return msg.decode('hex')

 

これを、cygwinのpydocに通すと、こうだ。

image

しかし、-wオプションを付けてHTML出力すると、こう。

image

コンソールでちょっと確認する、という用途には使えないのだが、まあ、これはこれでよいかな、と思う。
doxygenコメントを入れているので、pydocでもフォーマットはされていないながらなんとなく引数の説明もできているし。

 

doxygenに通すと、こんな感じだ。

image

悪くないんじゃないかね。