みろりHP


緑色さんの多目的ブログ
みろりえいちぴー
ごゆるりとおくつろぎあさーせ。
| カテゴリ:プログラミング |
Gitノート



2015 年のエンジニアに Git は欠かせない。
っていうフレーズを読んで以来、そのGitというものが気になっていてな。最近そろそろと手を出してみたらクッソ便利じゃんこれ。バージョン管理システムっていうらしいぜ。つまりは、スクリプトのバックアップがとれるわけだろ? しかもフォルダ単位でカンタンに。そしてそのバックアップの履歴を見ることもサクサクできて、そのバックアップ群をどっかのサーバに上げれば他の人と更新をいっしょにできるんだろ? サーバとかよくわからんから俺には関係ないな、ってちょっと思ったけどそれってDropboxでもできるんだろ? だったら全然できちゃうよ。
しかも、ブランチってシステムを使えば、上書きしあっちゃったりしないようにできるんだろ? すげーな。エンジニアではなくても全然有用じゃん。
というわけである程度使ってみて、よく使ったコマンドとかエイリアスとかノートしとく。



■ 準備

インストールするまで
$ brew doctor
$ brew install git
$ git --version

Gitのテキスト入力を相棒のSublime Text3で行う準備
$ ln -s /Applications/Sublime\ Text.app/Contents/SharedSupport/bin/subl /usr/local/bin/subl
$ git config --global core.editor "subl -w"

設定をする
$ git config --global user.name Midoriiro (ユーザ名登録)
$ git config --global user.email メアド (メアド登録しないと使えなかったような気がする)
上のを打つとユーザフォルダに .gitconfig ができるので [alias] 項目を作ってエイリアスを書く。俺はこんなのを用意してみた。
graph = log --graph --date=short --pretty='format:%C(yellow)%h %C(green)%cd %C(blue)%an%C(red)%d %C(reset)%s' --all
graph5 = log --graph --date=short --pretty='format:%C(yellow)%h %C(green)%cd %C(blue)%an%C(red)%d %C(reset)%s' --all -n 5
graph10 = log --graph --date=short --pretty='format:%C(yellow)%h %C(green)%cd %C(blue)%an%C(red)%d %C(reset)%s' --all -n 10
stt = status -uno
diffno = diff --name-only
diffwd = diff --word-diff
co = checkout
br = branch
inicom = commit --allow-empty -m 'Initial Commit.'
adda = add --all



■ 実用(上のエイリアスを適用しているものとする)

基本
$ git init (開始)
$ git inicom (空っぽで保存)
$ git adda (全部ファイルをステージング)
$ git commit -m "保存コメント" (保存)
$ git commit --amend (ひとつ前のコミットを修正)
$ git stt (状態確認)
$ git graph (コミット履歴を見る)
$ git diffno リビジョン番号 リビジョン番号 (リビジョン間でどのファイルが変更されてたっけなって確認)

Gitに含まないファイルの登録
.gitignoreを作成する。「python gitignore」でググったらよいのが出てきたので使わせてもらった。
$ git rm -r --cached (.gitignoreが反映されないとき)

ブランチを作って作業
$ git br ブランチ名 (新しいブランチ作る)
$ git co ブランチ名 (作ったブランチに移動)
$ git merge --no-ff 取り込むブランチ名 (ブランチを今のブランチに吸収)
$ git br -D ブランチ名 (いらんブランチを削除)

masterブランチの内容を他ブランチに反映させたいとき
pullとかでmasterを最新状態にする。
他ブランチに移動する。
$ git rebase master
コンフリクトが起きたらそれを解消する。
$ git add コンフリクトしたファイルのパス
$ git rebase --continue

やっべ間違えたってとき
$ git reflog (戻る履歴のリビジョン番号を確認する)
$ git reset --hard リビジョン番号 (指定したところまでフォルダをリセットする)
$ git co リビジョン番号 -- ファイルパス (ファイル単位でリセットする)

リモートリポジトリを使う(俺はDropboxでやってみた)
まずcdコマンドとかで対象フォルダまで行く。
$ git init --bare --shared=true (フォルダをリモートリポジトリに)
ローカルリポジトリへ戻る。
$ git remote add リモートリポジトリ名 リモートフォルダまでのパス (リモートリポジトリを登録)
$ git remote rm リモートリポジトリ名 (いらんくなったら消す)
$ git push リモートリポジトリ名 ブランチ名 (ローカルの保存履歴をリモートに上げる)

他のPCでリモートの保存履歴を引っ張ってくる
$ git init
$ git clone リモートフォルダまでのパス (最初の一回はこう。DLみたいなもんか)
$ git pull リモートリポジトリ名 ブランチ名 (二回目以降の履歴更新)



とりあえずこのあたりを使って、Mac-Windows間のスクリプト移動、更新をやってみてる。外部のPCにデータを置くとなるとサーバが必要になってくる、みたいな思い込みがあったので、Dropboxで大丈夫ってのは目ウロコだったぜ。


| 緑色 | プログラミング | comments(0) |
| カテゴリ:プログラミング |
Python accept_mouse_click pygameアプリ・クリック変換モジュール



前日譚: Python pygameをMacで使うのはヤメとこうぜ
前日譚では問題点がふたつあった。
  • mp3が読めねー
  • キー入力受け付けねー
後者のキー入力をマウスクリックで代用するモジュールを作ったぜ。現在緑さんはMacとWindowsの2台使いなわけだけれど、やっぱMacでもpygameアプリを開けないと不便だ。mp3のほうはムリ。OSがWindowsじゃなければ読み込みをスキップするくらいしか思いつかん。



# accept_mouse_click.py

import pygame.locals as pgl

def switch(event,
           left_click  =pgl.K_z,
           scroll_click=pgl.K_c,
           right_click =pgl.K_x,
           scroll_up   =pgl.K_UP,
           scroll_down =pgl.K_DOWN):
    '''マウスクリックをキー入力に変換します。
    かえたいとこだけ指定してください。
    '''

    change_table = {
        1: left_click,
        2: scroll_click,
        3: right_click,
        4: scroll_up,
        5: scroll_down,
    }

    if event.type != pgl.MOUSEBUTTONDOWN:
        return event

    if event.button not in change_table.keys():
        print('<accept_mouse_click NOTICE> Unknown mouse click:'
              + str(event.button))
        return event

    return DummyEvent(pgl.KEYDOWN, change_table[event.button])

class DummyEvent:
    '''eventのダミー。
    オリジナルのeventがreadonlyだったため、ダミーを作ります。
    ようはtypeの中にMOUSEBUTTONDOWN、keyの中にK_*が入ってりゃいいんだからさ!
    '''

    def __init__(self, type_, key):
        self.type = type_
        self.key = key
いやあモジュールを作るのにも慣れてきたなあ! クラスなんかもサクッと作っちゃったりしてね! ほんで使い方が以下。

for event in pygame.event.get():

    # 左クリックをZキー、スクロールボタンをAキーに変換したい場合。
    event = accept_mouse_click.switch(
        event,
        left_click=K_z,
        scroll_click=K_a
        )

    if event.type == KEYDOWN:
        # 以下、キー入力を受け付けるスクリプト……。
こんなのをこれまでのpygameアプリにぺたぺた貼り付ければ、Macでもマウス操作で遊べるようになるというわけだ。



今回のポイントは DummyEvent クラス。このモジュール、ようは event オブジェクトの中身を覗いてみて、マウスクリックだったらキー入力に入れ替えるってことをしてる。でも実は event オブジェクトの野郎が生意気にもreadonlyだったんだよな。だから書き換えられない。どうしたもんかと頭を捻ったのだが、こいつがどんな高度なクラスから生成されたオブジェクトだろうと、結局必要なのは type パラメータと key パラメータに定数が入ってるという事実だけなんだよな。であれば、そのふたつのパラメータをもったオブジェクトを自分で作っちまえばいいじゃんという流れで解決。やったー!

| 緑色 | プログラミング | comments(0) |
| カテゴリ:プログラミング |
Python begin_set 俺式プログラム始動モジュール



なんか知らんけど、プログラム作るとき、以下のような機能を毎回書いてることに気付いたんだよ。
  • プログラムの実行場所を、実行pyのある場所に移す。(でないと相対パスで画像とかを呼べない)
  • 実行py以下全ディレクトリを sys.path に登録する。(そうすることで階層構造になってるプログラムのどこからでも相対インポートなしでお互いをインポートできる)

だからこれら機能をサクッと全実行してくれるモジュールを作って、毎回スクリプトの最初でそれを読み込んで実行しちゃうことにしたぜ。



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

'''begin_set

俺式アプリ準備セット。
    1. カレント移して
    2. カレント以下のディレクトリを取得して
    3. 全部インポートパスに登録しますよ。

使い方はこう!
    import begin_set
    begin_set.exec_all(__file__)

でどうぞ! これを直接実行してもちゃんとテスト動作でうごくよ。

========================================
バージョン1.0(2017-04-19)
    まあこんなもんですかね。
バージョン1.1(2017-08-31)
    cd_がまともに動いてなかったので修正。ただし__file__を渡すことになった。
    加えてリファクタリングを行いました。
バージョン1.2(2017-09-01)
    os.path.abspath追加。
'''

import os
import sys
from pprint import pprint


def cd_(file_path):
    '''カレントディレクトリを file_path の場所に移す。'''

    if not isinstance(file_path, str) or not os.path.exists(file_path):
        raise ValueError(f'No such file: {file_path}')

    os.chdir(os.path.dirname(
        sys.executable
        if hasattr(sys, 'frozen') else os.path.abspath(file_path)))


def get_directory_tree(top_dir, rmpycache=True):
    '''トップディレクトリ以下全ディレクトリのリストを入手します。
    デフォルトでは__pycache__は除きます。'''

    tree = [os.path.abspath(top_dir)]
    for directory in tree:
        # ファイル内包表記と高階関数にハマってるのでこんなことになってる。
        # 見やすさ度外視! 読みづれえ!
        tree.extend([
            file for file in map(
                lambda file: directory + os.sep + file, os.listdir(directory))
            if (os.path.isdir(file)
                and not (
                    rmpycache and os.path.basename(file) == '__pycache__'))
        ])
    return tree


def remake_import_path(directory_tree):
    '''インポートパスに追加する。'''

    sys.path = directory_tree + sys.path


def exec_all(file_name):
    '''このモジュールをフル実行します。引数には__file__を渡せばいいと思います。'''

    cd_(file_name)
    remake_import_path(get_directory_tree(os.path.dirname(file_name), True))


if __name__ == '__main__':
    print(f'__file__: {__file__}' + '\n')

    print('cd_実行後の os.getcwd():')
    print(os.getcwd() + '\n')

    print('get_directory_tree(os.path.dirname(__file__), True):')
    pprint(get_directory_tree(os.path.dirname(__file__), True))
    print()

    print('make_import_path(directory_tree)後のsys.path:')
    remake_import_path(get_directory_tree(os.path.dirname(__file__), True))
    pprint(sys.path)




pythonのいろんな機能を使えてて、なんだか成長を感じるぜ。PEP8に従ってみてるし、isinstance()とか使ってるし、raiseで例外投げてるし、ファイル内包表記とかmap()とかもネットの丸写しじゃなく理解してるし。最近、ドキュメントをつけるためむかしの作品を見返してるから余計にそう感じる。


| 緑色 | プログラミング | comments(0) |
| カテゴリ:プログラミング |
Joshua Block『Effective Java 第二版』 後半



前半の続き。



命名パターンよりアノテーションを使おう
  • @Test public static void m1()
    の@部分のことをアノテーションっていうそうだ。うん? デコレータのこと? って思ったけど、ちょっと違うみたい。まあつまりは、testM1()、testM2()、みたいな名前を使うよりも @Test m1()、@Test m2()、みたいな書き方をしたほうがクールってことでいいんじゃないかな。

メソッドの最初でパラメータの正当性をチェックする
  • 確かにこれは必要だと思ってた。strを期待してるところにintがきてないか、とかそういうチェックをしろってことだよね。

throwがあるときはその条件をJavadocへ書く
  • raiseがあるときはその条件をdocへ書く、って言い換えてよさそう。

メソッドのシグニチャは慎重に作ってね
  • メソッド名はわかりやすく。メソッドをたくさん作りすぎない。パラメータは4個以下にする。同じ型のパラメータを何個も続けないこと。booleanよりもenumを用意すること。どれもなるほどって感じ。

オーバーロードするときは、それがオーバーライドでできないか考えて
  • 引数デフォルト値を作りまくってるときは、メソッドのオーバーライドで代用できないか考えて、と言い換えてよさそう。

可変長引数を使うときは第一引数に引数をつけたほうがいい
  • (int... args)
    じゃなくて
    (int a, int... otherArgs)
    にしよう。

配列を表示するときは
  • System.out.println( Arrays.toString(target) )

配列とかコレクションを返すメソッドがnullを返すのはヤメよう
  • クライアント側にnull判別コードが必要になるのを防ぐため。nullを返す代わりにから配列を返しましょう。いまさらだけどnullってのはpythonのNoneのことか。

ドキュメントコメントをしっかり書こう
  • メソッドの事前条件、事後条件、投げる例外について。Javadocには引数とか返り値とか例外のフォーマットがあるみたいだけれど、pythonってそういうのないよね。どうすればいいんだろ。

ローカル変数のスコープを最小限にしましょう
  • 俺も最近はプログラムのパッケージ化に慣れてきたし、こういう流儀はよく理解できるぜ。

ライブラリを積極的に使いましょう
  • 「すべてのプログラマは java.lang java.util java.io の内容を知っておけ」「ライブラリのコードはきみの書くコードより優れている」。耳が痛い。ついつい車輪の再発明をしちゃうんだよねー。もうすでに誰かが作っていそうな機能は、積極的にググろう。

正確な計算がしたかったらint、long、BigDecimalを使うこと
  • 9桁まではint、18桁まではlong、それ以上はBigDecimal。

他の型が使えるところで文字列を使わない
  • これはさっきの、「int enum じゃなくて enum を使いましょう」に似てる。わかる。

文字列結合には + ではなく StringBuilder を使いましょう
  • Javaの + による文字列結合は遅いんだって。だからプログラム内でなんべんも呼び出される文字列結合があれば、そこは StringBuilder を使ったほうがいいようだ。

最適化するな
  • この最適化ってのは、「もっと処理を速く」を追い求めることみたい。で、素人がそういうのやると大抵失敗するからヤメとけ、「速いプログラムより良いプログラムを書きましょう」とのこと。

命名規約を守ること
  • 俺はわりとpythonの命名規約を破ってる。とくにモジュール名。pythonのモジュール名はスネークケースで書くべきらしいんだけど俺はキャメルケースで書いている……。なんか格好いいから。けれどまあ、いちどしっかり命名規約、コーディング規約に従ってみるのも楽しめるかも。

例外処理を通常の制御フローで使うな
  • これは昔よくやったなあw 文字列の数値チェックがやり方わからないから、計算でエラーが出たらそれは数値じゃないとして扱う、とか。そういうことはヤメましょうとのこと(当然だ)。でも当時の、「知ってる技だけでなんとかした」っていうのはよかったと思う。

熟練者の特徴のひとつは、コードの再利用を高いレベルで行うことに熱心である点
  • これもわかるよ。コードを再利用しようとすると、高度な技術の使用を強いられる傾向にある気がする。すべての処理をベタで書くのがイヤで省略するために関数が登場したり、さんざん首を傾げたクラスやインスタンスも、基本的にコードの再利用を目指したものだと思うし。

例外を投げたあと、メソッドの呼び元が例外から回復することを考慮すること
  • 例外を投げたオブジェクトは元の状態へ戻るのが好ましい。そういうのをエラーアトミック性っていうんだって。そして元の状態へ戻るコードのことを回復コードというそうだ。俺がraiseという文の意味を知ったのは最近のことだから、そこまでピンとこないんだけど。

マルチスレッドに関する項目
  • ……わかんねえ!

シリアライズに関する項目
  • ……わかんねえ!



いやわかんねえで終わりかよ。まあ久しぶりのプログラミング本で楽しめた。活かして今後は、「ドキュメントをしっかり書く」「規約に従う」「コードの再利用に努める」あたりを実践してみよっかな。


| 緑色 | プログラミング | comments(0) |
| カテゴリ:プログラミング |
Joshua Block『Effective Java 第二版』 前半



プログラミング言語Javaの技術書。しかも入門編じゃなくて少々上級みたい。知り合いがもってたんで借りて読んだ。Javaとか知らんけれど、まあpythonの知識の応用で読めるんじゃない? って思って。それに読書は雑食に行いたいからね。

「言語」を扱うのに必要なのは、文法、語彙、慣習だ。それはプログラミング言語も同じである。一等高度なのがみっつめの慣習だ。これはよく理解できる。英語も、文法や語彙は簡単にわかるけれど慣習は身につけるのが難しい。プログラミング言語も、基礎的な文法はもうわかってるけれど、所詮ひとりで遊んでるものだから慣習とか書き方の流儀って点では俺のスクリプトはもうきっとめちゃくちゃなのだろう。そしてこの本はまさにその慣習を扱っているようだ。なるほどこれは上級だ。この本ではその慣習を、78項目に整理して書いてある。なんとなーくわかったものについてだけサマリと感想を書く。



コンストラクタじゃなくてstaticファクトリーメソッドを使え
  • ファクトリーメソッドってのは新しいインスタンスを作って返すメソッドのことみたい。
    @staticmethod
    def foo():
        return Klass('f', 'fo', 'foo')
    
    @staticmethod
    def bar():
        return Klass('b', 'ba', 'bar', 'barr')
    
    つまりこういうことかな? インスタンスを新しく作るときの面倒くさい初期値設定とかを省略できるようにするみたいな。作成するインスタンスごとにそれを作るファクトリーメソッドに名前をつけられるのがわかりやすくていい。

大量のコンストラクタパラメータに直面したらビルダーパターンを使え
  • Javaではメソッドのオーバーロードってものがある。pythonでは同じ名前のメソッドって作れないけど、Javaでは引数さえ違えば同じ名前のメソッドをいくつも作れて、それをメソッドのオーバーロードっていうようだ。
  • で。そのオーバーロードを利用して、コンストラクタを複数用意することができる。でもあんまりにもコンストラクタが多いときはビルダーパターンを使う。確かにこれはすげーなって思ったけど、これpythonでは名前付きオプションパラメータと引数デフォルト値があるからまったく必要なさそう。でも自分自身を return するメソッドっていうのはなるほどと思った。

インスタンス化不可のクラスを作る方法
  • アクセッサが private のコンストラクタを作り、中に
    throw new AssertionError();
    を書く。

定数を作るなかでなんべんも同じオブジェクトを作ってるならムダだからやめること
  • そういうときは定数の中身を
    static {}
    の中で埋める。

ファイナライザを避けること
  • try,finally のことだと思うんだけれど、なんでかよくわからん。なんか、ちゃんと実行される保証がないみたい。

equalsメソッドをオーバーライドするときは気をつけて
  • Javaのインスタンスにはたいていequalsメソッドがあって、それを使うことでインスタンス同士がイコールかどうかわかるらしい(
    instance.equals(instance2)
    )。クラスを継承するときはつねにこのルールが守られるようにしようねってことみたい。

equalsメソッドをオーバーライドするときはhashCodeメソッドもしないとダメ
  • ハッシュなるもの(よくわからん)が同一性の確認に1枚噛んでるってことはギリわかった(ギリ)。

すべてのサブクラスはtoStringメソッドをオーバーライドせよ
  • あー! これはアレでしょpythonでいうところ
    __repr__
    のことでしょう。わかるぜ。

うまく設計されたモジュールは、実装の詳細とAPIをハッキリ区別している
  • API部分はアクセッサを public にして、その他は全部 private とか protected にせよってことみたい。

publicのクラスではpublicフィールドじゃなくてアクセッサメソッドを使う
  • public float x
    じゃなくて
    public float getX()
    にしとけってことのようだけどよくわからん。まあともかくクラスの中の大事なところはしっかりと隠蔽すべきってことはわかったと思う。

クラスの可変性を最小限にせよ
  • オブジェクトの状態を変更するメソッドを提供しない、拡張不可にする、全フィールドを final にする、全フィールドを private にする、など。

継承よりコンポジションを選ぶこと
  • まったくピンとこない。こういうのはピンと来ないときに読んでもダメ。

インタフェースは型を定義するのに使う
  • 間違っても定数の詰め合わせを書くな。それをやりたいなら定数ユーティリティクラスを作り、上述のprivateコンストラクタでインスタンス化を防ぐこと。pythonにインタフェースも型定義もないため、俺が興味をもつべきなのか微妙。

タグ付クラスを使うな
  • パラメータの値によって、そのインスタンスの役割がまったく変わっちゃうようなもののことをいうようだ。そういうときは継承を使って、別物のクラスにしなさいってこと。わかるぜ、初心者のころよくやっちゃう奴だよね。俺は最近脱却できてるけれど、クラスとかがよくわかってないと、ついつい継承とか使うのを面倒がって、全部ひとつのクラスに収めようとしちゃうんだよね。

ジェネリックまわり
  • まったくわからんジェネリックてなんぞ。

int定数ではなくenumを使うこと
  • APPLE = 0
    ORANGE = 1
    
    みたいなのは int enum パターンといって、クソ実装らしい。クソw これ俺よくやるわ! そういうのは
    enum FRUIT { APPLE, ORANGE }
    って書くべき。このenumってやつ、便利そう。pythonにもあんのかなあ?



わからんところは躊躇なくスキップしてるので、ザクザク進んでる。300ページ級の大判本だから、この調子でサクザクいこう。


| 緑色 | プログラミング | comments(0) |
| カテゴリ:プログラミング |
Python InputGhost1.1 コピペ作業自動化ツール



みろりHPのファイル置き場 - InputGhost1.1

知り合いが「膨大なテキストを一行ずつコピペする」というスゲーしち面倒な作業をしてんだよ。ペースト先のアプリケーションの仕様で、まとめて全部貼り付けることができないんだ。貼り付ける先がテキストエリアじゃなくて改行不可のテキストボックスだから。だからそいつは、
  • 一行コピーする。
  • 対象アプリ開いてテキストボックスにペーストする。
  • またテキストへ戻って、一行コピーする。
  • 対象アプリ開いて次のテキストボックスにペーストする。
  • 以下ずっと繰り返し。
なんつーことをしてた。ねーよ。と思った。だからその作業の自動化ツールを作ってやったわけさ。



一行ずつコピペしたいテキストをこんなふうに用意しとく。


これ設定ファイル。一番下に、自動化する行動の順番を書く。今回は「改行して、一行コピペして、改行する」ってのをワンループとする。


実行すると、指定したアプリケーションが前面に出てきて、設定した行動を自動で行ってくれる。今回の場合、メモ帳が出てきて、改行コピペ改行が自動で行われる。


今回はユーザを上述の知り合いに限定してるから汎用的なツールではない。その知り合いに必要な入力が「改行、コピペ、『0』を入力」だけだったから、それ以外の行動は登録できない。あと、元テキストから空白を除いたり、空き行は無視するようになってる。だけどプログラムを使って入力を再現できるノウハウが手に入ってよかったぜ。ネトゲのBOTとかもこういうのが元になってたのかな。すくなくとも連打ゲー用のチートツールくらいなら、もう作れそうだ。



ソースは冒頭の公開セットに同梱してあるんで、今回新出だったtipsを書いとく。

対象のアプリケーションを引っ張り出すには?
subprocess.Popen('call notepad.exe', shell=True)

こう。subprocessはビルトインモジュールなので簡単だね。んー、もしメモ帳が複数起動していて、そのうちのひとつだけを対象にしたい場合はこれじゃあダメだよね。複数のウィンドウのうちひとつを選択して引っ張り出さないといけなさそうだが……うん、そのへんは機会があったらトライかな。こないことを祈る。メチャ面倒そうな予感がする。

キー操作の代行
user32 = ctypes.windll.user32
user32.keybd_event(ここにキーコード, 0, 0, 0)   # 押す
user32.keybd_event(ここにキーコード, 0, 0x2, 0) # 離す

今回の主役。えーとこれはちょっと調べた感じ、「ビルトインモジュールctypesが、windllっていうWindowsのapiを呼び出して、その中のuser32っていうapiを使わせてもらう」って流れみたいだ。キーコードは十進数で表記(参考サイト:仮想キーコード一覧)。

コピペを代行
pyperclip.copy('対象テキスト')
time.sleep(0.1)
user32.keybd_event(cls._KEYCODE_CTRL, 0, 0, 0)
user32.keybd_event(cls._KEYCODE_V, 0, 0, 0)
user32.keybd_event(cls._KEYCODE_CTRL, 0, 0x2, 0)
user32.keybd_event(cls._KEYCODE_V, 0, 0x2, 0)

今回コピペしたいテキストはプログラム側がもってるので、動作としては、「テキストをクリップボードに貼り付けて」「Ctrl+Vを代行させてペーストする」って方式をとってみた。クリップボード操作のpyperclipはビルトインではないからpipでインストールしないとダメ。クリップボード操作の直後の「0.1秒待機」が意外に超重要で、これを挟まないとコピペのとき想定通りの動きをしないことがある。クリップボードへの貼り付けが終わる前にCtrl+Vを押しちゃったりするのが原因かなあ? たぶん。


| 緑色 | プログラミング | comments(0) |
| カテゴリ:プログラミング |
Python クラスって何なの



またクラスの話かよ。思えば、pythonを始めて、ちょこちょこ手遊び2年が経過した。記念に2年前の俺へクラスの解説を書いてみる。あの頃はとりあえずドットインストールのpython講座動画で入門したのだよな。振り返ってみようとドットインストールサイトに行ってみたら、python講座が有料になっていた。残念。



さてきみは、足し算の書き方や関数の書き方、リストやディクショナリは理解できたのに、このクラスとかいう謎の何かへ単元が移った途端突然理解不能になり困惑しているところだと思う。ぶっちゃけ直近スクリプト書くにあたっては必要ないと思うし、別にいっかーと思いつつも、ネットに載ってるサンプルスクリプトではやたらめったらクラスが使われているのでますます困惑を極めていることだろう。そのふたつの困惑をとりあえず解決してやるぜ。

クラスってのはディクショナリみたいなもんだから安心しろ
少なくともきみの2年後である俺は、まあ基本的にはディクショナリみたいなもんだと思いながら使っている。下の例を見てみれば、ディクショナリを定義して値を取り出すのも、クラスを定義して値を取り出すのも似たようなものだとわかるだろう?
# ディクショナリ
dic = {
    'a': 'A',
    'b': 'B',
    'c': 'C',
}
print(dic['a'])  # 'A'が取り出せる。

# クラス
class Klass:
    a = 'A'
    b = 'B'
    c = 'C'
print(Klass.a)  # 'A'が取り出せる。
うん? 似てるっぽいけれど、ディクショナリではキーを文字列('a')で指定しているのに対し、クラスのほうは変数(a)で指定しているから違うんじゃないかって? ならばこう書いてやろう。
Klass_dic = Klass.__dict__
print(Klass_dic['a'])  # 'A'が取り出せる。
__dict__ はクラスの中身をディクショナリ形式にしてくれる。これでクラスっていう何かがぶっちゃけディクショナリみたいなもんだということがワカると思う。

サンプルスクリプトでやたらクラスが使われてんのはそれが便利だからだ
きみの2年後である俺がスクリプトにクラスを使うのは、パッケージ化がしたいときだ。今のきみでも、スクリプトを適当に書くより関数にまとめたほうが良いカモっていう感覚はなんとなくもっているだろう? 使い回しがきくようになるし、
変数のスコープが深くなるから他のトコで使う変数と名前衝突するのを防げるもんね。あとまとまってる感じがなんか嬉しいだろ? クラスを使うのはそれと同じ理由だ。きみがテキトーな小さな計算スクリプトをさっくりまとめたくて関数を作るのと同じテンションで、俺はすこし大規模な計算や複数の関数をさっくりまとめたくてクラスを作るのだ。クラスを使うと、{クラス名}.{関数名}って感じで名指しで内側の関数を実行できたり、{クラス名}.{変数名}って感じで値を取り出せたりできる。後者は上述したようディクショナリでも簡単にできるけれど、ディクショナリの中に関数を定義するのはちょっと面倒だし見栄えも気になってくる。
# ディクショナリの中で関数定義する場合
dic = {
    'a': 'A',
    'd': lambda s: s + dic['a']
}
print(dic['d']('a'))  # 'aA'って表示される

# クラスの中で関数定義する場合
class Klass:
    a = 'A'
    def d(s):
        return s + Klass.a
print(Klass.d('a'))  # 'aA'って表示される
後者のほうが書きやすく見やすい感じするだろう。きみの書いているスクリプトが全部ひとつのディクショナリにつっこめる仕組みがクラスってことだ。



幕間。
きみはこれから解説記事で「クラスが設計図でインスタンスがロボットやクルマ」系の例え話をよく目にすることになると思うが、それはきみの役には立たないので見なくてよい。きみはヘンに例え話をされるより、実際の言語仕様で説明されたほうが頭に入るヤツだ。



これだけ執拗に言われればクラスがディクショナリみたいなもんだということは十分わかったと思うけれど、インスタンスって何なのっていう疑問も連なってくるだろう。

インスタンスもディクショナリだと思ってイイよ
いやお前なんでもディクショナリかよときみはツッコミを入れてくるかと思うが、2年後の俺がそういう理解でクラス・インスタンスを実際不自由なく書けるのだから大丈夫だ。てかpythonスクリプトなんてすべてがディクショナリみたいなもんなのだ。数ヶ月後きみは試行錯誤する中でその片鱗に気がつくことになる(Python 変数もどき自動作成)し、さらにその半年後、オライリーという教典を読んでリファレンスという概念を知りその理解が正しかったことを知ることになる(Mark Lutz『初めてのPython』)。a,b,cというクラス変数をもったクラスと、a,b,cというキーをもったディクショナリがおんなじようなもんであるように、a,b,cというクラス変数をもったインスタンスもおんなじようなもんだ。
class Klass:
    a = 'A'
    b = 'B'
    c = 'C'

klass = Klass()
print(klass.a)  # 'A'って表示される
少なくともこの例だとインスタンスがインスタンスである意味がゼロだが、「クラスって何なの」と言ってる段階ではここまででよいと確信してる。その段階でインスタンスのステキな使い方をいっぺんに詰め込まれるとホント厳しくなる。とにかくディクショナリだ。ディクショナリでイイ。


| 緑色 | プログラミング | comments(0) |
| カテゴリ:プログラミング |
Python 波ダッシュ問題レポート



「1日1python」の時間です(ただし隔週放送)。みろりhpの記事をpythonで読み込んでいたら、スゲーイヤな問題に咬まれたのでそれについて書く。
なお今回のレポート内容はこのブログだとなんか手に余る感じ。ブログの文字コードがEUC-JPだからだ。今回の内容はUTF-8で書きたいところ。jugemブログは早急に最先端であるUTF-8を導入して頂きたいものだ。これについては随時補足しとく。



マジびっくりなことに、
は違う文字だ。前者は「波ダッシュ(WAVE DASH)」といって、後者が「全角チルダ(FULLWIDTH TILDE)」という。
pythonで下記スクリプトを実行してみればそれがわかる。
wavedash = '〜'        # 前者。波ダッシュ。
fullwidthtilde = '〜'  # 後者。全角チルダ。
print(wavedash == fullwidthtilde)  # Falseになりやがる。ビックリだな。
UTF-8にはどっちの文字も含まれてるんだが、EUC-JPとかSJISには全角チルダのほうが含まれていないらしい。だからこのレポートはUTF-8で書きたかったわけ。
【補足】一応上のスクリプトではエスケープ? がかかってちゃんと二種類表現されているのでコピペしてpython実行してもFalseは得られるみたい。

見分け方はこう。
# b'\xe3\x80\x9c' って出れば波ダッシュ。
print(str(wavedash.encode('utf8')))

# b'\xef\xbd\x9e' って出れば全角チルダ。
print(str(fullwidthtilde.encode('utf8')))

上の見分け方を使って判別関数を作っといたぜ。
def is_WAVEDASH(char):
    return char.encode('utf8') == b'\xe3\x80\x9c'

print(is_WAVEDASH(wavedash))        # Trueになる。
print(is_WAVEDASH(fullwidthtilde))  # Falseになる。


def is_FULLWIDTHTILDE(char):
    return char.encode('utf8') == b'\xef\xbd\x9e'

print(is_FULLWIDTHTILDE(wavedash))        # Falseになる。
print(is_FULLWIDTHTILDE(fullwidthtilde))  # Trueになる。

変換する関数も作ったぜ。てか今回の問題で俺がいちばん必要としたのがこれ。波ダッシュと全角チルダが混在するデータで処理をしようとするとどっかでオカシクなる。文字化けしたり、見た目には同じ文字なのに「違う」って言われたり(冒頭のヤツ)ね。そういう恐れのある文字列は全部以下の関数を通しちまおうぜ。
# 波ダッシュを全角チルダに変換する。reverseにTrue渡せば逆になるよ。
def convert_WAVEDASH_to_FULLWIDTHTILDE(string, reverse=False):
    wavedash = (b'\xe3\x80\x9c').decode('utf-8')
    fullwidthtilde = (b'\xef\xbd\x9e').decode('utf-8')
    if not reverse:
        return string.replace(wavedash, fullwidthtilde)
    else:
        return string.replace(fullwidthtilde, wavedash)



この問題に出会ったのは、みろりhpのデータをxmlエクスポートしたときだ。ナゼかこのブログはEUC-JP(波ダッシュしかないほう)で書かれているのに、出力するとUTF-8(どっちもあるほう)になり、しかももともと含まれていた波ダッシュがすべて全角チルダに置き換わる謎仕様だったんだ。それを処理しようとしたらおかしなことになった。そこでこの調査が必要になったわけ。


| 緑色 | プログラミング | comments(0) |
| カテゴリ:プログラミング |
Python raiseノート



「1日1python」の時間です(ただし隔週放送)。ようやく raise を理解したのでまとめるぜ。raise文との出会いはオライリーだった。raiseとは例外を飛ばすものとの事だったけど、そもそも例外って何かわかんねーしassertっていう何か似てるのもあるしで全く理解できなかったのだよな。でも先日プログラマさんの書いたコラムとか読んでて、throwっていう他の言語の構文を知ったことでとうとう理解できた。



例外って何?
プログラム実行中に起こる、想定外の現象のこと。ほんでプログラム実行中に起こる想定外っつったらエラーのことだから、まあエラーのことだ。以下のように書き下したらよく分かったよ。
  • 例外 -> 想定外 -> プログラムにおける想定外とは? -> エラー
まあその、ドシロートっていうのはこういう用語ひとつでも引っかかっちゃうものなのですよ。pyてょんちゃんが「Exception!!」って叫んだら、「作者のお前が想定してなかったことが起きたぜ!」って意味になるわけだね。

raiseって何?
raiseは例外を生み出してひとつ上の階層に投げる呪文だ。いやエラーなんて放っておいても起こるんだから…なんでわざわざ自分で作るわけ!? ってのがオライリー読んだときは疑問で理解ができなかったんだけど、python的にはエラーじゃないけど俺的にはエラーになって欲しいって状況を想定したら理解できた。
def foo(flag):
    if not flag:
        raise Exception('Falseが来たから俺の都合で終了するよ。')
    return flag

foo(False)
実行すると以下のようになる。
Traceback (most recent call last):
  File "/Users/.../py.py", line 11, in <module>
    foo(False)
  File "/Users/.../py.py", line 8, in foo
    raise Exception('Falseが来たから俺の都合で終了するよ。')
Exception: Falseが来たから俺の都合で終了するよ。
いやー、Exceptionてのは俺の手中にはない範疇のモノだと思っていたから、ちょっと感動ですね。

感動ついでに自作のExceptionを作ってみる
こんなの書いたらできたわ。
class MidoriException(Exception):
    def __init__(self, error_message='緑エラーだよ。'):
        self.error_message = error_message
    def __str__(self):
        return self.error_message

raise MidoriException()
実行すると以下。
Traceback (most recent call last):
  File "/Users/.../py.py", line 11, in <module>
    raise MidoriException()
__main__.MidoriException: 緑エラーだよ。
すげえMidoriExceptionなんつーのが表示されちゃったぜ。なにこれ面白いんだけど。python的にはエラーじゃないんだけど俺の都合でエラーにする箇所については俺オリジナルのExceptionクラスを使えば、結果を見たときわかりやすいかもしんない。

「上の階層に投げる」てのはどゆこと?
階層ってのは「関数の呼出元」と「呼ばれた関数」の層のことだ。「呼ばれた関数」側でraiseすると、生み出したExceptionは「呼出元」へ浮かび上がる。俺が浮かび上げるから raise なんだな。他の言語では throw っていう文が使われてるらしいけど、そっちでは「浮かび上げる」じゃなくて「投げる」っていう感覚なんだね。 ちょっと書いてみる。グローバル -> foo -> bar -> baz の順番で階層的に関数を呼び出して、第四階層であるbazでMidoriExceptionをraiseしてみるぜ。
# 第四階層
def baz():
    # 上の階層barにExceptionを能動的に浮かび上げ(raise)る。
    raise MidoriException('第四階層でエラーが発生しました。')

# 第三階層
def bar():
    # 浮かび上がってきたExceptionは勝手に上の階層fooに浮かび上がる。
    baz()

# 第二階層
def foo():
    # 同じく、勝手に上の階層に浮かび上がる。
    bar()

foo()
なお、fooの中でtryを書いてbarから浮かんできたExceptionを捕まえて、自分で改めてraiseしても同じ結果になるみたい。
def foo2():
    try:
        # ここで捕まえる。
        bar()
    except Exception as e:
        # 捕まえたのを浮かび上げる。
        raise e
てことはさ、普段エラーが起こっているときはその場で勝手にraiseが起きていると思ってよさげ。あーなるほどね!



「例外」が理解できなかったことについては上述した。いっぽう、raiseが理解できなかった理由は、raiseという言葉が指すものを初見で勘違いしたからだ。つまり、実際は「例外を生んで上の階層に浮かび上げる」ことを指すんだが、俺は「例外を生むこと」自体をraiseだと思っちゃったんだよ。こう、俺にはよくわからないpythonの内部から、Exceptionというものを持ち上げるっていうイメージ。それは間違ってた。「Exceptionというものを持ち上げる」のは
Exception()
だ。raiseをつけることで初めてそれが上層へ飛び上がる。で、まあそういう勘違いをしてたんだけど、他の言語のthrowっていう言葉だとそういう間違いをしようがないよな。しかも他の言語だと
throw new Exception();
っていう書き方をしてたんだよ。「新しくExceptionてクラスのインスタンス作ってそれを投げるのね」ってスグわかった。で、pythonだとそういう構文はあるのかな? って考えて、ああ、raiseってこのことだったのか! と思い至れたってわけ。


| 緑色 | プログラミング | comments(0) |
| カテゴリ:プログラミング |
Python pygameをMacで使うのはヤメとこうぜ



いやな? 俺が心血を注いで組んだpygameアプリがMacでは動かなかったのだよ。特に咬まれた以下二点について書く。
  • mp3を読み込んでくれねー( pygame.error: Unrecognized music format )
  • キーボード入力を受け付けてくれねー



mp3を読み込んでくれねー
oggに変換しようぜ。俺にはよくわかんないんだけれど、なんでも SDL_mixer(?) だの SMPEG(?) だのをbrewすれば使えるよ? みたいな情報があったんだけど、うまくいかなかったよ。だからoggに変換しようぜ。ひとまずMacフリーソフトで探したところ Video Converter Master Lite というアプリがよい感じだった。ちょっとmp3->oggのボタンが探しづらかったけれどちゃんとあるぜ。

キーボード入力を受け付けてくれねー
諦めようぜ。とりあえず状況から説明するとだな……、Mac上でpygameウィンドウのアプリを起動すると、そのウィンドウアプリが独立した新しいアプリケーションとして認められないみたいなんだよ。すると、そのpygameアプリが独立してキーボード入力を受け取ってくれない。Sublime Textで起動すればSublime上でキー入力したことになっちゃうし、ターミナルで起動すればターミナルでキー入力したことになっちゃう。ただしマウスイベントは受け付けてくれる。これはわからん諦めようぜ。Windows上では依然として動くので、それでお茶を濁す感じでひとつ。ゆえに「DialogFrame」らへんの自作アプリはWindows専用ってことになる。



本題は以上なんだけど。「これまでのゲームってMacでも動くのかな調査」にともなって自作ゲームであるところの「一石二鳥」をちょいと普通にプレイして自己レビューしてみた。なお「一石二鳥」は上述したような厄介な問題はなしできちんと動いたぜ。細かいことを言えばWindowsとMacのデリミタ違いを直す必要があったケド。
  • クリアはできた。
    • <SYSTEM>クリアおめでとうございます。あなたのスコアは 9670 でした。
  • ちとスタートが切りづらいなコレは。弾薬とお金は最初からもっとあっていい。
  • ベースキャンプでの弾薬補給は1個じゃなくて5個くらいでもいいんじゃね。商人にたどり着くまでは補給と石拾いを繰り返すことになるが、それはテンポが悪い。
  • 害獣との戦闘後は石ドロップがあってもいいと思う。ご褒美ナシだと戦闘が面倒なだけだ。
  • コレクションブックの点数最大値とか、トロフィーの取得条件は可視でもいいと思う。

典型的な「素人の作るゲームは難易度が高くなりがち説」に引っかかってしまったようだな。そう考えてみるとマゾゲーってのはすげーよな。難易度を上げつつ楽しめるようにデザインされてんだから。ギターといい歌といい絵といいゲームといい、自分で一度やってみると先行物のスゴさがわかっちまうよなあ。


| 緑色 | プログラミング | comments(0) |
     12
3456789
10111213141516
17181920212223
24252627282930
<< June 2018 >>
+ みろりHP内検索
+ 閲覧記事
+ 過去記事アーカイブ
+ カテゴリ
+ 年月選択
  • 2018年 06月 (4)
  • 2018年 05月 (4)
  • 2018年 04月 (7)
  • 2018年 03月 (6)
  • 2018年 02月 (6)
  • 2018年 01月 (8)
  • 2017年 12月 (9)
  • 2017年 11月 (9)
  • 2017年 10月 (4)
  • 2017年 09月 (6)
  • 2017年 08月 (6)
  • 2017年 07月 (8)
  • 2017年 06月 (4)
  • 2017年 05月 (7)
  • 2017年 04月 (8)
  • 2017年 03月 (7)
  • 2017年 02月 (10)
  • 2017年 01月 (6)
  • 2016年 12月 (8)
  • 2016年 11月 (8)
  • 2016年 10月 (5)
  • 2016年 09月 (5)
  • 2016年 08月 (7)
  • 2016年 07月 (9)
  • 2016年 06月 (6)
  • 2016年 05月 (8)
  • 2016年 04月 (10)
  • 2016年 03月 (10)
  • 2016年 02月 (8)
  • 2016年 01月 (9)
  • 2015年 12月 (9)
  • 2015年 11月 (6)
  • 2015年 10月 (5)
  • 2015年 09月 (4)
  • 2015年 08月 (8)
  • 2015年 07月 (5)
  • 2015年 06月 (3)
  • 2015年 05月 (7)
  • 2015年 04月 (8)
  • 2015年 03月 (12)
  • 2015年 02月 (8)
  • 2015年 01月 (4)
  • 2014年 12月 (5)
  • 2014年 11月 (5)
  • 2014年 10月 (7)
  • 2014年 09月 (4)
  • 2014年 08月 (7)
  • 2014年 07月 (6)
  • 2014年 06月 (4)
  • 2014年 05月 (12)
  • 2014年 04月 (9)
  • 2014年 03月 (6)
  • 2014年 02月 (6)
  • 2014年 01月 (8)
  • 2013年 12月 (7)
  • 2013年 11月 (10)
  • 2013年 10月 (10)
  • 2013年 09月 (9)
  • 2013年 08月 (11)
  • 2013年 07月 (10)
  • 2013年 06月 (9)
  • 2013年 05月 (15)
  • 2013年 04月 (11)
  • 2013年 03月 (5)
  • 2013年 02月 (7)
  • 2013年 01月 (6)
  • 2012年 12月 (9)
  • 2012年 11月 (10)
  • 2012年 10月 (10)
  • 2012年 09月 (4)
  • 2012年 08月 (2)
  • 2012年 07月 (7)
  • 2012年 06月 (13)
  • 2012年 05月 (13)
  • 2012年 04月 (15)
  • 2012年 03月 (4)
  • 2012年 02月 (12)
  • 2012年 01月 (9)
  • 2011年 12月 (5)
  • 2011年 11月 (13)
  • 2011年 10月 (2)
  • 2011年 09月 (2)
  • 2011年 08月 (1)
  • 2011年 06月 (1)
  • 2011年 05月 (4)
  • 2011年 04月 (10)
  • 2011年 03月 (8)
  • 2011年 02月 (11)
  • 2011年 01月 (14)
  • 2010年 12月 (14)
  • 2010年 11月 (17)
  • 2010年 10月 (17)
  • 2010年 09月 (19)
  • 2010年 08月 (22)
  • 2010年 07月 (18)
  • 2010年 06月 (16)
  • 2010年 05月 (19)
  • 2010年 04月 (15)
  • 2010年 03月 (22)
  • 2010年 02月 (18)
  • 2010年 01月 (18)
  • 2009年 06月 (2)
  • 2009年 04月 (1)
  • 2007年 12月 (10)
  • 2007年 11月 (7)
  • 2007年 10月 (9)
  • 2007年 09月 (4)
  • 2007年 07月 (5)
  • 2007年 06月 (11)
  • 2007年 05月 (6)
  • 2007年 04月 (4)
  • 2006年 01月 (21)
  • + ブックマーク
    + 最近のコメント
    + アクセスカウンター
    全体(since 2010.02.03.)
    今日… 昨日…