<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-4177643508104568252</id><updated>2024-10-24T21:20:09.169+08:00</updated><category term="datastore"/><category term="counter"/><category term="python"/><category term="account"/><category term="datetime"/><category term="eclipse"/><category term="entity group"/><category term="ide"/><category term="mac"/><category term="model"/><category term="openid"/><category term="osx"/><category term="property"/><category term="pydev"/><category term="quota"/><category term="readme"/><category term="recipe"/><category term="scale"/><category term="snow leopard"/><category term="tags"/><category term="template"/><category term="transaction"/><category term="tutorial"/><title type='text'>Google App Engine 最佳實踐</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://practicalappengine.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://practicalappengine.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>12</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-4177643508104568252.post-5711704327516302629</id><published>2010-08-27T15:38:00.000+08:00</published><updated>2010-08-27T15:38:28.927+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="datastore"/><category scheme="http://www.blogger.com/atom/ns#" term="datetime"/><category scheme="http://www.blogger.com/atom/ns#" term="model"/><title type='text'>App Engine 上的時區問題</title><content type='html'>在 App Engine 上如果要儲存時間，在 model 中就會用 DateTimeProperty, DateProperty 或 TimeProperty 來做為資料型態，比如說：&lt;br /&gt;
&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;...
from google.appengine.ext import db

class FooModel(db.Model):
  ...
  ...
  created = db.DateTimeProperty(auto_now_add=True)
&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;以 &lt;code&gt;DateTimeProperty&lt;/code&gt; 來說，它對應到的資料結構是 python 標準函式庫中的 &lt;code&gt;datetime.datetime&lt;/code&gt;，不過，當你加上了 &lt;code&gt;auto_now_add=True&lt;/code&gt; 這個設定之後，每當建立一個新的 entity 時，Datastore API 就會自動加上「現在時間」，也就是 &lt;code&gt;datetime.datetime.now()&lt;/code&gt; 的資料，但是 App Engine 所使用的預設時區是 UTC（也就是 GMT+0），簡單地說，如果你的網站使用者都是台灣的用戶，那就會感覺到這個時間晚了八個小時（因為台灣的時區是 CST，也就是 GMT+8），如果要處理這個問題，這裡提供兩個可能的解決方法：&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;不要依賴自動產生的時間，DIY&lt;/h3&gt;第一個解決方法，就是在產生/儲存 entity 時不要讓 Datastore API 幫你產生現在的時間，每次在產生或是更新 entity 資料時，自己手動產生出正確的時間。在解決問題之前，首先要製作一個台灣時區的 &lt;code&gt;tzinfo&lt;/code&gt;，才可以去改變 &lt;code&gt;datetime.datetime&lt;/code&gt; 所表示的時區：&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;...
from datetime import tzinfo, timedelta

class TaiwanTimeZone(tzinfo):
    def utcoffset(self, dt):
        return timedelta(hours=8)

    def tzname(self, dt):
        return &#39;CST&#39;

    def dst(self, dt):
        return timedelta(hours=0)
&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;
接著，在產生或更新 entity 資料時：&lt;br /&gt;
&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;...
foo = FooModel(......, 
               created=datetime.datetime.now(TaiwanZone()))
foo.put()
&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;這樣就會用台灣時區（GMT+8）的時間來儲存資料了。&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;在輸出時用 filter 換掉&lt;/h3&gt;另外一個方式，就是在 template 要輸出時再換掉時區，當然還是要準備一個 &lt;code&gt;TaiwanTimeZone&lt;/code&gt; 的 &lt;code&gt;tzinfo&lt;/code&gt; 類別，然而在儲存資料時，還是使用預設的 UTC 時區去處理，但是可以自訂一個 template filter （詳細的作法請見&lt;a href=&quot;http://practicalappengine.blogspot.com/2010/08/filter.html&quot; target=&quot;_blank&quot;&gt;這篇文章&lt;/a&gt;）在 template 中顯示時動態換掉時間及時區：&lt;br /&gt;
&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;# get template register
register = webapp.template.create_template_register()

@register.filter
def twtz(value):
    from datetime import datetime, timedelta
    return (value + timedelta(hours=8)).replace(tzinfo=TaiwanTimeZone())
&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;這樣只要在 template 中加上這個 filter 來顯示就可以了。</content><link rel='replies' type='application/atom+xml' href='http://practicalappengine.blogspot.com/feeds/5711704327516302629/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://practicalappengine.blogspot.com/2010/08/app-engine.html#comment-form' title='2 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/5711704327516302629'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/5711704327516302629'/><link rel='alternate' type='text/html' href='http://practicalappengine.blogspot.com/2010/08/app-engine.html' title='App Engine 上的時區問題'/><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4177643508104568252.post-6042858320661625438</id><published>2010-08-21T16:57:00.000+08:00</published><updated>2010-08-21T16:57:42.935+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="template"/><title type='text'>自訂範本系統中的 filter</title><content type='html'>在 App Engine 上製作網頁時，若是沒有使用 django 或是其它的 template 函式庫，應該都會直接使用 App Engine 所提供的 &lt;a href=&quot;http://code.google.com/intl/en/appengine/docs/python/gettingstarted/templates.html&quot; target=&quot;_blank&quot;&gt;django template wrapper&lt;/a&gt;（用的是 django 0.96 版的 template），不過這就沒辦法（很簡單地）照著 django 所提供的方式自訂標籤及 filter。不過 App Engine 還是有提供自訂 filter 的方式，只要按照下列的步驟（假設你應用程式的目錄是在 &lt;code&gt;$APP&lt;/code&gt;）：&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;建立一個 Python 模組來定義 filters:&lt;br /&gt;
&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;# $APP/my_filters.py
from google.appengine.ext import webapp

# 取得 template filters register
register = webapp.template.create_template_register()

@register.filter
def tolower(string_value):
    return string_value.lower()
&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;這樣一來便建立了一個自訂的 filter: &lt;code&gt;tolower&lt;/code&gt;，等等便可以用在 template 中。&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;雖然建立好了自訂的 filter(s)，但是 App Engine 的 template 函式庫還不知道有這個東西的存在，所以在使用範本引擎輸出前，記得加入下列的程式碼註冊你自訂的 module(s):&lt;br /&gt;
&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;...
from google.appengine.ext.webapp import template

# 註冊自訂 filters 的模組（載入模組的名稱）
template.register_template_library(&#39;my_filters&#39;)
&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;/li&gt;
&lt;li&gt;完成以上步驟後，就可以在範本中像這樣來使用自訂的 filters：&lt;br /&gt;
&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;html&quot; name=&quot;code&quot;&gt;...
Description: {{ description|tolower }}
&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;br /&gt;
如此一來，便不必在 request handler 中預先處理輸出的內容，可以把這部份的程式碼用 filter 來解決。</content><link rel='replies' type='application/atom+xml' href='http://practicalappengine.blogspot.com/feeds/6042858320661625438/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://practicalappengine.blogspot.com/2010/08/filter.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/6042858320661625438'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/6042858320661625438'/><link rel='alternate' type='text/html' href='http://practicalappengine.blogspot.com/2010/08/filter.html' title='自訂範本系統中的 filter'/><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4177643508104568252.post-9108324747480338674</id><published>2010-07-14T22:00:00.000+08:00</published><updated>2010-07-14T22:00:53.879+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="account"/><category scheme="http://www.blogger.com/atom/ns#" term="openid"/><category scheme="http://www.blogger.com/atom/ns#" term="python"/><title type='text'>使用 OpenID 作為帳號驗證</title><content type='html'>AppEngine 在 1.3.4 版本之後，開始實驗支援 OpenID 的身份驗證，除了可以在建立 app 時選擇使用 OpenID 作帳號驗證之外，也可以在後台設定。&lt;br /&gt;
&lt;br /&gt;
&lt;img alt=&quot;&quot; src=&quot;http://code.google.com/appengine/docs/images/authoptions.png&quot; /&gt;&lt;br /&gt;
&lt;small&gt;建立 app 時可以選擇使用 Open ID&lt;/small&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAwGNLWmr62qBQSo_ACmA-Sc7nSiO0GBp0mmeV_v96Yj978FuG1zS-Y1f17BC20iHgbCqkQFYEz3Pv1Wyh0_wRVEzew_GLXNQNmkVTIbOZUSSDctUamXMebP3cM9K-xshwXVmyd6MY7w/s320/auth.png&quot; /&gt;&lt;br /&gt;
&lt;small&gt;在管理後台的 Administration &amp;gt; Application Settings 中設定&lt;/small&gt;&lt;br /&gt;
&lt;br /&gt;
設定好了之後，其實程式也不用修改太多，還是可以直接使用 &lt;code&gt;google.appengine.api.users&lt;/code&gt; 模組中的函式來做身份認證，App Engine 已經實作了 Open ID 的規格，所以可以根據 Open ID 找出認證網址，但首先要在 app.yaml 檔案中加入 &lt;code&gt;/_ah/login_required&lt;/code&gt; 的 URL 像是這樣：&lt;br /&gt;
&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;yaml&quot; name=&quot;code&quot;&gt;(app.yaml)
...
- url: /_ah/login_required
  script: openid_login.py
...&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;
然後在你的登入頁面中，將登入的動作導向 &lt;code&gt;/_ah/login_required&lt;/code&gt; 這個 URL，而處理的程式就像這樣：&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;(openid_login.py)...
from google.appengine.ext import webapp
from google.appengine.api import users

class OpenIdHandler(webapp.RequestHandler):
    def get(self):
        ....
        # 使用者輸入的 Open ID URL
        openid_url = self.request.get(&#39;openid&#39;)
        # Open ID 認證結束後導向的 URL
        continue_url = &#39;....&#39;
        # 將使用者導向 Open ID provider 的認證網址：
        self.redirect(users.create_login_url(continue_url, None, openid_url))
...&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;如果 &lt;code&gt;openid_url&lt;/code&gt; 是空值，則 App Engine 會利用 Google Account API 來完成認證。&lt;br /&gt;
&lt;br /&gt;
當使用者用 OpenID 認證成功之後，就可以使用利用下面的方式來取得使用者的 OpenID 資訊：&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;...
from google.appengine.api import users
...
user = users.get_current_user()
if user:
    # 取得 openid identity
    id = user.federated_identity()
    # 取得 openid provider URL
    provider = user.federated_provider()
...&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;</content><link rel='replies' type='application/atom+xml' href='http://practicalappengine.blogspot.com/feeds/9108324747480338674/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://practicalappengine.blogspot.com/2010/07/openid.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/9108324747480338674'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/9108324747480338674'/><link rel='alternate' type='text/html' href='http://practicalappengine.blogspot.com/2010/07/openid.html' title='使用 OpenID 作為帳號驗證'/><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAwGNLWmr62qBQSo_ACmA-Sc7nSiO0GBp0mmeV_v96Yj978FuG1zS-Y1f17BC20iHgbCqkQFYEz3Pv1Wyh0_wRVEzew_GLXNQNmkVTIbOZUSSDctUamXMebP3cM9K-xshwXVmyd6MY7w/s72-c/auth.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4177643508104568252.post-7805012231763050718</id><published>2010-01-07T22:04:00.002+08:00</published><updated>2010-01-07T22:17:55.150+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="mac"/><category scheme="http://www.blogger.com/atom/ns#" term="osx"/><category scheme="http://www.blogger.com/atom/ns#" term="python"/><category scheme="http://www.blogger.com/atom/ns#" term="snow leopard"/><title type='text'>[Mac] 在 Snow Leopard 上開發 Google App Engine</title><content type='html'>Mac OSX 在 Snow Leopard (10.6) 之後，已經將預設的 python 設定為 2.6，不過系統還是有安裝 2.5 版本，所以開發基本上沒有什麼問題，只是要稍微作一些調整：&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;如果你的電腦還沒有安裝過 &lt;a href=&quot;http://developer.apple.com/tools/xcode/&quot; target=&quot;_blank&quot;&gt;XCode&lt;/a&gt;（Mac 系統安裝光碟內及iPhone SDK 都有），必須要先安裝，讓系統有安裝編譯的工具&lt;/li&gt;&lt;br /&gt;&lt;li&gt;雖然系統內建了 Python 2.5，不過並沒有安裝 &lt;a href=&quot;http://www.pythonware.com/products/pil/&quot; target=&quot;_blank&quot;&gt;PIL&lt;/a&gt; 這個 Python 處理影像的函式庫，因為 App Engine 中的 image API 會用到 PIL，所以也要安裝這個函式庫。為了讓函式庫能支援 JPEG 檔案的處理，所以就要先來安裝 libjpeg。&lt;/li&gt;&lt;br /&gt;&lt;li&gt;首先到&lt;a href=&quot;http://www.ijg.org/&quot; target=&quot;_blank&quot;&gt;這裡&lt;/a&gt;下載 &lt;code&gt;jpegsrc.v7.tar.gz&lt;/code&gt; 檔案，然後在文字模式下依照下列步驟編譯及安裝：&lt;blockquote&gt;&lt;code&gt;&lt;br /&gt;$ tar zxvf jpegsrc.v7.tar.gz&lt;br /&gt;....&lt;br /&gt;$ cd jpeg-7&lt;br /&gt;$ export CC=/usr/bin/gcc-4.0&lt;br /&gt;$ ./configure --enable-shared --enable-static&lt;br /&gt;$ make&lt;br /&gt;$ sudo make install&lt;/code&gt;&lt;/blockquote&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;如果一切都很順利的話，那就可以到 PIL 網站下載 &lt;code&gt;Python Imaging Library 1.1.6 Source Kit&lt;/code&gt; 原始檔案回來編譯：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;br /&gt;$ tar zxvf Imaging-1.1.6.tar.gz&lt;br /&gt;...&lt;br /&gt;$ cd Imaging-1.1.6&lt;br /&gt;# 將 setup.py 檔案中找到 JPEG_ROOT 然後改成 JPEG_ROOT = &quot;/usr/local/lib&quot;&lt;br /&gt;$ /usr/bin/python2.5 setup.py build&lt;br /&gt;$ sudo /usr/bin/python2.5 setup.py install&lt;/code&gt;&lt;/blockquote&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;如果一切都沒有問題的話，那應該就沒什麼問題了。只是記住當你在啟動 &lt;code&gt;dev_appserver.py&lt;/code&gt; 時，要使用 &lt;code&gt;/usr/bin/python2.5&lt;/code&gt; 來啟動，而不要使用 &lt;code&gt;/usr/bin/python&lt;/code&gt; 以免用到 Python 2.6 版。&lt;br /&gt;&lt;br /&gt;若是使用了 &lt;b&gt;GoogleAppEngineLauncher.app&lt;/b&gt; 這個應用程式的話，可以在 Preferences... 中設定 Python 的路徑為 &lt;code&gt;/usr/bin/python2.5&lt;/code&gt;</content><link rel='replies' type='application/atom+xml' href='http://practicalappengine.blogspot.com/feeds/7805012231763050718/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://practicalappengine.blogspot.com/2010/01/mac-snow-leopard-google-app-engine.html#comment-form' title='6 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/7805012231763050718'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/7805012231763050718'/><link rel='alternate' type='text/html' href='http://practicalappengine.blogspot.com/2010/01/mac-snow-leopard-google-app-engine.html' title='[Mac] 在 Snow Leopard 上開發 Google App Engine'/><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4177643508104568252.post-5745980196476984578</id><published>2009-08-10T13:24:00.002+08:00</published><updated>2009-08-10T14:04:55.081+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="datastore"/><category scheme="http://www.blogger.com/atom/ns#" term="quota"/><title type='text'>減輕資料查詢、更新的負擔</title><content type='html'>在 Google App Engine 上開發應用程式，很多人會面臨到使用額度（Quota）的問題，所以開發者在將應用程式上線後，必須不斷觀察應用程式的存取狀況，以便隨時調整應用程式，避免某一項使用額度過份衝高。&lt;br /&gt;&lt;br /&gt;這篇文章要介紹的是－－儘可能地減少資料查詢、更新的動作，因為 Datastore API 的呼叫次數有限制，而且一個 request 的處理時間也有 30 秒的限制，如果在操作資料時沒有注意到一些細節，可能就會碰到問題。&lt;br /&gt;&lt;h4&gt;平行讀取、更新或刪除 data entities&lt;/h4&gt;&lt;br /&gt;在「&lt;a href=&quot;http://practicalappengine.blogspot.com/2009/07/blog-post.html&quot; target=&quot;_blank&quot;&gt;避免資料寫入衝突&lt;/a&gt;」這篇文章中，提到可以使用 App Engine 所提供的 &lt;code&gt;memcache&lt;/code&gt; API 來作資料快取，這樣也是一種減少呼叫 Datastore API 的策略。&lt;br /&gt;&lt;br /&gt;除此之外，善加利用每個 data entity 的 key 也可以達到減少呼叫 Datastore API 的目標。例如以下這段程式碼：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;...&lt;br /&gt;# 修改許多 data entity&lt;br /&gt;for product in products:&lt;br /&gt;    product.price = product.price * 1.1&lt;br /&gt;    product.put()&lt;br /&gt;...&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;在這個例子中，&lt;code&gt;products&lt;/code&gt; 中有多少個 entities，就會呼叫多少次 &lt;code&gt;put()&lt;/code&gt;，但若是將上述的程式碼修改為：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;...&lt;br /&gt;from google.appengine.ext import db&lt;br /&gt;# 修改許多 data entity&lt;br /&gt;for product in products:&lt;br /&gt;    product.price = product.price * 1.1&lt;br /&gt;db.put(products)&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;由於 &lt;code&gt;db.put()&lt;/code&gt; 函式支援 list 資料型態的參數，所以這樣的作法僅會算作一次 Datastore API 的呼叫，比起上述的方式大大節省了 Datastore API 的呼叫次數。&lt;br /&gt;&lt;br /&gt;讀取資料時也是以此類推，若原本的程式為：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;...&lt;br /&gt;products = []&lt;br /&gt;# keys 為一個 key list&lt;br /&gt;for key in keys:&lt;br /&gt;    products.append(db.get(key))&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;也可以改寫成：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;...&lt;br /&gt;from google.appengine.ext import db&lt;br /&gt;# keys 為一個 key list&lt;br /&gt;products = db.get(keys)&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;同理可證，若要同時刪除許多 entities，也可以使用 &lt;code&gt;db.delete&lt;/code&gt;。&lt;br /&gt;&lt;h4&gt;有效率地使用 GQL&lt;/h4&gt;&lt;br /&gt;另外，若是要使用 &lt;code&gt;GqlQuery&lt;/code&gt; 作資料查詢時，若是只需要取出 entities 的 key 值，在 GQL 查詢語句中僅需取出 &lt;code&gt;__key__&lt;/code&gt; 欄位即可：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;...&lt;br /&gt;from google.appengine.ext import db&lt;br /&gt;&lt;br /&gt;product_keys = db.GqlQuery(&#39;SELECT __key__ FROM Product WHERE title = :title&#39;, title=&#39;...&#39;)&lt;br /&gt;# 或是 product_keys = Product.gql(&#39;WHERE title = :title&#39;, title=&#39;...&#39;, keys_only=True)&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;如此一來，讀取的時間就會比讀取全部的欄位還要快許多（如果資料量夠多的話...）&lt;br /&gt;&lt;br /&gt;另外，若是查詢語句會重複使用，可以將該語句建立成一個 &lt;code&gt;GqlQuery&lt;/code&gt; 物件後，再利用 &lt;code&gt;bind()&lt;/code&gt; 方法重新利用該查詢語句。例如本來的程式碼可能是：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;...&lt;br /&gt;from google.appengine.ext import db&lt;br /&gt;&lt;br /&gt;conditions = [[&#39;x&#39;, &#39;y&#39;], [&#39;1&#39;, &#39;2&#39;], .....]&lt;br /&gt;for cond in conditions:&lt;br /&gt;    query = db.GqlQuery(&#39;SELECT * FROM Foo WHERE first = :first, second = :second&#39;, first=cond[0], second=cond[1])&lt;br /&gt;    ....&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;如此一來，迴圈每執行一次就會建立一個 &lt;code&gt;GqlQuery&lt;/code&gt; 物件，資料查詢會變得很沒有效率，如果碰到這樣的情況，程式碼應該改寫為：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;...&lt;br /&gt;from google.appengine.ext import db&lt;br /&gt;&lt;br /&gt;conditions = [[&#39;x&#39;, &#39;y&#39;], [&#39;1&#39;, &#39;2&#39;], .....]&lt;br /&gt;prepared_query = db.GqlQuery(&#39;SELECT * FROM Foo WHERE first = :first, second = :second&#39;)&lt;br /&gt;for cond in conditions:&lt;br /&gt;    query = prepared_query.bind(first=cond[0], second=cond[1])&lt;br /&gt;    ....&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;如此便能重複利用 &lt;code&gt;GqlQuery&lt;/code&gt; 這個物件了。</content><link rel='replies' type='application/atom+xml' href='http://practicalappengine.blogspot.com/feeds/5745980196476984578/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://practicalappengine.blogspot.com/2009/08/blog-post.html#comment-form' title='2 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/5745980196476984578'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/5745980196476984578'/><link rel='alternate' type='text/html' href='http://practicalappengine.blogspot.com/2009/08/blog-post.html' title='減輕資料查詢、更新的負擔'/><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4177643508104568252.post-8245550254649025006</id><published>2009-08-05T14:33:00.017+08:00</published><updated>2009-08-05T15:29:05.268+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="eclipse"/><category scheme="http://www.blogger.com/atom/ns#" term="ide"/><category scheme="http://www.blogger.com/atom/ns#" term="pydev"/><category scheme="http://www.blogger.com/atom/ns#" term="tutorial"/><title type='text'>使用 Eclipse + PyDev 開發 Google App Engine 專案</title><content type='html'>在 Windows 上開發應用程式，大多數的開發者會藉著使用 &lt;abbr title=&quot;Integrated Development Environment&quot;&gt;IDE&lt;/abbr&gt; 來輔助開發，簡化一些設定、啟動或測試等步驟。而在 Windows 上若要開發 Google App Engine 的應用程式，使用 &lt;a href=&quot;http://eclipse.org/&quot; target=&quot;_blank&quot;&gt;Eclipse&lt;/a&gt; 作為 IDE，並且搭配 &lt;a href=&quot;http://pydev.sourceforge.net/&quot; target=&quot;_blank&quot;&gt;PyDev&lt;/a&gt; 這個 Eclipse 的 plugin，會簡化許多開發的設定，尤其是最新的 PyDev 甚至還直接支援了 Google App Engine 專案呢！以下就為各位做個簡單的介紹。&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;安裝及設定&lt;/h4&gt;&lt;br /&gt;在開始之前，先確定您的 Windows 環境已經安裝了 &lt;a href=&quot;http://java.sun.com/javase/downloads/index.jsp&quot; target=&quot;_blank&quot;&gt;Java SDK (JDK)&lt;/a&gt; 以及 &lt;a href=&quot;http://python.org/download/releases/2.5.4/&quot;&gt;Python&lt;/a&gt; （目前 Google App Engine 僅支援 Python 2.5.x） 。Java 是為了執行 Eclipse，而 Python 當然就是為了用來執行 Google App Engine 的專案囉。&lt;br /&gt;&lt;br /&gt;首先，到 &lt;a href=&quot;http://eclipse.org/downloads/&quot;&gt;Eclipse 的官方網站&lt;/a&gt;下載 Eclipse Classic 3.5.0&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9lJaSRvZ4AjVzrjVxGs0lc39NRdeoJiXed3TrrbRZ2UiuuVFWj8IgiWh628ViMo4RBzIVibPmvSffhcdmk2YRHxOG9yfVhIFgKU1DLZv1eUDOPoZ_PONYV2jhyubRN1Yyf3Mfztyj_A/s1600-h/download_eclipse.png&quot;&gt;&lt;img style=&quot;margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 227px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9lJaSRvZ4AjVzrjVxGs0lc39NRdeoJiXed3TrrbRZ2UiuuVFWj8IgiWh628ViMo4RBzIVibPmvSffhcdmk2YRHxOG9yfVhIFgKU1DLZv1eUDOPoZ_PONYV2jhyubRN1Yyf3Mfztyj_A/s320/download_eclipse.png&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5366366403425934242&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;下載回來後，將 zip 檔案解壓縮，執行 &lt;b&gt;eclipse&lt;/b&gt; 目錄中的 &lt;code&gt;eclipse.exe&lt;/code&gt; 便可以開啟 Eclipse：&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEIJvMUuxiMq_p1J2t_sLi8EiiF_OjDmMtSzlyJfEyK8OEsCsk8jdzY3zNOFyLqsSC0JsQR99vF6EOFK29NO772bLRDCrhhZzc4q4sRHD3ApIoGiSZGzv5_Aa0VrEXHgRLKxNMKmbTug/s1600-h/eclipse_ide.png&quot;&gt;&lt;img style=&quot;margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 240px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEIJvMUuxiMq_p1J2t_sLi8EiiF_OjDmMtSzlyJfEyK8OEsCsk8jdzY3zNOFyLqsSC0JsQR99vF6EOFK29NO772bLRDCrhhZzc4q4sRHD3ApIoGiSZGzv5_Aa0VrEXHgRLKxNMKmbTug/s320/eclipse_ide.png&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5366369260542778898&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;開啟 Eclipse 之後，首先將環境的設定作點修改，從功能表列的 &lt;b&gt;Window&lt;/b&gt; » &lt;b&gt;Preferences&lt;/b&gt; 進入設定畫面，首先設定用空白取代 tab 字元，因為 Python 對於程式碼的縮排有嚴格的一致性，所以為了避免不必要的煩惱，在 &lt;b&gt;General&lt;/b&gt; » &lt;b&gt;Editor&lt;/b&gt; » &lt;b&gt;Text Editors&lt;/b&gt; 的設定中，&lt;em&gt;將 tab 取代為 4 個空白字元&lt;/em&gt;。&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKsgJEfjvJ5g1wy8yPKTP6e2CSyBOaxAXwLjidX-MvMLeW5s8shEYBL9FLEcD6J4hPFgIYeflD8659PPmcLKUXMBl4AcjlIboHZbS1FI-QYARDMVgiV30isqrFaD4Hs8vfvb7UiPk4jw/s1600-h/s2t.png&quot;&gt;&lt;img style=&quot;margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 155px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKsgJEfjvJ5g1wy8yPKTP6e2CSyBOaxAXwLjidX-MvMLeW5s8shEYBL9FLEcD6J4hPFgIYeflD8659PPmcLKUXMBl4AcjlIboHZbS1FI-QYARDMVgiV30isqrFaD4Hs8vfvb7UiPk4jw/s320/s2t.png&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5366370450176702834&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;除此之外，也將由 Eclipse 所建立的專案，調整成&lt;em&gt;預設使用 UTF-8 作為字元編碼&lt;/em&gt;，以及&lt;em&gt;使用 UNIX 換行字元&lt;/em&gt;：&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXPSLYhQ9PRBKJ9XnnvwLtZYoa0py7v2VEt5EGsy56FZpmwMVipZTPo0q_-eLzNGAukUssrGZoLuth4WUmdBAjdfoZ0eqVZVMIcLB6aQaOPaK1d1ulo0D1cvZxDfhPK-UwA6kjC60LBQ/s1600-h/charset_and_line.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 74px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXPSLYhQ9PRBKJ9XnnvwLtZYoa0py7v2VEt5EGsy56FZpmwMVipZTPo0q_-eLzNGAukUssrGZoLuth4WUmdBAjdfoZ0eqVZVMIcLB6aQaOPaK1d1ulo0D1cvZxDfhPK-UwA6kjC60LBQ/s320/charset_and_line.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5366371141030042450&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;為了讓 Eclipse 能夠作為開發 Python 專案的 IDE，此時還需要安裝 PyDev 這個 plugin，可以從&lt;a href=&quot;http://pydev.sourceforge.net/download.html&quot; target=&quot;_blank&quot;&gt;PyDev 的官方網站&lt;/a&gt;上看到安裝 URL（如：http://nightly.aptana.com/pydev/site.xml），將這個 URL 複製下來，回到 Eclipse，到功能表列的 &lt;b&gt;Help&lt;/b&gt; &amp;raquo; &lt;b&gt;Install New Software...&lt;/b&gt;，將剛才複製的 URL 貼在 &lt;em&gt;Work with:&lt;/em&gt; 的文字框中，並按下 &lt;b&gt;Add&lt;/b&gt; 按鈕：&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTQlpnUTKsoWiInTlIt25IPN0hYlmOrGlSDZyQU4M0aFjWzmcr0Z46IVCPp78BDlQYcgyWy2vjg1-VyEFRT3Qi1oxZUIZ6PGA2sUTzC-nHI4W3kMge3jxeG1FwTynEcngUfwn5EuLY7w/s1600-h/Add+PyDev+Repo.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 297px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTQlpnUTKsoWiInTlIt25IPN0hYlmOrGlSDZyQU4M0aFjWzmcr0Z46IVCPp78BDlQYcgyWy2vjg1-VyEFRT3Qi1oxZUIZ6PGA2sUTzC-nHI4W3kMge3jxeG1FwTynEcngUfwn5EuLY7w/s320/Add+PyDev+Repo.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5366372663060495858&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;接著就是勾選 PyDev 然後將它安裝完成，安裝完畢後，Eclipse 便會請你重新啟動或是套用變更將 plugin 完成整合到 Eclipse 中。&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIuXT0HSTmBth0rUOEdr1ChiILOzNparxsdJmRCuVvBvW7eMphv3natZGp8mOAXjQXxBMGXNtGMdmZPT_LsOQqTE6hj-pMgo95VDPhxf4EVHQb7eZFkEssu2fPY-dc7goFdvZTk4dZ0Q/s1600-h/Install+PyDev.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 297px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIuXT0HSTmBth0rUOEdr1ChiILOzNparxsdJmRCuVvBvW7eMphv3natZGp8mOAXjQXxBMGXNtGMdmZPT_LsOQqTE6hj-pMgo95VDPhxf4EVHQb7eZFkEssu2fPY-dc7goFdvZTk4dZ0Q/s320/Install+PyDev.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5366373099256136610&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;安裝完 PyDev 之後，別忘了先設定 PyDev，讓它瞭解 Python 被安裝在哪裡：&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0VlGXiuAiUtGqvK6gQcpQTnBCV0Sy0d_JIfIUXU3CN1gCJZUFaTNGrWyBJ1pm2hONPDdcoN3Lyg4iExCjbRypGQWYupLTKo5_Qh_p810kSlrEHemWvihHaMZ0zyTS2amigdxtYXz5Lg/s1600-h/setup+pydev.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 275px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0VlGXiuAiUtGqvK6gQcpQTnBCV0Sy0d_JIfIUXU3CN1gCJZUFaTNGrWyBJ1pm2hONPDdcoN3Lyg4iExCjbRypGQWYupLTKo5_Qh_p810kSlrEHemWvihHaMZ0zyTS2amigdxtYXz5Lg/s320/setup+pydev.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5366375615471123442&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;建立 Google App Engine 專案&lt;/h4&gt;&lt;br /&gt;設定完 Python 之後，建立新專案時，就有 &lt;b&gt;PyDev Google App Engine Project&lt;/b&gt; 可以選擇了：&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvGlHPzU2sDcrgQF21scy-dWZcmlNmPP2koawxXRkbFRDZ9N5iXqASKDgBp_G2uocIXTT-W9D2wEbEbAg40xr8Gj21GKZN0mKzPS-_uwF8ggcZPjI4PC4QxOe2XDNIltS2yxGJ-TZpnQ/s1600-h/pydev_gae.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 318px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvGlHPzU2sDcrgQF21scy-dWZcmlNmPP2koawxXRkbFRDZ9N5iXqASKDgBp_G2uocIXTT-W9D2wEbEbAg40xr8Gj21GKZN0mKzPS-_uwF8ggcZPjI4PC4QxOe2XDNIltS2yxGJ-TZpnQ/s320/pydev_gae.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5366374888732838450&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;輸入專案名稱，以及別忘了選擇正確的 Python 版本：&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6E0M96r99DOnLNnmU7tzVHryayHHIwktAZyyxDTV1z3a_Qs3B2iWCnzfCCvF-fuA6dcVdakFqfcGVqK2WSYG1YdzpNUCU8xm5BzOaxMpRhVMw20slVNd5bvNtbj_OTEKGFt4d9_-9tA/s1600-h/create+gae+proj.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 302px; height: 320px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6E0M96r99DOnLNnmU7tzVHryayHHIwktAZyyxDTV1z3a_Qs3B2iWCnzfCCvF-fuA6dcVdakFqfcGVqK2WSYG1YdzpNUCU8xm5BzOaxMpRhVMw20slVNd5bvNtbj_OTEKGFt4d9_-9tA/s320/create+gae+proj.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5366376081032614898&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;接著選擇 Google App Engine 的安裝位置：&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQ1k-ReBmYwFymNYZprT9VMbeq_TD75VhFEerMycEgn2hKlcLNAoQ9qtzj2-8FrYY_YrsZOSzbenNHws5EZml8nYdltikTO01wOrgcnRVDakD48HVZFozbZCH8gnxKgbBGhrEHfE3tuw/s1600-h/setup+gae.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 302px; height: 320px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQ1k-ReBmYwFymNYZprT9VMbeq_TD75VhFEerMycEgn2hKlcLNAoQ9qtzj2-8FrYY_YrsZOSzbenNHws5EZml8nYdltikTO01wOrgcnRVDakD48HVZFozbZCH8gnxKgbBGhrEHfE3tuw/s320/setup+gae.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5366376488646977650&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;最後就是填入你的 application ID 及專案的範本：&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCorvRfUzNL8bu9-zLAWfeQYd81jQ7X-7IoG_pZeApjvQHj2_r5jvX872AE2qdXLkokmJU2fo79a9tw9GDPtinaD10oezN1ua8Xn3uvz_JIsctpsEwwNabnGSnxsa3Vx5ErekBXTmOYQ/s1600-h/app+id+and+template.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 302px; height: 320px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCorvRfUzNL8bu9-zLAWfeQYd81jQ7X-7IoG_pZeApjvQHj2_r5jvX872AE2qdXLkokmJU2fo79a9tw9GDPtinaD10oezN1ua8Xn3uvz_JIsctpsEwwNabnGSnxsa3Vx5ErekBXTmOYQ/s320/app+id+and+template.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5366376992278621938&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;如此一來專案就建立完成了。&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;執行及部署&lt;/h4&gt;&lt;br /&gt;當你的程式寫完，想要啟動開發用伺服器來作測試時，在專案視窗中的 &lt;b&gt;src&lt;/b&gt; 目錄上按下右鍵，選擇 &lt;b&gt;Run as...&lt;/b&gt; 就有 &lt;b&gt;Run: Google App&lt;/b&gt; 可以選擇了：&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7lVvHLdpVqgUr9aFompRZludz3XrrLOkrC0K-poeRH3vtXwQPXdXDGfQq4RrqO670r2c1I5Lf-q9vg2emRqOcenG0JC3a7nH9CkXK9ojbDoYvZwiIOrGBeTM7RSw9Jekvwvhm9NDfmA/s1600-h/run+gae.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 254px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7lVvHLdpVqgUr9aFompRZludz3XrrLOkrC0K-poeRH3vtXwQPXdXDGfQq4RrqO670r2c1I5Lf-q9vg2emRqOcenG0JC3a7nH9CkXK9ojbDoYvZwiIOrGBeTM7RSw9Jekvwvhm9NDfmA/s320/run+gae.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5366377719286226546&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;如果要將應用程式部署至 Google App Engine 上，則一樣在 &lt;b&gt;src&lt;/b&gt; 目錄上按下右鍵，選擇 &lt;b&gt;PyDev: Google App Engine&lt;/b&gt; 就有 &lt;b&gt;Upload&lt;/b&gt; 及 &lt;b&gt;Manage&lt;/b&gt; 可以使用。&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqNHPx5pXvV7dmwFIb6aIYzTc_PUqu-dXuAbPnlJY3GnMlod3qigqZ3NjWGcMjMfRJw0zPXGuqD_4IWbw165bguAkqyRy2ye35S9iWZYMpcZ1HsqdLamJl2oYRw4DXzoFtBSGol6WIcA/s1600-h/deploy+gae.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 278px; height: 320px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqNHPx5pXvV7dmwFIb6aIYzTc_PUqu-dXuAbPnlJY3GnMlod3qigqZ3NjWGcMjMfRJw0zPXGuqD_4IWbw165bguAkqyRy2ye35S9iWZYMpcZ1HsqdLamJl2oYRw4DXzoFtBSGol6WIcA/s320/deploy+gae.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5366378286186996834&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;這樣是不是簡單多了呢？祝各位開發愉快 :-)</content><link rel='replies' type='application/atom+xml' href='http://practicalappengine.blogspot.com/feeds/8245550254649025006/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://practicalappengine.blogspot.com/2009/08/eclipse-pydev-google-app-engine.html#comment-form' title='6 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/8245550254649025006'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/8245550254649025006'/><link rel='alternate' type='text/html' href='http://practicalappengine.blogspot.com/2009/08/eclipse-pydev-google-app-engine.html' title='使用 Eclipse + PyDev 開發 Google App Engine 專案'/><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9lJaSRvZ4AjVzrjVxGs0lc39NRdeoJiXed3TrrbRZ2UiuuVFWj8IgiWh628ViMo4RBzIVibPmvSffhcdmk2YRHxOG9yfVhIFgKU1DLZv1eUDOPoZ_PONYV2jhyubRN1Yyf3Mfztyj_A/s72-c/download_eclipse.png" height="72" width="72"/><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4177643508104568252.post-4292364081606642444</id><published>2009-07-22T23:57:00.000+08:00</published><updated>2009-07-22T23:57:24.447+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="datastore"/><category scheme="http://www.blogger.com/atom/ns#" term="scale"/><title type='text'>避免資料寫入衝突</title><content type='html'>在使用 App Engine 時，常常會建立 model 來作 sequence（如：手動配置資料的 id）、counter（計數器）之類的工作，以前面文章提到的 counter 為例，如果 &lt;code&gt;Counter&lt;/code&gt; 在 datastore 中的 data entity 只有一個，每次新增資料時，程式都會到同一個地方作資料寫入（update 也是一種 write）的動作，如果新增資料的頻率很高，那就很容易發生寫入衝突（write contention）而使得程式的效能低落。&lt;br /&gt;&lt;br /&gt;試想你在作一個類似 &lt;a href=&quot;http://twitter.com/&quot; target=&quot;_blank&quot;&gt;Twitter&lt;/a&gt; 的網站，使用者增加訊息的速度是非常驚人的，如果只使用單一個 data entity 來儲存、更新計數器，那每則訊息的更新時間就會變長，因為都在等待更新計數器的動作。如果你曾經學習過用在磁碟機上的 &lt;a href=&quot;http://en.wikipedia.org/wiki/RAID&quot; target=&quot;_blank&quot;&gt;&lt;acronym title=&quot;Redundant Array of Inexpensive Disks&quot;&gt;RAID&lt;/acronym&gt;&lt;/a&gt; 技術，那便可以借鏡 RAID 5 規格中分散資料擺放的方式，使得各個讀寫資料的動作可以同時進行。&lt;br /&gt;&lt;br /&gt;以計數器為例，可以試著把計數器拆開成好幾個碎片（shard），在更新計數器時，可以只要隨便選一個碎片來更新就可以了，如果要取得完整的數值，只要把各碎片的值加總就得到答案了。&lt;br /&gt;&lt;br /&gt;這樣的概念，程式要怎麼寫呢？首先把 &lt;code&gt;Counter&lt;/code&gt; 的定義修改成這樣：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;from google.appengine.ext import db&lt;br /&gt;&lt;br /&gt;class Counter(db.Model):&lt;br /&gt;    name = db.StringProperty(required=True)&lt;br /&gt;    count = db.IntegerProperty(required=True, default=0)&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;增加了一個 &lt;code&gt;name&lt;/code&gt; 欄位，這是為了標記碎片是屬於哪一個計數器，也就是 &lt;code&gt;name&lt;/code&gt; 欄位相同的碎片就是屬於同一個計數器。有了這樣的 model 之後，更新計數器的程式碼可以改寫成這樣：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;...&lt;br /&gt;import random&lt;br /&gt;&lt;br /&gt;shard_name = &#39;product_counter_%d&#39; % random.randint(0, 3) # 假設我分成4個碎片&lt;br /&gt;counter = Counter.get_by_key_name(shard_name)&lt;br /&gt;if counter is None:&lt;br /&gt;    counter = Counter(key_name=shard_name, name=&#39;product_counter&#39;)&lt;br /&gt;counter.count += 1&lt;br /&gt;counter.put()&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;上面的例子是我預計將計數器切成 4 個碎片，然後每個「碎片 entity」的 &lt;code&gt;key_name&lt;/code&gt; 分別為 &lt;code&gt;product_counter_0&lt;/code&gt;, ..., &lt;code&gt;product_counter_3&lt;/code&gt;，於是便能夠直接根據 &lt;code&gt;key_name&lt;/code&gt; 將碎片的 entity 取出，若是碎片不存在就立刻產生一個新的 entity，而新的 entity 除了要設定 &lt;code&gt;key_name&lt;/code&gt; 之外，最重要的就是要記得標上 &lt;code&gt;name&lt;/code&gt; 欄位的值，因為當我們需要加總計數器時，就是靠著這個欄位來取出各個碎片。最後則是將 &lt;code&gt;count&lt;/code&gt; 欄位的值加 1 後儲存，完成更新計數器的動作。&lt;br /&gt;&lt;br /&gt;接下來要處理的就是加總的動作囉，因為可以藉著 &lt;code&gt;name&lt;/code&gt; 欄位來查詢，所以程式也變得很簡單：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;...&lt;br /&gt;counters = Counter.all().filter(&#39;name =&#39;, &#39;product_counter&#39;)&lt;br /&gt;total = 0&lt;br /&gt;for counter in counters:&lt;br /&gt;    total += counter.count&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;所以 &lt;code&gt;total&lt;/code&gt; 的值就是計數器正確的數值了。&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;改善效能&lt;/h4&gt;&lt;br /&gt;上面的例子雖然改善了寫入衝突的問題，但光是這樣作，除了每次讀取計數器數值時都要做一次查詢（耗費 Datastore API 呼叫次數）以外，碎片如果太多也會增加計算時間，為了改善這個問題，可以為計數器作 cache。&lt;br /&gt;&lt;br /&gt;Google App Engine 有提供 Memcache 的服務，我們可以直接拿它來作為計數器的 cache，首先從計數器的加總下手，一旦數值被計算出來後就放進 cache，如此一來，讀取計數器數值時就可以先從 cache 讀取，不在 cache 裡才去作資料查詢：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;# 讀取計數器總數時，使用 cache&lt;br /&gt;from google.appengine.api import memcache&lt;br /&gt;&lt;br /&gt;counter_name = &#39;product_counter&#39;&lt;br /&gt;total = memcache.get(counter_name) # 這裡用 counter_name 作為 cache key&lt;br /&gt;if total is None:&lt;br /&gt;    total = 0&lt;br /&gt;    counters = Counter.all().filter(&#39;name =&#39;, counter_name)&lt;br /&gt;    for counter in counters:&lt;br /&gt;        total += counter.count&lt;br /&gt;    memcache.set(counter_name, total)&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;那當計數器被更新的時候怎麼辦？除了更新 entity 之外，若是計數器的 cache 還存在，可以直接把 cache 裡的值加 1，這樣計數器的總數就不必重新計算了：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;# 更新計數器時，別忘了更新 cache&lt;br /&gt;from google.appengine.api import memcache&lt;br /&gt;import random&lt;br /&gt;&lt;br /&gt;counter_name = &#39;product_counter&#39;&lt;br /&gt;shard_name = &#39;product_counter_%d&#39; % random.randint(0, 3) # 假設我分成4個碎片&lt;br /&gt;counter = Counter.get_by_key_name(shard_name)&lt;br /&gt;if counter is None:&lt;br /&gt;    counter = Counter(key_name=shard_name, name=counter_name)&lt;br /&gt;counter.count += 1&lt;br /&gt;counter.put()&lt;br /&gt;&lt;br /&gt;# 更新 cache&lt;br /&gt;total = memcache.get(counter_name)&lt;br /&gt;if total:&lt;br /&gt;    memcache.incr(counter_name) # 使用 incr 函式直接對數值資料加 1 &lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;這樣就不必擔心更新了計數器，卻忘記更新快取了。</content><link rel='replies' type='application/atom+xml' href='http://practicalappengine.blogspot.com/feeds/4292364081606642444/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://practicalappengine.blogspot.com/2009/07/blog-post.html#comment-form' title='2 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/4292364081606642444'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/4292364081606642444'/><link rel='alternate' type='text/html' href='http://practicalappengine.blogspot.com/2009/07/blog-post.html' title='避免資料寫入衝突'/><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4177643508104568252.post-6232289791857596004</id><published>2009-07-06T13:30:00.000+08:00</published><updated>2009-07-06T14:03:30.424+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="counter"/><category scheme="http://www.blogger.com/atom/ns#" term="datastore"/><category scheme="http://www.blogger.com/atom/ns#" term="entity group"/><category scheme="http://www.blogger.com/atom/ns#" term="transaction"/><title type='text'>支援交易運算的計數器</title><content type='html'>&lt;h4&gt;支援交易運算&lt;/h4&gt;&lt;br /&gt;在許多資料庫運算中，為了保持相關的資料庫運算結果能夠不被分割地將同時送進資料庫（也就是一旦有一個操作發生失敗，則整串操作都會同時取消），所以提供了「交易」（transaction）的運算。&lt;br /&gt;&lt;br /&gt;Google App Engine 上的 datastore 當然也支援「交易」運算，在上述的例子中，共有兩個資料庫運算－－&lt;code&gt;Product&lt;/code&gt; entity 的寫入以及 &lt;code&gt;Counter&lt;/code&gt; entity 的更新，試著想像這兩個操作若各自（或同時）發生錯誤時會有什麼問題，若是 &lt;code&gt;Product&lt;/code&gt; entity 在寫入時發生錯誤，程式就中斷了，那就沒什麼太大的問題，若是 &lt;code&gt;Product&lt;/code&gt; entity 成功寫入 datastore，但 &lt;code&gt;Counter&lt;/code&gt; entity 在更新時發生錯誤，這時就會造成計數器的統計數量不一致，這時候就適合使用交易運算了。&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;交易運算的限制&lt;/h4&gt;&lt;br /&gt;在 Google App Engine 中，交易運算只能操作相同 entity group 的資料，如果我們要將 &lt;code&gt;Product&lt;/code&gt; 及 &lt;code&gt;Counter&lt;/code&gt; 的新增動作在同一個交易運算中完成的話，就必須讓它們成為同一個 entity group。&lt;br /&gt;&lt;br /&gt;要成為同一個 entity group，不管是 &lt;code&gt;Product&lt;/code&gt; 還是 &lt;code&gt;Counter&lt;/code&gt; 都需要一個共同的 parent，因此需要一個額外的 data model 來作為這個 entity group 的 root，這裡我提供一個 &lt;code&gt;Index&lt;/code&gt; 的 model，順便用來作為每一筆 &lt;code&gt;Product&lt;/code&gt; 資料的「序號產生器」 :p&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;class Index(db.Model):&lt;br /&gt;    max_index = db.IntegerProperty(required=True, default=0)&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;有了這個 model 後，新增 &lt;code&gt;Product&lt;/code&gt; 及更新 &lt;code&gt;Counter&lt;/code&gt; 的動作就可以改寫為：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;# 新增 product 的程式片段...&lt;br /&gt;...&lt;br /&gt;# 從 Index 中取出 Product 的 index&lt;br /&gt;ind = Index.get_by_key_name(&#39;product&#39;)&lt;br /&gt;if ind is None:&lt;br /&gt;    ind = Index()&lt;br /&gt;ind.max_index += 1&lt;br /&gt;ind.put()&lt;br /&gt;&lt;br /&gt;# 新增 product entity 並設定 parent 為 ind&lt;br /&gt;p = Product(parent=ind, key_name=&quot;product_%d&quot; % ind.max_index,........) &lt;br /&gt;p.put()&lt;br /&gt;&lt;br /&gt;# 根據 key_name 取得 counter，並且指定 parent&lt;br /&gt;counter = Counter.get_by_key_name(&#39;product_counter&#39;, parent=ind.key())&lt;br /&gt;if counter is None:&lt;br /&gt;    # 如果 counter 不存在，則建立一個新的，別忘了指定 key_name 及 parent&lt;br /&gt;    counter = Counter(parent=ind, key_name=&#39;product_counter&#39;)&lt;br /&gt;&lt;br /&gt;counter.count += 1&lt;br /&gt;counter.put()&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;在新增 &lt;code&gt;Product&lt;/code&gt; 及 &lt;code&gt;Counter&lt;/code&gt; 時，都加上指定 &lt;code&gt;parent&lt;/code&gt; 的參數，以此將這些資料建構成一個 entity group，然而，一旦資料在 entity group 中，在取出時也要加上 &lt;code&gt;parent&lt;/code&gt; 參數。&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;作成交易運算的函式&lt;/h4&gt;&lt;br /&gt;因此，我們可以把上面的程式碼包成一個函式，比方說是 &lt;code&gt;create_product&lt;/code&gt;，這樣就可以利用 Datastore API 中的 &lt;code&gt;run_in_transaction&lt;/code&gt; 函式來作成交易運算了。&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;....&lt;br /&gt;def create_product():&lt;br /&gt;    # 放入上述的程式碼&lt;br /&gt;&lt;br /&gt;....&lt;br /&gt;# 新增資料時...&lt;br /&gt;from google.appengine.ext import db&lt;br /&gt;db.run_in_transaction(create_product)&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;如此一來，只有當 &lt;code&gt;Index&lt;/code&gt;, &lt;code&gt;Product&lt;/code&gt; 及 &lt;code&gt;Counter&lt;/code&gt; 的資料操作都成功時，更新的資料才會進入 datastore 中。</content><link rel='replies' type='application/atom+xml' href='http://practicalappengine.blogspot.com/feeds/6232289791857596004/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://practicalappengine.blogspot.com/2009/06/blog-post_4940.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/6232289791857596004'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/6232289791857596004'/><link rel='alternate' type='text/html' href='http://practicalappengine.blogspot.com/2009/06/blog-post_4940.html' title='支援交易運算的計數器'/><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4177643508104568252.post-8752955892674440834</id><published>2009-06-18T23:41:00.000+08:00</published><updated>2009-06-18T23:41:24.950+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="counter"/><category scheme="http://www.blogger.com/atom/ns#" term="datastore"/><title type='text'>資料計數器</title><content type='html'>&lt;h3&gt;Datastore API 的限制&lt;/h3&gt;&lt;br /&gt;在使用 Datastore API 時有個限制，就是每作一次 query ，最多能存取到的資料數量為 1000 筆（註1），也因為這個限制，所以也不能直接使用 &lt;code&gt;Query&lt;/code&gt; 物件下的 &lt;code&gt;count()&lt;/code&gt; 方法來計算資料數量。&lt;br /&gt;&lt;br /&gt;舉例來說，如果要設計一個網路商店，每一件商品是一個 &lt;code&gt;Product&lt;/code&gt; entity，假如網站中的商品數量超過 1000 筆，那這樣的程式碼：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;from google.appengine.ext import db&lt;br /&gt;&lt;br /&gt;class Product(db.Model):&lt;br /&gt;    ...&lt;br /&gt;    # product 各欄位&lt;br /&gt;&lt;br /&gt;...&lt;br /&gt;query = Product.all()&lt;br /&gt;number_of_products = query.count()&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;並不會幫你統計出究竟有多少 &lt;code&gt;Product&lt;/code&gt; entity，因為 &lt;code&gt;query&lt;/code&gt; 的結果最多就是 1000 筆資料。&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;手動記錄&lt;/h3&gt;&lt;br /&gt;為了解決這樣的問題，其實可以自己定義一個「計數器」的資料，用來統計究竟有多少資料，於是立刻可以寫出這樣的 data model:&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;from google.appengine.ext import db&lt;br /&gt;&lt;br /&gt;class Counter(db.Model):&lt;br /&gt;    count = db.IntegerProperty(required=True, default=0)&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;有了這樣的資料模型，便能夠用來統計網站中任何資料的數量。我們可以經由不同的 &lt;code&gt;key_name&lt;/code&gt; 來區分出不同的計數器，根據上述的例子，便可以在新增 &lt;code&gt;Product&lt;/code&gt; 時，在計數器上加1以便統計。&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;# 新增 product 的程式片段...&lt;br /&gt;...&lt;br /&gt;p = Product(........) # 新增 product entity&lt;br /&gt;p.put()&lt;br /&gt;&lt;br /&gt;# 根據 key_name 取得 counter&lt;br /&gt;counter = Counter.get_by_key_name(&#39;product_counter&#39;)&lt;br /&gt;if counter is None:&lt;br /&gt;    # 如果 counter 不存在，則建立一個新的，別忘了指定 key_name&lt;br /&gt;    counter = Counter(key_name=&#39;product_counter&#39;)&lt;br /&gt;&lt;br /&gt;counter.count += 1&lt;br /&gt;counter.put()&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;以此類推，當要刪除一個或多個 &lt;code&gt;Product&lt;/code&gt; entities 時，也要同步更新對應的計數器資料，以保持資料數量的一致性（consistency）。&lt;br /&gt;&lt;br /&gt;&lt;hr&gt;&lt;br /&gt;&lt;h6&gt;註&lt;/h6&gt;&lt;ol&gt;&lt;li&gt;之後會介紹如何讀出超過1000筆的資料&lt;/li&gt;&lt;/ol&gt;</content><link rel='replies' type='application/atom+xml' href='http://practicalappengine.blogspot.com/feeds/8752955892674440834/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://practicalappengine.blogspot.com/2009/06/blog-post_18.html#comment-form' title='2 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/8752955892674440834'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/8752955892674440834'/><link rel='alternate' type='text/html' href='http://practicalappengine.blogspot.com/2009/06/blog-post_18.html' title='資料計數器'/><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4177643508104568252.post-8510020425655330089</id><published>2009-06-18T17:10:00.003+08:00</published><updated>2009-06-18T17:17:33.251+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="datastore"/><category scheme="http://www.blogger.com/atom/ns#" term="property"/><title type='text'>屬性中的 indexed=False</title><content type='html'>如果你的程式中，有個 data model 的 property 很多，那資料在存取時會隨著 property 數量的成長而降低效能（尤其是發生在呼叫 &lt;code&gt;put()&lt;/code&gt; 來儲存資料時），這是因為在儲存或更新資料時，datastore 也會針對這些 property 作一些 index 的動作，使得整個操作的時間變長。&lt;br /&gt;&lt;br /&gt;如果確定不會用某個 property 來作資料查詢 filter 的條件，那麼可以在定義 property 時加上 &lt;code&gt;indexed=False&lt;/code&gt; 參數，提示 datastore 這個 property 不要做 index，範例程式如下：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;from google.appengine.ext import db&lt;br /&gt;&lt;br /&gt;class Foo(db.Model):&lt;br /&gt;    ....&lt;br /&gt;    bar = db.StringProperty(indexed=False)&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;</content><link rel='replies' type='application/atom+xml' href='http://practicalappengine.blogspot.com/feeds/8510020425655330089/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://practicalappengine.blogspot.com/2009/06/indexedfalse.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/8510020425655330089'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/8510020425655330089'/><link rel='alternate' type='text/html' href='http://practicalappengine.blogspot.com/2009/06/indexedfalse.html' title='屬性中的 indexed=False'/><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4177643508104568252.post-8707104635172801838</id><published>2009-06-15T11:40:00.008+08:00</published><updated>2009-06-15T20:22:00.101+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="recipe"/><category scheme="http://www.blogger.com/atom/ns#" term="tags"/><title type='text'>為儲存的內容加上標籤（簡單版）</title><content type='html'>現在很多網站都會為儲存的內容加上標籤，像 &lt;a href=&quot;http://flickr.com/&quot; target=&quot;_blank&quot;&gt;Flickr&lt;/a&gt; 的每一張相片都可以設定標籤：&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiy9uCnn-GVdYe6ePs5xmGSshGxzvBa1yOa0o-OYsU1b28Vf2l7L6LAuhrdV9ZKa6QKfU2DQNlH-FW5wGU5sWPUinYTwGEKWrkz9hkuI1t59XiyX5pss9jBPen6Ym0vo7GmhAa-7PxVzQ/s1600-h/flickr_tags.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 144px; height: 70px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiy9uCnn-GVdYe6ePs5xmGSshGxzvBa1yOa0o-OYsU1b28Vf2l7L6LAuhrdV9ZKa6QKfU2DQNlH-FW5wGU5sWPUinYTwGEKWrkz9hkuI1t59XiyX5pss9jBPen6Ym0vo7GmhAa-7PxVzQ/s400/flickr_tags.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5347395204629268082&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;或是像 delicious 的書籤也可以設定標籤：&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5er5OK8Px5ZKLMnkq3WIj-6MIzU4GoNWEaxhT9rxldHbK6lZbiqMKm_M_lvkn079HFjS0fRQ-Ps0zGFkwvk-HSl_ByZPAWlgtsY7XmO6mHHzI2QMni_OgT6x0eBvl3JdGl33-Ii1mvw/s1600-h/delicious_tags.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 43px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5er5OK8Px5ZKLMnkq3WIj-6MIzU4GoNWEaxhT9rxldHbK6lZbiqMKm_M_lvkn079HFjS0fRQ-Ps0zGFkwvk-HSl_ByZPAWlgtsY7XmO6mHHzI2QMni_OgT6x0eBvl3JdGl33-Ii1mvw/s400/delicious_tags.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5347396090760697746&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;在 App Engine 上開發應用程式，如果要為儲存的內容加上標籤的支援，那麼在資料模型（Data Model）的定義時，可以加上一個 property：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;from google.appengine.ext import db&lt;br /&gt;class MyContent(db.Model):&lt;br /&gt;    ....&lt;br /&gt;    tags = db.StringListProperty()&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;如此一來，&lt;code&gt;tags&lt;/code&gt;就可以儲存&lt;b&gt;由字串所組成的 list&lt;/b&gt;資料，也就是可以儲存 MyContent 物件的標籤。&lt;br /&gt;&lt;br /&gt;假設使用者透過表單送出標籤的資料，而標籤的資料是一個&lt;em&gt;以逗號（,）隔開的字串&lt;/em&gt;，那麼要儲存標籤的作法就是：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;....&lt;br /&gt;tags_string = self.request.get(&#39;tags&#39;)&lt;br /&gt;content = MyContent()&lt;br /&gt;....&lt;br /&gt;content.tags = map(lambda x: x.strip(), tags_string.split(&#39;,&#39;))&lt;br /&gt;content.put()&lt;br /&gt;....&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;雖然 &lt;code&gt;tags_string.split(&#39;,&#39;)&lt;/code&gt; 已經產生一個由字串所組成的 list 了，但是使用者輸入的字串，可能會在逗號的前後留下空白，所以用了 &lt;code&gt;map&lt;/code&gt; 函數將 list 中的每個元素空白消掉（&lt;code&gt;strip&lt;/code&gt; 函數）&lt;br /&gt;&lt;br /&gt;如果要取出含有某個標籤值（如：&lt;code&gt;food&lt;/code&gt;）的內容，則可以直接使用 GQL 中特殊的語法來取出：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;....&lt;br /&gt;query = MyContent.gql(&#39;WHERE tags = :1&#39;, &#39;food&#39;)&lt;br /&gt;for content in query:&lt;br /&gt;    ....&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;因為 GQL 的設計，雖然 &lt;code&gt;tags&lt;/code&gt; 欄位是一個 list，但是只要比對的元素有出現在 list 中，則 &lt;code&gt;=&lt;/code&gt; 運算的結果就會是 &lt;code&gt;True&lt;/code&gt;，所以就能夠輕易地根據 list 中的元素來查詢資料。</content><link rel='replies' type='application/atom+xml' href='http://practicalappengine.blogspot.com/feeds/8707104635172801838/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://practicalappengine.blogspot.com/2009/06/blog-post_15.html#comment-form' title='2 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/8707104635172801838'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/8707104635172801838'/><link rel='alternate' type='text/html' href='http://practicalappengine.blogspot.com/2009/06/blog-post_15.html' title='為儲存的內容加上標籤（簡單版）'/><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiy9uCnn-GVdYe6ePs5xmGSshGxzvBa1yOa0o-OYsU1b28Vf2l7L6LAuhrdV9ZKa6QKfU2DQNlH-FW5wGU5sWPUinYTwGEKWrkz9hkuI1t59XiyX5pss9jBPen6Ym0vo7GmhAa-7PxVzQ/s72-c/flickr_tags.png" height="72" width="72"/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4177643508104568252.post-4566863610077175011</id><published>2009-06-15T11:33:00.002+08:00</published><updated>2009-06-15T11:36:23.291+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="readme"/><title type='text'>本部落格的宗旨</title><content type='html'>筆者因為深感 Google App Engine 的中文討論資源不足，所以打算把自己的研究整理成專門的 blog 來發表，順便與同好們互相討論學習。&lt;br /&gt;&lt;br /&gt;未來這個部落格將會不定期更新一些在 Google App Engine 上撰寫程式的技巧及範例，主要會以 Python 版本為主，若有筆誤或是觀念錯誤的部份，還請各位讀者不吝賜教。</content><link rel='replies' type='application/atom+xml' href='http://practicalappengine.blogspot.com/feeds/4566863610077175011/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://practicalappengine.blogspot.com/2009/06/blog-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/4566863610077175011'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4177643508104568252/posts/default/4566863610077175011'/><link rel='alternate' type='text/html' href='http://practicalappengine.blogspot.com/2009/06/blog-post.html' title='本部落格的宗旨'/><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg'/></author><thr:total>0</thr:total></entry></feed>