パスワードを忘れた? アカウント作成
246733 journal

okkyの日記: Pythonを使ってみる→早くも挫折の予感が… 23

日記 by okky

今日の 10:00ぐらいから「初めての Python」を始めたのだが…はやくも挫折の予感。

関数プロトタイプ宣言のやり方が判らない。というかもしかして、プロトタイプ宣言できないのか??

#! /usr/bin/python
 
def a(x):
    if ( x < 0 ):  return 0
    return b( x ) + x
 
def b(y):
    if ( y < 0 ):  return 0
    return a( y ) + y
 
print "result = %d\n", ( a( 4 ))

はちゃんと動くのに、

#! /usr/bin/python
 
print "result = %d\n", ( a( 4 ))
 
def a(x):
    if ( x < 0 ):  return 0
    return b( x ) + x
 
def b(y):
    if ( y < 0 ):  return 0
    return a( y ) + y

が a() という関数が無いというエラーになる。上の例で相互参照が解決しているんだから動きそうなもんなんだが…

しかも、関数宣言と呼び出し順序に拘束があるなんて、いまどきシェルスクリプトでもあるまいに…プロトタイプ宣言とか、なんか回避方法がありそうな気がするのだが…

いや、多分上が動いて下が動かないなら、

#! /usr/bin/python
 
def main():
    print "result = %d\n", ( a( 4 ))
 
def a(x):
    if ( x < 0 ):  return 0
    return b( x ) + x
 
def b(y):
    if ( y < 0 ):  return 0
    return a( y ) + y
 
#---------------------
main()

とかすりゃいいんだろうけど…

この議論は、okky (2487)によって ログインユーザだけとして作成されたが、今となっては 新たにコメントを付けることはできません。
  • それより#! /usr/bin/perlが気になります。

    --
    署名スパムがウザい?アカウント作って非表示に設定すればスッキリさ。
    • ひゃああああ、
      そこはもちろん、#! /usr/bin/python でございますよ。
      オリジナルが perl だったってだけで。

      しかし。こんなに根性が違うとは思わなかった。python は「初期化時の型」を覚えていて、自動型変換もしてくれないのね…。配列に代入したら自動的に配列に拡張してくれるわけでもない…(そんな配列しらん とか index out of range とか何度言われた事か…いや、まだまだ言われそうだ)。

      うぅ、まるでCのようなのに、生半可にいろいろ使える分さらに悩ましい…

      --
      fjの教祖様
      親コメント
      • > python は「初期化時の型」を覚えていて、自動型変換もしてくれないのね…。
        Pythonの世界では型のチェックだの変換だのはプログラマの責任でどうぞ、ということになってます。

        > 配列に代入したら自動的に配列に拡張してくれるわけでもない
        「配列」が何を指しているのか判りませんが、ひょっとして文字列を「文字の配列」だと思ってます?Pythonにおいて文字列は文字の配列ではありませんよ。

        親コメント
        • 文字列は文字の配列ではありませんよ。

          いえ、そうじゃなく。

          Perlでは突如として

          $a[12345] = 0.27;

          とか書けるんですよ。こう書いた瞬間に配列「a」が宣言され、少なくともaの範囲としてa[12345:12345]が確保される(多分内部的にはリンクリストになっているんでしょうが)。

          疎配列(構成要素のほとんどが None で埋められているような配列)の場合、「値を入れなくちゃいけない所だけ代入すれば、あとは触らなくても勝手に配列が大きくなる」という意味でいろいろ便利なんで、この辺りを多用してまして…。

          --
          fjの教祖様
          親コメント
          • by tslashn (37583) on 2010年08月16日 19時24分 (#1810317)

            python sparse arrayでぐぐれば、pythonでは面倒そうなことがわかりますね。
            そもそも、なぜpython?

            親コメント
            • - そろそろ python でも勉強しないと、RHELのスクリプトが判らない規模になってきたし…

              - Googleも Python 使ってるって言うよな。なら疎配列とかも自在でないと困るよな。彼らも穴だらけの情報を元にした統計処理がメインだし。きっと大丈夫だよ。

              - じゃぁ、手元にあるこれを Perl から Pythonに移植してみるかね ←(今ここ)

              という状態。

              --
              fjの教祖様
              親コメント
              • by tslashn (37583) on 2010年08月17日 8時01分 (#1810518)
                1は「なるほど~」とおもったが、2は結構あやうい推論ですね。
                Googleの原点といえばPageRank。
                これを計算するpythonプログラムは第三者から公開されていますね。
                http://www.eioba.com/a69792/the_google_pagerank_algorithm_in_126_lines_of_python

                しかし、こいつらは、穴だらけというよりは、つながりのほうが少ないですからなあ。
                穴だらけの統計処理だったら第一選択はRというのがなんとなくの雰囲気。
                (測定値に穴があるときそこは0ではありませんから
                それをだまって0にするperlは最悪なような:)

                教祖様におかれましてはここはひとつ、Python, R, Ruby, Go, ...と各種の言語で書き比べて
                比較されてはいかがでしょう。
                親コメント
              • by okky (2487) on 2010年08月17日 11時59分 (#1810662) ホームページ 日記

                それをだまって0にするperlは最悪なような

                統計ライブラリ類はこの辺に手抜きがありますよね。
                自分で書いている分は if defined() で調べまくって、 undef なものは undef として処理させています。

                いや、それでもこれはまだいいのですが、最初いくつ要素があって、いくつデータがあるのか判らないTSVファイルを読み込まなくちゃいけないので、正規表現とかでのデータ切り出しも必要だし、配列を動的に拡大しているのでメモリ空間はフラグメントの嵐になっていてメモリを大量に消費するし…でも、そんな部分を改善するために面倒なコーディングしたくないし。

                なので、なんかもっとこう…「いい」感じで手間がかからない、処理速度が速い言語はないものかと。

                ただし、できる事ならRHELに対して「新しくインストールする」必要性が無いといいなぁ、というのもありまして。
                # そういう事をしなくちゃいけなくなると、絶対、手離れが悪くなるから。
                # Go, R, Ruby はこの段階で優先度が下がる。

                そのときふと思い出したんですよ。
                「あぁ、そういえばPython チュートリアルの2007年版、買ったままだったな」と。

                PythonはPerlよりも後発だったよな、型bindingも強かったよな。「じゃぁ、いろいろと処理系側で自動的によきに計らってくれるに違いない」(自動型変換とか。配列の自動拡張とか)。

                まさかいまどき「純粋逐次処理系」だとは思いませんでした~~。
                # 実行速度とかを考えると、これは律速にしかならないので。

                --
                fjの教祖様
                親コメント
              • by tslashn (37583) on 2010年08月17日 21時39分 (#1811129)

                最初いくつ要素があって、いくつデータがあるのか判らないTSVファイルを読み込まなくちゃいけない

                だけなら、Rでread.table()の一言で終わりですん。
                しかし、

                正規表現とかでのデータ切り出しも必要だし、

                だと、rubyのほうが有利かな。

                $ irb
                irb(main):001:0> a=Array.new
                => []
                irb(main):002:0> a[13]=1.26
                => 1.26
                irb(main):003:0> a
                => [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1.26]
                irb(main):004:0> str="1\t\t2\t3\t4\n"
                => "1\t\t2\t3\t4\n"
                irb(main):005:0> str.chomp.split("\t")
                => ["1", "", "2", "3", "4"]
                irb(main):006:0>

                のように、配列であると宣言すれば適当なところに突然代入するのはok
                tab区切りを、空文字列を含めて配列にするのも大丈夫。
                (バックスラッシュが通貨記号なのはcygwinでやっていたらそう、、、)

                処理速度が速いはおそらくRのほうだろうねえ。

                ただし、できる事ならRHELに対して「新しくインストールする」必要性が無いといいなぁ、というのもありまして。
                # そういう事をしなくちゃいけなくなると、絶対、手離れが悪くなるから。
                # Go, R, Ruby はこの段階で優先度が下がる。

                とりあえずrubyはcygwinに入っているじゃあありませんか。
                RHELは知らないけどCentOSと同じだとすると、
                root様ならyum install ruby
                すればその後のupdateも自動。
                ユーザー権限でいくなら、展開後
                ./configure --prefix=$HOME/ruby192
                make
                make install
                くらい。
                (gccとかが入っていれば、、、インストール時の選択しだいで入っていないこともある?)

                Rをコンパイルするのはもっとだいぶ面倒なのは確か。
                (fortranも入れておかないといけないし、そのほかライブラリーがいっぱい)
                一応、redhat用やwindows用のbinaryはダウンロード可能。
                しかし、あとのグラフ化まで使えるので、結構意味はあると思う。
                でも、仮にも教祖様が苦労するレベルではないので、
                「手離れが悪く」っていうのはほかの人にやらせたいということなら、
                windows版バイナリーでやれがいいかも:p

                自動型変換といえば、perlでは3=="1"+"2"が真だったりするんでしたっけ。
                文字列は文字列ですから、"12" == "1" + "2"
                の方が人間的ですぞ。強い型bindingとは相性が悪いでしょう。

                親コメント
          • そういう用途には辞書(連想配列)を使うのが妥当です。シーケンスであるリストと違って順序性は保証されませんが。

            >>> a = dict()
            >>> a[12345] = 1
            >>> a[12345]
            1

            もっとも、このままa[123]などと存在しないキーで辞書を参照するとKeyError例外の嵐なので、そういう時はたとえばa.get(123, None)と書くと、辞書aの中に123というキーがあれば対応する値、なければNoneが返ります。

            >>> a[123]
            Traceback (most recent call last):
              File "<pyshell#2>", line 1, in <module>
                a[123]
            KeyError: 123
            >>> print a.get(123, None)
            None
            >>> print a.get(12345, None)
            1

            ただしこの場合、getの呼び出し後も辞書a自体には何の変化もありません。従ってa.get(123,None)の後、a[123]を参照するとやっぱりKeyErrorが発生します。
            >>> print a.get(123, None)
            None
            >>> print a[123]
            Traceback (most recent call last):
                File "", line 1, in
                    a[123]
            KeyError: 123

            存在するキーでアクセスしたらその値を返し、存在しないキーでアクセスしたらデフォルト値を返すと同時にデフォルト値をセットしたいという場合はa.setdefault(123, None)と書きます。こうすれば以後a[123]はNoneを返します。

            >>> print a.setdefault(123,None)
            None
            >>> print a[123]
            None
            >>> print a.setdefault(12345, None)
            1

            もっとも、多用するならdictクラスを継承して新しいクラスを作ったほうがいいでしょうけど。

            親コメント
  • by ninestars (5792) on 2010年08月16日 15時37分 (#1810184) 日記
    #!/usr/bin/perl なのは置いとくとして、通常のスクリプティングであれば
    #下記の定型を使用するのが良いかと。

    def xxxx():
        """ 何か処理 """
        pass

    def main():
        xxxx()
        pass

    if __name__ == '__main__':
        main()

    python の特徴として、モジュール化に特別な書式が不要なのだが、
    逆に import 時に不要な実行を行わないよう __name__ の値を評価し、
    モジュールとして読み込まれたのか判別するのが一般的。

    • ベタなスクリプトという側面と、構造化したソースを両立した結果でしょうかね

      # まだ疎いので上記は大変参考になりました

      --
      M-FalconSky (暑いか寒い)
      親コメント
    • あー、なるほど。

      ってことは、Pythonというのは
      「ファイル全部を読み込んで、シンボル解決を全部やって、それから実行」
      しているんじゃなくて、あくまでも

      「1行づつ読んでは実行している。
       ただし、def とか class は Lispでいう defun と同じで、
          とりあえず quote された状態に相当するのでとりあえず読むだけ。

          どこかで評価しなくちゃいけない行が現れたら、
          その時までに読み込んで定義されたシンボルを使って
          実行できれば実行する。無理だったらエラー」

      って事か…暗黙のスコープとして「今まで読んだ分」があり、それを回避するには、一番最後までは全部 quote 状態にして、最後に「Go!」って書かなくちゃいけない…ギャース、その暗黙のスコープは(T.T) すごくはまるもとだ。

      参考資料をかなり厳選しないといけなさそう。安直に書いてるものとかだと大嘘が書いてありそうだ…。

      --
      fjの教祖様
      親コメント
  • pythonの場合、関数定義は実行時まで評価されません。

    def a
    def b

    のタイミングで、シンボルaないしbに関数(実はオブジェクトへの参照)が代入されます。が、その時点では評価はされない (というよりも、aから見た global namespaceの'b'に何が代入されているか、動的に変更されるケースすらありうる) ので、実行時に参照が充足されればokです。

    存在しない架空の文法で書くと

    try:
      print a(); // この時点ではglobal namespaceに aは存在しない → error
    except:
      pass

    a = _(){ bを参照する関数定義 }; // 実行時評価
    b = _(){ aを参照する関数定義 };
    print a(); // ここでaの中身を実行すると、既にglobal namespaceにbがあるので、エラーなく相互参照が可能

    てな感じだと思っていただければと。

  • print "0x" + hex(x) # hex()は整数の十六進表記文字列を返す組み込みメソッド
    x = 128

    を実行すると、1行目でxが未定義というエラーになります。これは納得できますよね。

    print f(128)
    f = lambda x: "0x" + hex(x) # lambdaで無名の関数オブジェクトを生成

    を実行すると、1行目でfが未定義というエラーになります。これも納得できますよね。

    defで関数を定義した

    print f(128)
    def f(x):
      return "0x" + hex(x)

    も同様に、1行目でfが未定義というエラーになります。それだけのことです。

    • あーー、これは困ったな。実は最初の1つと後の2つは違う事を言っています。

      print "0x" + hex(x) # hex()は整数の十六進表記文字列を返す組み込みメソッド
      x = 128

      を実行すると、1行目でxが未定義というエラーになります。これは納得できますよね。

      えー、この段階ですでに「納得できない」だったりします。あ、いや、Pythonがそういう言語だ、と言うのは判りました。でも、それは必ずそうならざるを得ない、と言うものではない。

      例えば Perl ですと、(x ではなく $x と書く必要がありますし、hex()は変換方向が逆向きなんで sprintf を使いますが):

      print "0x" . sprintf "%lx\n", $x; # hex()は整数の十六進表記文字列を返す組み込みメソッド
      $x = 128;

      こう書くとエラーになりません。

      bash-3.2$ ./test.pl
      0x0
      bash-3.2$

      とこのようになります。これは sprintf の行で $x と言った瞬間に $x という変数が undef (pythonでいうNone ?) という値で作られます。参照するだけで作られる。で、undef な変数は「数値として参照する場合は0として参照される」というルールに従って、sprintf の結果が "0" になる。

      変数は「参照するだけで」存在できる。もちろん、配列も突如として $a[1790000] という値を参照したら、その瞬間に(内部的にどう表現しているのかはともかく) $a[1790000] という変数が出来て、値はundefになります。

      このように「仮に前方参照ができないとしても」エラーになる必然性はないんです。

      .

      print f(128)
      f = lambda x: "0x" + hex(x) # lambdaで無名の関数オブジェクトを生成

      を実行すると、1行目でfが未定義というエラーになります。これも納得できますよね。

      確かに perl でもエラーになるのですが、理由はちょっと違います。

      f をアクセスした瞬間に、fは undef で定義されます。つまり f(128) と言われた瞬間に「fというシンボルが定義されていないから」実行できないのではありません。
      「fは存在する。ただし、その内容が nil だ。nil は実行できぬ。故にエラーだ」
      となります。

      .

      print f(128)
      def f(x):
          return "0x" + hex(x)

      も同様に、1行目でfが未定義というエラーになります。

      Perlの場合は、def…じゃなくて sub ですが…は1行目でも有効です。

      ようするに Perl は2パスになっているんです。1パス目で def をはじめとする全てのシンボルを登録し(ついでに内部コードに直して) 2パス目で実行を開始する。ただし、def のような quote ものは全部すっとばす。

      すると、2-3行目の関数fの宣言は1パス目で処理され、登録された後なので、1行目は「シンボルエラー」にもなりませんし「内容がnil」にもなりません。なので実行可能になります。

      .

      で、これで判ると思いますが。
      関数宣言に関する前方参照不能性は、「Pythonが1パスで実行可能な文法」を採用していると言う事です。
      しかし、変数は「前方参照不能」なのではなく「書き込み参照以外、変数生成対象としない」と言う事です。
      と言う事は、当然実際にはあと2通りのパターンのプログラミング言語があり得る事になります。

      これらはどれが良いと言うわけではありません。
      参照するだけで変数生成対象とすると、「変数名のスペルミス」がほとんどまったく検出できません (Perl はわざわざ「変数宣言」を別途用意してそこにない変数は生成しない、use strict という機能を後付けで作らなくちゃいけなかったぐらいです)。全体を2回パースするのは遅いですし、スコープを厳密に把握していないと「あれ?」と言いたくなるような所で同じ名前の関数が二つ宣言されてたりしかねません。

      こんなに違っていたとは…面白い。

      とはいえ、Perlで書いちゃったものをPythonに移植するのは、簡単ではないということは、8時間いじっただけでもよく判りました。line-by-line での移植は駄目だな。Pythonをもっとまじめに勉強しないと…。

      # とりあえず、配列の飛離れた位置を一気に有効にする方法と、配列のサイズを事前宣言する方法かな。

      --
      fjの教祖様
      親コメント
      • 今の時代に use strict; 無しの perl を語るとは思わなかった。

        親コメント
      • > でも、それは必ずそうならざるを得ない、と言うものではない。
        「あらゆるオブジェクトの前方参照は未定義エラーになる」というPythonの言語仕様にも相応に合理性があるんだから納得してくださいね、という話を「必ずそうならざるを得ない、と言うものでない」から納得できない、とすり替えて逃げますか。

        親コメント
      • 逆に、perlは大昔にちょろっと勉強しただけなので、そういう違いがあったのかと今はじめて知りました。ふーん。

        pythonだと、importでの読み込み時に環境を見ながら関数の定義を(単に代入で f = f_foo とか f_bar とかやって)挿し替えたりするのですが(行儀の悪い方法ですが、便利です)、perlではそういうことはあまりしないんですかね。

        単純移植はいずれにせよ難しそうですね。

        疎行列を簡単にemulateしたい時は辞書にしちゃう手もあります。 {}.get(key, default) で、keyがなければdefaultを得ることができます。実行効率は知りませんが。

        見掛け上リストでアクセスしたければ、クラスを作ってそのクラスに __getitem__(self, key), __setitem__(self, key, value) とかを実装してあげればできます。

        親コメント
      • by tslashn (37583) on 2010年08月17日 8時30分 (#1810523)

        関数宣言に関する前方参照不能性は、「Pythonが1パスで実行可能な文法」を採用していると言う事です。
        しかし、変数は「前方参照不能」なのではなく「書き込み参照以外、変数生成対象としない」と言う事です。
        と言う事は、当然実際にはあと2通りのパターンのプログラミング言語があり得る事になります。

        Cのような関数宣言はないんですよね。
        関数定義はある。
        しかるに、def fはfという変数にlambda式を代入するのと等価であると。
        そしてそれは変数と同じで後で別の内容をbindすることができます。
        なので、関数と変数のscopeは同じで、どちらも、書き込み参照・定義によってのみ生成される
        と統一的に理解するべきです。
        f(128)で生じるエラーがname 'f' is not definedであって
        function f is not definedとかいわれないことにも注目。

        親コメント
typodupeerror

開いた括弧は必ず閉じる -- あるプログラマー

読み込み中...