再帰的に関数を実行するときに返り値が取得できない

はまったこと

数値を入力させたいなど,正しい値が得られるまで同じ処理を繰り返したいことがある.
具体的には,例えばこんな感じ.

def get_number():
    try:
        num = raw_input('enter number: ')
    except ValueError:
        get_number()
    else:
        return num

get_number()
type(get_number)

try節でValueErrorが発生したらexcept節で再帰的にget_number()を呼び出す.
いつかValueErrorが発生しない値が得られたら,else節でnumをreturnして終了.
そう考えたくなる.

ところが,

None
<type 'NoneType'>

実行結果はこうなる.
値が返っていない.

正しい書き方

def get_number():
    try:
        num = raw_input('enter number: ')
    except ValueError:
        return get_number()
    else:
        return num

get_number()
type(get_number)

except節で自身を呼び出すときにもreturnしてやると正しく実行できる.
stackoverflow.com

再帰的に実行しているときにreturnで値が返っても,最上位のtry-except文においてはelse節が実行されていないからこうなるらしい.

AppleScriptで動画の長さを取得する

ファイルの情報を取得するのにはFinderにファイルを渡して問い合わせるのが一般的だと思う.
2/3 AppleScriptの構造を上手に調べる [Mac OSの使い方] All About

choose file
set theFile to result

tell application "Finder"
properties of item theFile
end tell

こんな感じにすると一通りの情報が出てくるが,動画に関しては長さやビットレート,解像度といった情報は出てこない.
Finderには表示されるわけだが,これは何らかの方法でFinder以外の部分が計算しているのだろう.

とりあえず方法としては,QuickTimeでファイルを開いてdocumentの要素を取り出すというものがある.

tell application "QuickTime Player"
	set tDoc to open theFile
	tell tDoc
		duration of tDoc
	end tell
end tell

一旦開くことになるのがいまいちかもしれない方法.

ちなみに何の値が取り出せるのかは,Script Editorで「ファイル」→「用語説明」→「QuickTime Player」と辿るとわかる.
AppleScriptでQuickTime Playerを自動化する | QuickTime

gb4e.styの例文で例文番号を操作する(途中から始める)方法

\begin{exe}
\setcounter{xnumi}{7}
 \ex \begin{xlist}
      \ex ひとつめの例文です
      \ex ほげほげ
     \end{xlist}
 \ex \begin{xlist}
     \ex みっつめの例文です
     \end{xlist}
 \ex \begin{xlist}
      \ex ふがふが
     \end{xlist}
\end{exe}

こんな感じで,exe環境の中の,番号を変更したい例文の前で,

\setcounter{xnumi}{7}

などとやると,次の例文は指定した番号の次から開始される.

homebrew caskで任意のバージョンのアプリケーションをインストールする・ダウングレードする

起こった問題

いまだにOSX El Capitanを使っているのだが(そしてそれほど「いまだに」とは思っていなかったのだが),
先日CotEditorをなんとなくbrew upgradeしたら,OS10.12.xx以上じゃないと使えません的なメッセージが出た(たぶん3.6.7; どのバージョンからそうなのかは不明).
いよいよこういうロバストそうなプログラムが動作しなくなると環境が古くなってきた感じがする.

ダウングレードしたい

これがcaskでないプログラムだったら,

brew switch hogehoge ver

でいいらしいのだが,
Homebrewで旧バージョンをインストールする方法(brew versionsはもう使えない) - Qiita
caskで同様にやろうとするとうまくいかない.

brew info

でも最新のバージョンしか出てこないし,switchサブコマンドも受け付けない(caskがすでにサブコマンド?)
たとえば

find / -name coteditor* 2>/dev/null

などとやれば,これまでupgradeしてきた.dmgファイルが残っているのは確認できるが,
これを直接はhomebrewが利用できるようにはなっていないということか.

やったこと

見つかった方法はふたつ.
Downgrade an application installed with Brew Cask – Joeri Verdeyen

  1. Homebrew caskのGitHubプロジェクトページに行く
  2. "Find file"(もしくはキーボードで "t")でパスのベースネームにカーソルが行くので,formula名を入力する
  3. *.rbファイル(caskファイル)を選択し,"history"をクリック
  4. コミット(変更履歴)一覧が出るので,希望のバージョンをクリック
  5. 当該バージョンのページの"raw"をクリックしてスクリプト自体を表示し,URLをコピー
  6. brew cask uninstall formula -> brew cask install URL

もう一つの方法は,ローカルの*.rbファイルを直接いじってバージョンを指定してしまうというもの.
https://devforgalaxy.github.io/en/2016/11/05/use-homebrew-cask-to-downgrad-or-install-en.html
しかしこの方法では,バージョンの数字の他にsha256というハッシュ値を指定する必要があるが,
これは結局GitHubの*.rbファイルからとってくるくらいしか方法がわからなかったので,それならその*.rbファイルから直接brew installしたほうが早いという気がする.
編集したらuninstallしてinstallする.

2019/1/18追記

うっかりbrew cask updateしたらまたCotEditorを最新にしてしまったので,同様の方法を試みたが,
なぜか下記のようなエラーが出る.

curl: (22) The requested URL returned error: 416
Error: Cask 'coteditor' is unavailable: Failed to download https://raw.githubusercontent.com/Homebrew/homebrew-cask/2e9a7b52a2c4b60d98e272de7235cd75cf960586/Casks/coteditor.rb. Did you mean “coteditor”?

上のfindの方法で.dmgファイルは見つけることはできるし,brew caskの管理下に置いておくとまた同じことが起こるので,
旧バージョンの.dmgファイルから直接インストールしてしまうことにした.

一時ファイルの名前が既存のファイルと同じにならないようにする方法

十分長いランダム文字列でファイル名を決めておけばそう重複することはないだろうが,天文学的確率で起こる問題を回避するために.
所定のディレクトリに作ろうとする名前のディレクトリがある限り,新規にランダム文字列を生成する.

2018/12/3追記:最初にディレクトリ名が重複しない場合にディレクトリを生成する処理が抜けていた

import os
import random
import string

temp_name = u''.join(random.choice(string.ascii_letters + string.digits)
                         for c in range(30))
    temp_dir = os.path.join(os.environ['HOME'], u'Desktop', temp_name)

while os.path.exists(temp_dir):
    temp_name = u''.join(random.choice(string.ascii_letters + string.digits)
                         for c in range(30))
    temp_dir = os.path.join(os.environ['HOME'], u'Desktop', temp_name)

os.mkdir(temp_dir)

try-exceptで複数の例外を定義する方法

やりたいこと

いくつか例外として処理したい条件があって,それぞれ別の挙動を設定しておきたい.
疑似コードで書くと以下のような状態.

try:
    procedure1
except Exc1:
    behavior1
except Exc2:
    behavior2
finally:
    procedure2

やったこと

条件ごとにExceptionに名前をつける.
使っていない例外の変数をいちいちNoneにしてるのが間抜けだが一応動く.

try:
    if condition1:
        Exc1 = Exception
        raise Exc1(u'condition1 is satisfied.')
    if condition2:
        Exc1 = None
        Exc2 = Exception
        raise Exp2(u'condition2 is satisfied.')
    else:
        Exc1 = None
        Exc2 = None 
        procedure
except Exc1:
    behavior1
except Exc2:
    behavior2
finally:
    procedure2

正解らしいもの

やり方がないわけないと思ったが,どうもExceptionのclassを定義してやるのが正解らしい.
参考:
except節で複数の例外を捕捉する - Misc Notes

class Exc1(Exception):
    message = u'description of Exc1'
class Exc2(Exception):
    message = u'description of Exc2'

try:
    if condition1:
        raise Exc1
    if condition2:
        raise Exc2
    procedure1
except Exc1:
    behavior1
except Exc2:
    behavior2
finally:
    procedure2

リスト内包表記でできないかもしれないこと

リスト内包表記

リスト内包表記とは,(定義はわからないが)forループの結果をそのままリストにする書き方であり,ちょっとした応用的なpython独特の文法らしい.
内包表記を使わずにリストを定義→forループ→リストにappend,とやるより速度がだいぶ速いらしい.
基本的な書き方は,

num_list = [i for i in range(3, 10, 2)] # -> [3, 5, 7, 9]

内包表記にif条件を追加する場合,if文は末尾につける.

num_list = [i for i in range(3, 10, 2) if i % 3 == 0] # -> [3, 9]

さらにelseをつける場合は末尾ではなく,条件式の順序が入れ替わって前に出る.

num_list = [i if i % 3 == 0 else i * 2 for i in range(3, 10, 2)] # -> [3, 10, 14, 9]

elifは使えず,代わりにelseの後にif文を続ける.

num_list = [i if i % 3 == 0 else i * 2 if c % 5 == 0 for i in range(3, 10, 2)] # -> [3, 10, 9]

詳しい説明は数多のサイトがあるので省略.

できなかったこと

冒頭にpass文を置いて特定の条件の場合を飛ばすこと.

num_list = [pass if i % 3 == 0 else i * 2 for i in range(3, 10, 2)]
# -> [10, 14] となるはずが
# -> syntax error