python

Pickle some parts of object

ใน Python มีกระบวนการอย่างหนึ่งเรียกว่า pickle ซึ่งใช้ในการแปลงข้อมูลจาก object ไปเป็นข้อความเพื่อนำไปเก็บหรือส่งข้อความนั้นข้ามระบบแล้วแปลงข้อมูลจากข้อความกลับมาเป็น object ใหม่

>>> a = {'k1':'v1', 'k2':'v2', 'k3':[1,5,3,4]}
>>> import pickle
>>> s = pickle.dumps(a)
>>> s
"(dp0\nS'k3'\np1\n(lp2\nI1\naI5\naI3\naI4\nasS'k2'\np3\nS'v2'\np4\nsS'k1'\np5\nS'v1'\np6\ns."
>>> b = pickle.loads(s)
>>> b
{'k3': [1, 5, 3, 4], 'k2': 'v2', 'k1': 'v1'}

แล้วเกิดสมมติว่าเรามี object แบบนี้

class kls:
    def __init__(self):
        self.hello = ''
        self.b = 'b'
        self.c = 'this is c'
        self._d = 'this is d' 

โดยที่เราไม่อยากให้ pickle เอา d ไปด้วย วิธีง่ายๆคือให้ override __getstate__ เท่านั้นเอง

def __getstate__(self):
    odict = self.__dict__.copy()  # Copy because we want to modify the clone, not the source
    del odict['_d']
    return odict

ส่วนเวลาเอากลับมาก็ override __setstate__

def __setstate__(self, dict):
    self.__dict__.update(dict)   # update attributes
    self._d = 'this is d'

ซึ่งในขั้นนี้ เราอาจจะทำการคำนวณค่าต่างๆเช่น _d กลับมาอีกรอบก็ได้ สำหรับรายละเอียด สามารถอ่านได้จากเอกสารเกี่ยวกับ Pickle

Django's ErrorDict to Python's dict

วันก่อนพยายามจะทำ ajax form validation กับ django ก็ประสบกับปัญหาว่าอยากจะส่ง error กลับมาเป็น json ซึ่งปกติจะใช้ simplejson ทำการ dump ข้อมูลจาก dict ออกมาได้เลย

from django.http import HttpResponse
import simplejson
def json_response(request, obj):
    return HttpResponse(simplejson.dumps(obj))

ปกติก็แค่ส่ง dictionary กลับไปก็ใช้ได้ ซึ่งตอนแรกก็เห็นว่า form.errors มันก็เป็น dict เลยใส่ลงไปเลยไม่ได้คิดอะไรมาก ปรากฏว่ามัน Error เพราะว่า form.errors เป็น ErrorDict ซึ่งเก็บ function ของ django อยู่ ทำให้ simplejson ไม่สามารถเอาข้อมูลออกมาได้ เลยจำเป็นต้องแปลงให้เป็น dict ก่อน

def edict_to_dict(edict):
    return dict([(key, val) for key, val in edict.iteritems()])

หลังจากนั้นมันก็กลายเป็น dict ธรรมดาส่งไปให้ simplejson ได้เลย

Joining array of strings in Java

จาก tweet ของ @veer66 เลยอยากรู้ว่าทำไมเลยลองหาดู ดีที่สุดได้แค่นี้

Java

String[] s = "Hello World".split(" ");
StringBuffer sb = new StringBuffer();
for (int i=0;i<s.length;i++){
     sb.append(s[i]);
     if (i != s.length-1)
        sb.append(" ");
}
System.out.println(sb.toString());

ถ้าภาษาอื่นล่ะ

Python (ผ่าน interactive mode)

>>> " ".join("hello world".split(" "))
'hello world'

Ruby (ผ่าน irb)

irb(main):001:0> "Hello World".split(' ').join(' ')
=> "Hello World"

C#

Console.WriteLine(String.Join(" ","Hello World".Split(' ')));

Ruby เท่สุดแฮะรู้สึกว่ามันเป็นธรรมชาติมาก

ตอนที่เขียน Python ใหม่ๆก็รู้สึกอยู่ว่ามันขัดๆ พอเริ่มคิดได้ว่า C# มันก็ไม่มี join ตรงๆเหมือนกัน ถึงนึกออกว่าตอนเขียน C# มันก็รู้สึกแปลกๆเหมือนกัน

อะ แถมด้วยโค้ดตอบ tweet นี้

irb(main):002:0> print 'คิดถึง'*99

Come back to GAE

We moved out and now we move in. Birdnest is now hosted by Google App Engine again.

New Birdnest Features: Callback and username stripout in Replies timeline

Today announcement for #birdnest, 2 new features roll out, callback parameter support and stripping out the username at the front of tweet.

Callback parameter

Requested by @iake right after #barcampbangkok2.

When supply Twitter API with a parameter called "callback", Twitter sends back the JSON result enclosed in a function you specified with callback parameter. It's useful for creating javascript widget but #birdnest cannot handle these result and doesn't filter the unnecessary information out. The new #birdnest can recognize this callback function and can filter the unwanted data out.

Stripping out the username at the front of tweet

Request by @bact.

Why put @username in front of every reply tweets in reply timeline especially those who have a very long name. Let #birdnest strips it out for you. Don't worry #birdnest doesn't touch your name when it is on you friends timeline or public timeline.

JSON-Only Features

These feature are available only on JSON and I don't think there are many XML format user. If you are, please tell me. I'll try to make it for you. What? Can't wait? You can do it yourself and send the patch to the project homepage.

Wanna try?

Just point your Twitter gateway to http://nest2.onedd.net/. Notice the gateway name, it's nest2.onedd.net, not nest.onedd.net. Because I want these features to be tested by all of you first.

As always, all suggestions and bug reports are welcome at the project Google Code page.

Happy Tweeting

Pylons กับ dependencies

หลังจากเราสร้าง Project ด้วยคำสั่ง paster กันไปแล้ว ทีนี้ก็มาถึงเรื่องของ dependencies แล้วล่ะ

ใน Pylons ปกติผมเห็นแต่ คน เค้า ใช้ SQLAlchemy กันทีนี้ก็คือถ้าจะลงก็ใช้ easy_install ได้

easy_install SQLAlchemy

ทดลองด้วยการเรียกใช้ Interactive Python Shell แล้วพิมพ์ตาม ต้องได้ผลตามนี้

>import sqlalchemy >sqlalchemy.__version__ '0.5.0beta3'

ทีนี้ก็ใช้งาน SQLAlchemy ได้แล้วล่ะ

แต่เดี๋ยวก่อน ถ้าเกิดเราไม่รู้ว่าต้องลงอะไรบ้างล่ะ เช่นตอนที่สร้าง project เราเลือกใช้ template อื่นนอกจาก Mako เช่น Jinja เราก็จะต้องมาลง Jinja ด้วย แต่ช้าก่อนถ้าเราลองสั่ง paster ดู จะได้ผลลัพธ์ดังนี้

Traceback (most recent call last): File "/home/wien/bin/paster", line 8, in load_entry_point('PasteScript==1.6.3', 'console_scripts', 'paster')() File "/home/wien/py-lib/PasteScript-1.6.3-py2.5.egg/paste/script/command.py", line 68, in run commands = get_commands() File "/home/wien/py-lib/PasteScript-1.6.3-py2.5.egg/paste/script/command.py", line 110, in get_commands plugins = pluginlib.resolve_plugins(plugins) File "/home/wien/py-lib/PasteScript-1.6.3-py2.5.egg/paste/script/pluginlib.py", line 81, in resolve_plugins pkg_resources.require(plugin) File "/usr/lib/python2.5/site-packages/pkg_resources.py", line 626, in require needed = self.resolve(parse_requirements(requirements)) File "/usr/lib/python2.5/site-packages/pkg_resources.py", line 524, in resolve raise DistributionNotFound(req) # XXX put more info here pkg_resources.DistributionNotFound: Jinja: Not Found for: hello (did you run python setup.py develop?)

เอ๊ะ มันทะแม่งๆแฮะ มาบ่นว่า Jinja ไม่มีไม่พอ มีการท้าทายว่า ได้ลองเรียก python setup.py develop รึยัง ท้าแบบนี้มีหรือจะไม่ลอง

[wien@localhost]$ python setup.py develop Processing dependencies for hello==0.1dev Searching for Jinja Reading http://www.pylonshq.com/download/ Reading http://pypi.python.org/simple/Jinja/ Reading http://wsgiarea.pocoo.org/jinja/ Reading http://jinja.pocoo.org/ Best match: Jinja 1.2 Downloading http://pypi.python.org/packages/source/J/Jinja/Jinja-1.2.tar.gz#md5=1235a005ade00b213800ff1e798c0241 Processing Jinja-1.2.tar.gz Running Jinja-1.2/setup.py -q bdist_egg --dist-dir /tmp/easy_install-HQeuAp/Jinja-1.2/egg-dist-tmp-fgE5yD Adding Jinja 1.2 to easy-install.pth file

อ่าวเฮ้ย มีท่านี้ด้วย เด็ดจริงๆ เลยเป็นความรู้ว่า ใช้ท่า python setup.py develop เพื่อลง dependencies ได้ด้วย ตรงนี้ยังไม่รู้ว่ามันจะเอามาใช้ตอน deploy ได้รึเปล่า แต่เดาว่ามันน่าจะใช้ได้ล่ะ ใครลองแล้ววานบอก

ด้วยรักและไพลอนส์66

Pylons, รับน้อง, Routing

รำลึกอดีตให้ฟังหน่อยนึงว่าหลังจากเข้าไปร่วมงาน NWA แล้วผมก็โดนเวทล้างสมองของ Sirn ให้หันมามอง Pylons เลยลองเล่นดูนิดหน่อย แต่ก็ไม่วายโดนรับน้องจนได้

ถ้าเราติดตั้ง Pylons ด้วย easy_install โดยไม่ระบุเวอร์ชัน ตัว Easy_install จะลง เป็นเวอร์ชัน 0.9.7rc1 ให้โดยอัตโนมัติ ซึ่งใน 0.9.7 นั่นมันจะมี Breaking Change อยู่จำนวนหนึ่ง แต่ในวิกิของ Pylons ยังไม่มีข้อมูลนี้

บังเอิญว่าผมดันไปเลือกใช้ 0.9.7 โดยไม่รู้อิโหน่อิเหน่ ค้นวิธีแก้ยังไงก็ไม่เจอ สถานการณ์แบบนี้ผมเรียกว่า ซวย ครับ

หนึ่งใน Breaking Change ที่โดนแน่ๆคือเรื่อง Webhelper แต่นั่นเป็นเรื่องที่เราจะไม่พูดกันครั้งนี้ เพราะครั้งนี้เราจะบอกถึง Breaking Change ที่ทำให้ผมเซ้งเซ็งอีกเรื่องแทน นั่นคือ Routing เป็นยังไงมาติดตามดู

เนื่องจากเป็นเด็กใหม่ เราก็ต้องทำตัวเป็นเด็กใหม่ที่ดีด้วยการศึกษาตาม Getting started ที่เค้าทำมาให้ หลังจากกำลังภายในของผมเริ่มหมดไปกับการถาม sirn และ vsatayamas ไปแล้ว ผมก็ฟันฝ่าตัว Getting Started มาจนถึงการทำ controller และ action แล้วล่ะ จากใน Docs หลังจากสร้าง HelloController เสร็จแล้วก็ต้องก็สร้าง method index ขึ้นมา หลังจากทำขั้นตอนดังกล่าวผมก็เปิดด้วยความกระหยิ่มยิ้มย่อง เข้าหน้า http://localhost:5000/hello ตามสเต็ปเป๊ะๆๆๆ สิ่งที่โผล่ขึ้นมาต้อนรับเป็นข้อความอันคุ้นเคย

404 Not Found

ความคิดที่แว่บเข้ามาในหัว "นั่นไง โดนเข้าแล้ว" เป็นที่ Routing ชัวร์ๆ อีแบบนี้ ก็เลยเปิดดูใน config/routes.py ก็มีบรรทัดแบบนี้อยู่แล้ว

map.connect('/{controller}/{action}') map.connect('/{controller}/{action}/{id}')

อ่านคร่าวๆ หมายความว่าจะได้ Route URL ในลักษณะนี้

/{controller}/{action}/{id} แล้วถ้าไม่ระบุ action ก็ต้อง route แบบนี้ /{controller}/{action}

แล้วผิดตรงไหน ลองไปลองมาก็เลยฉุกคิดได้นิดนึง เปลี่ยนไปเข้า http://localhost:5000/hello/index แทน ปรากฏว่าหรามาเลยครับ Hello world!!!

เลยตะหงิดๆ ขึ้นไปดูในคู่มือของ Routes ได้ความว่า ใน Routes รุ่นใหม่ที่มาพร้อมกับ Pylons 0.97 จะมีเรื่อง minimization เข้ามา คือจะไม่มีการ map url แบบครึ่งๆกลางๆแล้ว ดังนั้นจากที่คิดว่า /{controller}/{action} จะได้ map ไปผูกกับ controller ที่ระบุแล้วกำหนด action เป็น index เองถ้าไม่กำหนด ก็หมดสิทธิทำแบบนี้แล้ว

อธิบายเป็นภาษาคนก็คือ ต้องระบุ action ทุกครั้งด้วยนะจ๊ะเด็กโง่

แต่เฮ้ย ชาวบ้านชาวเมืองเค้าไม่เห็นต้องระบุ action ก็ยังเรียก index ให้ Pylons จะยอมน้อยหน้าได้ไง ขนาด ASP.NET MVC ยังทำได้เลย ถ้า Pylons ทำไม่ได้ย้ายกลับนะเฟร้ย

เลยไปค้นๆมา ได้ความว่ามันก็มีทางเปิดใช้ minimization เพื่อให้ Routes ทำงานตามพฤติกรรมเดิม โดยในไฟล์ Routes.py จะมีบรรทัดนึงที่เขียนว่า

map.minimization = False

ก็แก้เป็น True ซะก็สิ้นเรื่อง ง่ายมะๆ

แต่ช้าก่อน ถ้ามันง่ายขนาดนั้น แล้วไม่เปิดใช้ไว้ตั้งแต่แรกฟระ ความจริงปรากฏว่า Routes รุ่นถัดไปจะไม่ยอมให้เปิดการทำงานของ Minimization แล้ว วิธีแก้ที่ยั่งยืนกว่าคือ เพิ่มกฏเอง ฟังดูเหมือนจะยาก แต่แค่เพิ่มเข้าไปบรรทัดนึงเท่านั้นเอง

map.connect('/{controller}', action='index')

แบบนี้ ถ้าเข้า http://localhost:5000/hello ก็จะวิ่งไป index ให้แล้ว

ก็ง่ายๆเช่นนี้แล ด้วยรักและไพลอนส์

ปล. แต่ว่าเข้า http://localhost:5000/hello/ ก็ยัง 404 นะ

Updates on IronRuby

Last week I went to Thailand Next Web App and joined in a Rails session. They had a talk and discussed about running Rails in production. Of course, JRuby, Glassfish and other Ruby implementation including IronRuby were mentioned. Then I was asked about it because I was the only .NET guy there. At that time, I didn't follow the IronRuby for a long time so I replied them that no one other than John Lam and Phil Haacked have used the IronRuby.

Few days ago, while I read the feed about asp.net, I found an open source IronRuby project that aims to help developer implement WPF/Silverlight in rails-like fashion. And that's mean we, the developers, now can use IronRuby.

After some googling, I've recalled that IronRuby, like IronPython, is part of the Microsoft Dynamic Language Runtime. They both are released with full source under Microsoft Public License(MS-PL). Though the source code of IronRuby is hosted on RubyForge and IronPython is on the CodePlex, the license doesn't mention that they're open source projects. However, the IronRuby libraries are tended to be open source and are opened to receive the contributions.

From the IronRuby home page, the implementation still does not pass the RubySpecs and can only dispatch some Rails requests. That's mean it still cannot be used to host a Rails application. But you can utilize .NET framework and create an application with it, like IronNails. If you want to give it a try, you have to compile it yourself from the source. Of course, Windows machine is essential.

In TNWA last week, Sirn said "Don't host your rails apps with IIS". I'm sure the situation will change if IronRuby can host Rails app perfectly and I'm waiting for that day.

PS. As for IronPython, I saw how IronPython used with ASP.NET and it was fantastic!! However, I never have chance to try it myself.

The new nest has been completed

Dear all mobile twitter-ers, the construction of the new nest has been completed. After sweats and tears, we've set up a new gateway for birdnest at http://nest.onedd.net. I hope all 50x error we've encountered before now have gone. Please update your gateway url to this new nest.

But this new gateway comes at the cost of latency. Since this new birdnest is not hosted by Google App Engine anymore, there might be little drop in speed. However, you can still choose the old gateway as your desire. The app engine version is available but there's no service for their anymore. Moreover, I've added more variations for some methods, namely friends timeline and direct message related.

Last but not least, @sugree told me he might releases a new jibjib with this new gateway url soon. Please keep an eye on his site.

Happy tweeting.

porting birdnest

When I worked on Birdnest last week, I realized the simplicity and how powerful framework Google App Engine provided. Google provided framework makes my job in implementation the birdnest for AppEngine really easily. As you have seen, I implemented the working prototype of Birdnest within 3-4 hours after studied the web framework. With git and Google Code, the project went to usable state within 2 days.

Nevertheless, Birdnest is not responsive and reliable as it should be. It often returns error with 304 status code which mean Gateway timeout for some users. After some investigate, @sugree and I implied this happen because App Engine limits the outgoing urlfetch, HTTP related module we've used to call the Twitter API, request timeout to 5 seconds and Twitter servers are not responsive enough to reply within that duration. This causes Birdnest to be terminated and throw out 504 error to the clients.

Some of you might noticed the discussion between @sugree and I over twitter, we're now setting up Birdnest somewhere outside the Google App Engine to solve the above problem. Of course, to minimize porting time, web.py is chosen to be target framework because of its similarity to Google webapp framework. Anyway, we have to change urlfetch to httplib which is more complex and a bit harder to use. Moreover, web.py doesn't provide some functionality we've rely on App Engine. However, this is still considered easier than using php or any other language. Thanks to battery included.

The contruction of this new nest is planned to be completed and ready to use within Monday and the new gateway url will be announced thereafter.

Happy tweeting