ずっと君のターン

2008-11-20 快晴。寒い

PythonとJavaScriptはちょっと似てると思ってる

| 02:52 | PythonとJavaScriptはちょっと似てると思ってる - ずっと君のターン を含むブックマーク

先週の金曜日にちょっとした集まりがあって、どういう流れでそうなったのか思い出せないんだけど、なぜかPythonistaがJavaScriptをDisって私がJSを弁護するという展開になった。

まぁでもここではそれの場外乱闘をしたいわけではなくて、ただそんとき言い忘れたことを思い出したので、それについてちょっと書きたい。

要は「PythonとJavaScriptってわりと似てね?」って話。

Pythonの勉強を始めて最初に思ったことなんだけど、PythonとJS、この二つはなんだか他人と思えない。別に上のPythonistaに喧嘩売ってる訳ではなくて、以前ホントにそう思った・・・んだけどなぁ

・・・

ずっと以前にJavaScriptの本読んでかなり驚いたことの一つに変数のスコープの話がある。JSだと変数がブロックのどの位置で宣言されてもその変数はブロックの先頭から有効になるという奴。例えば

function fooIsUndefined() {
  alert(foo);
  var foo = 'hello';
}

上のはfooがブロック内部で宣言されてるのでalertは「undefined」と表示してくれるんだけど

function raiseError() {
  alert(foo);
}

こっちは宣言されてないのでエラー。

あとこれはおまけだけど

function fooIsUndefined() {
  alert(foo);
  if (false) var foo = 'hello';
}

宣言文は実行されなくても大丈夫。これも「undefined」と表示される。

で、この動作、Pythonでも同じ。まぁ同じって言っても両方ともエラーだけど。

def foo_is_undefined():
  print foo
  foo = 'hello'

のエラーは

UnboundLocalError: local variable 'foo' referenced before assignment

「まだ束縛されてない変数ですよ」だけど

def raise_error():
  print foo

NameError: global name 'foo' is not defined

「そんな変数ねぇよ」

で、

def foo_is_undefined():
  print foo
  if False:
    foo = 'hello'

UnboundLocalError: local variable 'foo' referenced before assignment

「まだ束縛されてない変数ですよ」

ほらJSと一緒。この挙動って自分にはちょっと驚きだったんだけど、他にもこういう動作する言語たくさんあったりするんだろうか?

あとJSだと関数の内部でも関数を定義できたりするね。

function outer() {
  function inner() {
    alert('inner!');
  }
  inner();           // もちろんここでは呼べる
}
// inner();          // これは関数が見つからなくてエラー
new outer().inner(); // これなら大丈夫

Pythonでも関数のネストは可能。つかevalが貧弱すぎるPythonには必須。まぁさすがに関数をnewするのは無理だけど。

def outer():
  def inner():
    print 'inner'
  inner()      # 大丈夫
#inner()       # エラー
#outer.inner() # さすがに無理

関数のネストができる言語はいっぱいあるけどRubyはできないんだよな。できたらうれしいんだけど。

あとあれ、関数名が関数オブジェクトで()演算子で実行されるとかも一緒。

function bar() {
  alert('bar');
}
var xyzzy = bar;
xyzzy();
def bar():
  print 'bar'
xyzzy = bar
xyzzy()

まぁこれはCとかでも一緒か。Rubyが特殊なだけかも。

あとはー、あれ?なんも思いつかないな。

なんかあれだな、考えてみれば最初のやつ以外は別に珍しくもないし、最初のやつも実は自分で書きながら思ったけど、似てるっていうにはちと微妙だ。最近Pythonさぼってるから覚えてないけど、以前はもっと似てると感じた気がしたんだけど・・・。

うむ、残念ながらPythonとJavaScriptはちょっと似てると思ってたけどそうでもなかった。まぁそれはそれとしてJSも言語としてはそう悪くないはずなのでそんなに嫌わなくていいと思うよ。てことで、まとまらずにおわる。

2008-07-23 いい天気

Pythonのデコレータが面白いのでRubyで実装してみた

| 19:20 | Pythonのデコレータが面白いのでRubyで実装してみた - ずっと君のターン を含むブックマーク

・・・けどいまいちだったorz

一応説明すると、デコレータって言うのはPythonにあるアノテーションでメソッドを拡張できる機能・・・らしい。

今回の目標はこれを動かすこと。(@declareArgs(float, float)がデコレータ)

http://www.itmedia.co.jp/enterprise/articles/0501/24/news034_3.html

# http://www.itmedia.co.jp/enterprise/articles/0501/24/news034_3.html
import math

def declareArgs(*argTypes):
 def checkArguments(func):
   assert func.func_code.co_argcount == len(argTypes)
   def wrapper(*args, **kwargs):
   pos = 1
   for (arg, argType) in zip(args, argTypes):
    assert isinstance(arg, argType), \
       "Value %r dose not match %s at %d" % (arg, argType, pos)
    pos += 1
   return func(*args, **kwargs)

  wrapper.func_name = func.func_name
   return wrapper

 return checkArguments

@declareArgs(float, float)
def calcDistance(x, y):
 return math.sqrt(x * x + y * y)

print calcDistance(3.14, 1.592)
print calcDistance(2.0, 4) 

実行結果。

# http://www.itmedia.co.jp/enterprise/articles/0501/24/news034_3.html
$ /opt/Python-2.4/bin/python DecoratorTest.py
3.52052041607
Traceback (most recent call last):
  File "DecoratorTest.py", line 27, in ?
    print calcDistance(2.0, 4)
  File "DecoratorTest.py", line 11, in wrapper
    assert isinstance(arg, argType), \
AssertionError: Value 4 dose not match at 2 

ていうか、なんでITmediaはデコレータの説明なのにわざわざリフレクション使いまくった例にしたんだろう。移植がめんどいよ・・・。

とりあえずデコレータはこんな感じの実装にしてみた。

# decorator.rb
module Kernel
  def decorate(decorator, target)
    decorator = method(decorator) if decorator.is_a? Symbol
    method_name, method_body =
      case target
      when Method
        [target.to_s.sub(/#<Method: (\w+)#(\w+)>/, '\2'), target]
      else
        [target, nil]
      end
    if self.is_a? Class
      method_body ||= instance_method(target).bind(self.new)
      define_method method_name, &decorator.call(method_body)
    else
      method_body ||= method(target)
      self.class.instance_eval{define_method method_name, &decorator.call(method_body)}
    end
  end
end

使ってみたところはこう。

require 'decorator'

def declare_args(*arg_types)
  lambda do |func|
    raise <<-eos.strip unless func.arity == arg_types.size
      The number of arguments, #{func.arity}, should be #{arg_types.size}.
    eos
    lambda do |*args|
      args.size.times do |pos|
        arg, arg_type = args[pos], arg_types[pos]
        raise <<-eos.strip unless arg.instance_of? arg_type
          Value #{arg} does not match #{arg_type} at #{pos}.
        eos
      end
      func.call(*args)
    end
  end
end

def calc_distance(x, y)
  Math.sqrt(x * x + y * y)
end
decorate declare_args(Float, Float), :calc_distance

puts calc_distance(3.14, 1.592)
puts calc_distance(2.0, 4)

実行結果

% ruby dec1.rb
3.52052041607487
dec1.rb:12:in `calc_distance': Value 4 does not match Float at 1. (RuntimeError)
        from dec1.rb:10:in `times'
        from dec1.rb:10:in `calc_distance'
        from dec1.rb:27

んー、まぁ一応動いてるんだけど、アノテートみたいにメソッド定義の前に付けられないのがなぁ・・・。あとデコレータの実装見れば分かるけど、instance_method(target).bind(self.new) ←ここで新しくnewしたオブジェクトをバインドしてるのが最悪。なんとかうまく実行時のインスタンス引っ張って来れないもんか。

まぁ結局、こういうのはActiveSupportのalias_method_chainを使ったほうがいいと思った。

2008-07-21 主に曇り

protocalendar用のウィジェット

| 01:57 | protocalendar用のウィジェット - ずっと君のターン を含むブックマーク

日付を簡単に入力したかったのでprotocalendar.js用のウィジェットを作ってみた。

こんだけ。今のところprotocalendarのオプションは何も設定できない。

# widgets.py
from django import newforms as forms

class CalendarWidget(forms.TextInput):
  def render(self, name, value, attrs=None):
    return super(CalendarWidget, self).render(name, value, attrs) + """
      <script type="text/javascript">
         InputCalendar.createOnLoaded('%s');
      </script>
    """ % attrs['id']

使い方はこう。

import time
import widgets
class YourForm(djangoforms.ModelForm):
  class Meta:
    model = models.YourModel
  date = forms.CharField(widget=widgets.CalendarWidget, initial=time.strftime('%m/%d/%Y'))

現在日付を初期値にする必要がないならimport timeとinitialオプションはいらない。