Pythonメモ

Pythonメモ

rarfile

rarfileモジュールはpip installしただけでは動作しない。unrar.exeをダウンロードしてPATHの通ったフォルダに置いておくことが必要。

pyaudio

まず大前提としてふたつのことをしなくてはならない。

スタート→設定→システム→サウンドに行き、

  • サウンドコントロールパネル→録音タブでステレオミキサーを有効にする
  • マイクのプライバシー設定で「アプリがマイクにアクセスできるようにする」をオンにする

以上を怠るとpyaudio.PyAudio().open()すると

というエラーが出る。

Windowsをクリーンインストールしたあとなど、いままで動いていたプログラムがいきなり動かなくなってはまる。かすかな記憶をたどってステレオミキサーを有効にするところまでは自分でたどり着いたが、マイクへのアクセス権の問題は思い出せなかった。もうこんな無駄なことはしたくないのでメモを残しておく。

ログをとる

いままでwith open~してf.write(datetime.datetime.now().strftime(・・・) + ‘エラーメッセージ’)という感じでログをとっていたが、loggingモジュールというのを使えばdatetimeとか使わなくても日時付きでかんたんにログをとることができた模様。

いままではこんな書きかたをしていた。

datetimeモジュールとtracebackモジュールを使いつつファイルをオープンして・・・とやっているが、loggingモジュールを使うとすっきり書ける。

logging.basicConfigメソッドでフォーマットを設定したのち、例外をキャッチしたところでlogging.exceptionで例外の内容を書きだす。logging.exceptionの場合、エラーレベルはlogging.ERRORなので、logging.basicConfigでlevelを設定する必要はない。

logging.basicConfigでhandlers引数を指定することでPycharmからUnexpected argumentだと注意されるのがうざいのだが。

どうしてもPycharmからの注意が目障りな場合

かなり行数が長くなるのだが…。

loggingでググっても信頼できそうで、かつわかりやすくまとまった記事はなかったのでけっきょく公式ドキュメントを拾い読みするしかなかった(Logging HOWTO — Python 3.8.2 ドキュメント)。

設定の行数がすげー長い。使っているクラスやメソッドも5つもあって覚えられない。毎回ググるのがつらい。

冒頭にいちど長い設定を書けばそれ以降はすっきり書けるので、多くの箇所でログを出したい場合はloggingを使ったほうがいい。しかし、ログを出したい箇所が1個や2個と少ない場合はwith openしてdatetimeとtraceback使いつつ書きだすほうが手間がかからないかも。

文字コード

pythonプログラム内では文字はUnicodeとして扱っている。

たとえばファイルを読み書きするときや、requests.postなんかするときに文字コードというものが出てくる。つまり、文字コードとはプログラムの出入り口で使われるもの。

プログラムの外に文字データを出すとき。たとえばファイルに書き込むときにutf-8やshift-jisなんかの文字コードにエンコードする。

あるいは、ファイルを読み込むときに文字コード表を使ってUnicodeにデコードする。間違った文字コードを使うと文字化けしたり例外となる。

requests.postするとプログラムの外=通信路にデータが出されるのでエンコードされる。ただこの場合、日本語や特殊な記号などはプログラム内ですべてパーセントエンコードされてASCIIコードの範囲に変換されてからエンコードされる。utf-8でもshift-jisでもASCIIコードの範囲内の文字は共通の文字コード。どの文字コードでエンコードしてもおなじになる。

ASCIIコード

ASCIIコードを書きだしてみる。

要は、キーボードに割り当てられている文字や制御文字の文字コードがASCIIコード。こいつらの文字コードはUTF-8でもShift-JISでもASCIIコードと一致する。

0はNULLなので空白。8はBS(Back Space)なので:が削除されている。10はLF(Line Feed)なので改行されている。32は半角スペース。何も表示されていないのは制御文字。11~13が表示されない理由はなんかよくわからん。

UnicodeコードポイントはASCIIコード(を含む文字コードぜんぶ)とは直接は関係ないのだが、結果として0~127の範囲はASCIIコードと一致する。

~について

Unicodeには「波型」と「全角ティルダ」という二つの異なる「〜」が存在する(参考1, 参考2)。

なんかよくわからんが、いちどcp932でエンコードしてからShift-JISでデコードすればいいようだ。

クラスもインスタンスもオブジェクトとして管理されている

test2.pyというモジュール。

これをtest.pyというメインモジュールからインポートして使う。

メインモジュールではPersonクラスを知らないはずなのに、helloメソッドを呼ぶことができる。これはメインモジュールが受け取ったPersonクラスのインスタンスにhelloメソッドが含まれているからだ。

test2.pyをすこし変更する。奇妙な書き方だが、インスタンスではなくPersonクラスそのものをtest関数で返すようにした。

これをtest.pyというメインモジュールからインポートして使う。

Personクラスをインポートしていないのだが、Personクラスでインスタンスを生成することができる。

インスタンスだけでなくクラスもオブジェクトであり、変数に格納できるし、importしなくてもモジュール間でやりとりすることができる。関数もそう。

もっともクラスや関数をこんなかたちでやりとりすることってないと思うけど。

クラスも関数もインスタンスとおなじくオブジェクトなのでは?とすれば上記のようなことができるのでは?と思い、実際にやってみたらやっぱりできたというだけのお話。

すべてオブジェクトで管理している

pythonプログラムはオブジェクトにアクセスすることで処理をしている。自分で定義した関数やクラスだけでなく、インポートしたモジュール(ファイル)やパッケージ(ディレクトリ)も全部オブジェクトとして管理している。

メインモジュール(実行ファイル)が操作することができるオブジェクトはdir関数で一覧を取得することができる。たとえばkerasというパッケージをインポートするとpprint(dir())したときそのなかにkerasが含まれる。

dir(keras)とするとkeras.につなげてアクセスできるオブジェクト一覧を取得できる。それらはkerasディレクトリ直下の__init__.pyで定義・インポートされたオブジェクト群である。

また、from keras import models としてmodelsというモジュールをインポートするとやはりdir()の一覧にmodelsが含まれる。dir(models)とするとmodels.につなげてアクセスできるオブジェクト一覧を取得できる。それらはmodels.pyというファイルで定義・インポートされたオブジェクト群である。

モジュール・パッケージのインポートについて

モジュール=ファイル、パッケージ=ディレクトリ

モジュールの実体はファイルであり、パッケージの実体はディレクトリ(に含まれる__init__.py)。

相対パス

相対パスでインポートする際、pythonコマンドで実行するファイルと同階層以上には昇れない。同階層より上のファイル(モジュール)やディレクトリ(パッケージ)を指定するとつぎのようなエラーとなる。

存在しないファイルやディレクトリを指定してもおなじエラーになるので、同階層で..(親ディレクトリ)を指定した段階で例外を投げていると思われる。

相対パスはパッケージ内で使うことを想定した機能といえる。すなわち、.(現在のディレクトリ)や..(親ディレクトリ)はパッケージ内でのみ使うことができる。

__all__

__init__.pyに定義する__all__は、from package import * と書いた際にインポートされるファイル(モジュール)を指定する。__all__を定義しないとfrom package import * としてもなにもインポートされない。ただし、ファイル名を明示してインポート(from package import module)すればインポートできる。

__all__を定義することでインポートされるファイルを*から隠すことはできるが、直接指定してインポートすればインポートできてしまう。完全に隠すことはできないと思われる。

__init__.py

パッケージ(ディレクトリ)packをインポートする( import pack)と、pack直下の__init__.pyが実行される。そのなかで定義・インポートされるオブジェクト(関数やクラスや変数やモジュールやパッケージ)をたとえばpack.funcなどとpack.につなげて書くことで使うことができる。__init__.pyに定義・インポートされていないオブジェクトにはアクセスできない。

まぎらわしいのは、たとえば__init__.pyのなかで

としたとき、pack.funcにアクセスできるのは当然だが、pack.mod.funcとしてもアクセスできる。つまり、「from .mod import 〇〇〇」としたとき、〇〇〇だけではなくmod全体もインポートされる。だからpack.mod.funcとしてもアクセスできるのだ。

mod全体がインポートされているので、modに含まれているfunc以外にもアクセスできる。pack.mod.func2などとするとアクセスできる。ただし、pack.func2としてはアクセスできない。

pack直下の__init__.pyで「from .mod import func」としたとき、packにぶら下がるオブジェクトはfuncとmodだけである。だからpack.func2はエラーとなる。

以上のことはパッケージ内に限った話である。たとえば

とするとmodにアクセスできるが、packにはアクセスできない。pack全体もインポートされるということはなく、modのみがインポートされる。パッケージ内のルールでパッケージ外のルールを類推する、あるいはその逆をすると混乱してしまう。完全に分けて考えるべきだ。

__init__.py その2

前節のとおり、__init__.pyに定義・インポートされていないオブジェクトにはアクセスできない。ただし__init__.pyでインポートされたモジュールの中でインポートされる__init__.pyと同階層のモジュールにもアクセスできる。

requestsというパッケージで確認する。requests直下にはhelp.pyというファイル(モジュール)があるが、__init__.pyでインポートされていないのでこうなる。

ところが、_internal_utils.pyというファイルも同様に__init__.pyでインポートされていないにもかかわらずこうなる。

これにはかなり悩んだが、__init__.pyでインポートされているほかのモジュールで_internal_utilsがインポートされているからなのだ。findstrで調べたところ、models.pyとかsessions.pyなどからインポートされている。

ほんとうはこんなことやらないほうがいいんだろうけど、requestsの下にディレクトリsubをつくり、そのなかにtest.pyをつくった。そしてmodels.pyに「from .sub import test」を追記したうえでどうなるか。

ここからわかることは、__init__.pyでインポートされたモジュールからインポートされたモジュール(とパッケージ)のうち、__init__.pyと同階層にあるモジュール(とパッケージ)のみがアクセス可能になるということ。

傍証として、models.pyではdatetimeがインポートされているがこうなる。

結論。たとえばrequestsというパッケージをimport requests としてインポートしたとき、requests.につなげてアクセスできるオブジェクトは以下。

  • __init__.pyで定義・インポートされているオブジェクト
  • __init__.pyでインポートされたモジュール内でインポートされている、__init__.pyと同階層のモジュール

うーん

とすると、pack.subpackだけでなくpackにもアクセスできる。奇妙な感じがするが、そんなインポートの仕方はしないので考えないことにしよう。

ふと思いついて

としてみたらエラーになった。

だとやはりmatplotlibにアクセスできる。

from ~ import ~

import ~でインポートできるのはパッケージ(ディレクトリ)やモジュール(ファイル)だけ。関数やクラスはインポートできない(import pack.mod.funcはできない)。from ~ import ~ならパッケージ・モジュールに加えて関数やクラスもインポートできる。ああ、まぎらわしい。

要するに

なにがなんだかわからなくなってきた。パッケージ外からインポートする場合について考える。

パッケージpackがインポートされると直下の__init__.pyが実行され、そこで定義されたオブジェクトがパッケージにぶら下がる。そこで定義されていないモジュールはファイルが存在してもぶら下がらない。

モジュールをインポートすると、そのモジュール内で定義されたオブジェクトがモジュールにぶら下がる。

関数やクラスをインポートすると、グローバルなスコープ(?)にぶら下がる。

結局

モジュールやパッケージをテキトーにインポートしてみて、dir(pack)やdir(mod)として目的の関数やクラスが使えるかどうか確認すればおk。

from mod import * はしないほうがいい

たとえばmod.py

がメインモジュールと同階層にあったとして、メインモジュール

を実行するとこうなる。

requestsまでインポートされてしまっている。これは気持ち悪い。

*でインポートするとモジュール内でインポートされているライブラリなんかもぜんぶインポートされてしまうので単純に無駄だし、名前が衝突する恐れも高まる。このように

__all__を定義すれば限定できるようだが、公式ドキュメントも*の使用を推奨していないし、使うものだけインポートすればいいと思う。

参考

6. モジュール — Python 3.8.2 ドキュメント

標準出力に上書き・追記する

print関数で大量にログを出力すると、行数が際限なくふえて見づらくなることがある。sys.stdout.writeを使うと標準出力の上書きや追記ができる(後述するが、べつにprint関数でもできる)。

まずは上書き。\r(カーソルを文頭へ戻すことを表す制御文字(Carriage Return))を入れる必要がある。sys.stdout.flush()はあってもなくてもうまくいくが、処理が重たくなると必要なのかもしれない(未確認)。

つぎに追記。追記の場合はsys.stdout.flush()が必要なようだ。

print関数で上書き・追記する

ググって出てくる方法がsys.stdout.writeを使った方法だったので、print関数ではできないと思い込んでいたがそうではなかった。\rを調べるとカーソルを文頭に戻す制御文字とのこと。これを使えばprint関数でもできるんじゃね?と思って試したらふつうにできた。

print関数で改行をしないようにendオプションを指定しつつ、\rを使えばできる。

追記は以下。sys.stdout.flush()が必要。

pythonスクリプトをプロンプトを開かずにダブルクリックで実行する

以下のようなvbsファイルをつくるとできる(参考)。

ショートカットのアイコンを変更するには、プロパティ→ショートカット→アイコンの変更→参照で画像ファイルを選択する。

エラーログをとる

以前はこんな感じにしていたが、これだとどこで例外発生したかわからない。行数の情報がほしい。

ググるとtracebackモジュールのformat_excメソッドで例外の情報がとれるらしい。

ZIPファイルを展開し中身をいじってまた固める

ファイル操作するときは

毎回なにからはじめるか混乱するが、まずはパス(のリスト)を取得すべし。なぜならファイルを削除するにもリネームするにもつくるにも(ZIPで固めるとか)、まずはパスが必要。

インポートするモジュールはos, glob, shutil, zipfile。sysモジュールはなぜかosモジュールとセットでインポートしがちだけど、ファイル操作ではけっきょく使わない。

  • glob.globで絶対パスのリストを取得
  • os.path.exitsでファイル/ディレクトリの存在確認
  • shutil.rmtreeでディレクトリごと削除(からのos.mkdirで同名のディレクトリ生成)
  • zipfile.ZipFileで固める

windows環境ではファイル読み込み・書き出し時のデフォルト文字コードがSJIS

なので、以下のようにencodingオプションを指定してUTF-8で書き込む。

読み込むときもencodingを指定する。意味的にはdecodingかなと思うが、そんなオプションはない。

後方参照

\\1ではなく\1で後方参照したい場合はraw文字列にする。

ファイル名の一括置換

ファイル名の一括置換は折にふれてやるのでメモ。はじめos.walkを使っていたが、globモジュールのほうが便利。

ビルトインサーバー

コマンドプロンプトから以下を実行。

pythonのcgiプログラムでは以下を記載する。

UNIXの場合は1行目に以下を追加。windowsでは不要。

POSTを受け取るには以下。

変数sにname属性がnameの要素のvalueの値が入る。

みたいな感じでHTMLを返す。
CGIプログラムの実行権限に注意。
これでいちおうpythonでクライアントにHTMLを返せるが、とはいってもHTMLはPHPで生成するのが楽だと思う。慣れてるし。
PHPでPOST受け取ってexecでpythonに流して処理の結果をPHPに戻し、最終的にクライアントに返す画面はPHPでつくる。
データ処理はpythonで、画面づくりはPHPで。

ビルトインサーバーを止めるには

Ctrl+Cでは止まらないので、Ctrl+Breakで止める。Breakキーはキーボードの右上のあたりにある。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする