Python 2.7 への移行

このページでは、Python アプリケーションを Python 2.7 ランタイムにアップグレードするのに必要な手順について説明します。この手順をひととおり完了すると、Python 2.7 ランタイムの数多くの新機能(マルチスレッド処理Jinja2 テンプレート エンジン、バイトコードのアクセスとアップロードなど)と、いくつかの新しい付属のサードパーティ ライブラリを利用できるようになります。

前提条件と注意事項

Python 2.7 を使用するには、アプリケーションが次の要件を満たしている必要があります。

  • アプリケーションで Django を使う場合、バージョン 1.2 以降を使う必要があるアップグレードの詳細については、Django のドキュメントをご覧ください。
  • リクエストを同時処理する場合には、WSGI の使用で説明されているウェブ サーバー ゲートウェイ インターフェース(WSGI)スクリプト ハンドラをアプリケーションで使用する必要があります。

これらの一般的な前提条件を満たす以外に、いくつかの App Engine 機能やサードパーティ ライブラリを使用する必要があります。アプリケーションにインポートするバージョンを更新して、アップグレード後にアプリケーションを十分にテストしてください。以下のリストでは、互換性に関する主な問題や、それらの問題の解決に役立つその他のリソースを示しています。

  • パフォーマンス: Python 2.7 へのアップグレード後にアプリケーションのパフォーマンスが変化することがあります。レスポンスのレイテンシが増えた場合は、フロントエンド インスタンス クラスを増やして同時リクエストを有効にできます。同時リクエストを使うと、より大きなインスタンスで負荷を減らしながらアプリケーションのパフォーマンスを高速にすることができます。詳しくは、インスタンスでのリクエストの処理をご覧ください。
  • Django: Django 1.2 以降を Python 2.7 と一緒に使う必要があります。アップグレードについて詳しくは、Django リリースノートをご覧ください。
  • PyCrypto: Crypto.Util.randpool は非推奨になりました。Crypto.Random を使うことをおすすめします。詳細については、RandomPool の対処方法をご覧ください。
  • webapp: WebApp テンプレートは Python 2.7 では非推奨です。代わりに、Django テンプレートを直接使用するか、jinja2、または別のテンプレート エンジンを使用できます。
  • WebOb: Python 2.7 では WebOb バージョン 1.1 がサポートされます。このバージョンには、古いバージョン(0.9)との完全な下位互換性がありません。アプリケーションで WebOb を使用する場合は、アップグレードによって発生したエラーを把握するために、WebOb を広範囲にテストする必要があります。
  • zipimport: Python 2.7 では zipimport がサポートされませんが、Python 2.7 では .zip ファイルからのネイティブ インポートができます。
  • simplejson: Python 2.7 では simplejson がサポートされませんが、Python 2.7 には代わりにさらに高速な標準ライブラリ json モジュールが含まれています。

同時リクエストと WSGI

アプリケーションの設計やパフォーマンスに最も影響を与える Python 2.7 の機能は、同時リクエストを処理できるマルチスレッド アプリケーションのサポートです。同時リクエストを処理できる機能によって CPU 使用率が向上し、アプリケーションのパフォーマンスを大幅に強化できます。特に、複数の CPU コアを生かした、より上位のインスタンス クラスを利用するアプリケーションではこの効果が顕著になります。

アプリケーションでマルチスレッド処理を有効にするためには、従来の Python ランタイムを使った CGI(Common Gateway Interface)に基づくアプローチから脱却して、WSGI(Web Server Gateway Interface)に基づくアプローチに移行する必要があります。これは、CGI スクリプトはリクエストを順番に処理するように設計されていて、入出力ストリームへのアクセスに環境変数を利用しているからです。

これに対して、WSGI のリクエスト処理モデルでは、アプリケーションからもっと直接的に入出力ストリームにアクセスできます(同時要求が可能になります)。ただし、複数のリクエストが並列に処理される結果、リクエスト ハンドラのロジックでローカルよりも広いスコープを持つデータ(アプリケーションの状態など)を利用したり、そうしたデータをやり取りしたりするときに、競合状態になることがあります。そのため、新しい WSGI アプリケーションが必ずスレッドセーフとなるように、競合状態を防止するようなコードを記述することが重要になります。

詳しくは、アプリケーションのスレッドセーフ化をご覧ください。

app.yaml の更新

Python 2.7 では、app.yaml のヘッダーに特別な runtime 構成要素が必要になります。Python 2.7 アプリケーションには、threadsafe: [ true | false ] 要素が必要なことにご注意ください。true の場合、App Engine はリクエストを同時に送信します。false の場合、App Engine はリクエストを順次送信します。次の app.yaml ヘッダーでは同時リクエストが有効です:

application: myapp
version: 1
runtime: python27
api_version: 1
threadsafe: true
...

WSGI の使用

Python 2.7 ランタイムでは、run_wsgi_app アダプタを使ってプログラムを CGI スクリプトとして実行する代わりに、WSGI(ウェブ サーバー ゲートウェイ インターフェース)アプリケーションを直接実行することできます。それには、app.yaml 内の CGI ハンドラ(myapp.py など)を WSGI アプリケーション名(myapp.app など)に置き換えます。

...
handlers:
- url: /.*
  script: myapp.app
...

また、WSGI アプリケーション オブジェクトをグローバル スコープに移動する必要もあります。

import webapp2

class MainPage(webapp2.RequestHandler):
  def get(self):
    self.response.headers['Content-Type'] = 'text/plain'
    self.response.out.write('Hello, WebApp World!')

app = webapp2.WSGIApplication([('/', MainPage)])

""" Old code:
def main():
  run_wsgi_app(app)

if __name__ == '__main__':
  main()
"""

app.yaml で指定することもできますが、CGI スクリプトによって処理されるリクエストは順番に処理され、同時には処理されません。また、app.yaml ファイルに CGI スクリプトと WSGI アプリケーションを混在させることはできず、CGI ハンドラを定義する場合は threadsafetrue に設定することもできません。

main() の使用や __name__ == 'main' のチェックなどの、従来の Python ランタイムの規則は一部非推奨になりました。これらの手段は過去の CGI スクリプトをキャッシュに保存しておくのに役立ちますが、現在では WSGI アプリケーションを直接実行しているため、こうした手順は不要になりました。

アプリ ルート ディレクトリの使用

Python 2.5 では、スクリプトが含まれるディレクトリに設定されている現在の作業ディレクトリで CGI スクリプトを実行していました。これは Python 2.7 で変更されました。WSGI では、リクエスト ハンドラの有効期間の始めにある現在の作業ディレクトリがアプリケーションのルート ディレクトリです。

アプリケーション コードを確認し、現在の作業ディレクトリがアプリケーション ルートであるとみなされるようにすべてのハンドラが記述されていることを確認します。

ライブラリの構成

Python 2.7 ランタイムには、サードパーティ モジュールがいくつか含まれます。これらの一部はデフォルトで使用できますが、それ以外は使用前に構成が必要です。使用するバージョンも指定できます。

libraries:
- name: PIL
  version: "1.1.7"
- name: webob
  version: "1.1.1"

アプリケーションが最新バージョンのモジュールを使用するよう指定できます。これは、まだユーザーがいないアプリケーションを開発している場合に便利です。これにより、新しいバージョンを追跡する必要がないからです。ただし、アプリケーションを実際に使用している場合、下位互換性のない新しいライブラリ バージョンがアプリケーションで使用される可能性があります。 最新バージョンを使用するには、次のように指定します。

libraries:
- name: PIL
  version: latest

サポートされているライブラリのリストについては、サードパーティ ライブラリをご覧ください。

アプリケーションのスレッドセーフ化

同時要求の処理は、各ハンドラが自身のスコープ内の変数としかやり取りしない場合には簡単になります。しかし、あるハンドラが読み取っているリソースを別のハンドラから変更しようとする場合はたちまち複雑になります。複数の要求から同じデータを操作しようとする場合、互いに干渉し合う可能性がありますが、そうした場合にもアプリケーションを想定どおりに動作させることをアプリケーションの「スレッドセーフ」化と呼びます。

アプリケーションをスレッドセーフ化するには、原則として、共有リソース(状態情報やグローバル変数など)の使用頻度をできるだけ制限します。ただし、通常はリソースの使用を完全に排除することは不可能であり、その対処のためにオブジェクトのロックなどの同期メカニズムが使われます。

Python 2.7 では、Python のスレッド ライブラリにアクセスできます。これを使って、あるロジック ブロックについて、その中のコードを同時にではなく順番に実行するよう強制するロックを宣言できます。たとえば、次のコードについて考えてみます。

class Configuration(ndb.Model):
  some_config_data = ndb.StringProperty()
  _config_cache = None
  _config_lock = threading.Lock()
  @classmethod
  def get_config(cls):
    with cls._config_lock:
      if not cls._config_cache:
        cls._config_cache = cls.get_by_id('config')
    return cls._config_cache

このコードでは、いくつかのグローバル構成変数を _config_cache という変数に収めるキャッシュを作成しています。ここでは _config_lock というロック オブジェクトを使うことで、既存の _config_cache のチェックが信頼できるかどうかを確かめています。そうしないと、すべての競合するリクエストで _config_cache が空であることが判明したことにより、同じ変数に同じデータを何度も設定するために Datastore は何回もアクセスされ、時間の無駄になる可能性があります。

ロックの使用には十分注意してください。ロックを実行すると、そのメソッドを実行している他のスレッドが強制的にブロックされ、パフォーマンス上のボトルネックになるおそれがあります。

アプリケーションの webapp2 への更新

: webapp をリクエスト ハンドラとして使わない場合は、このセクションをスキップできます。

Python 2.7 ランタイムに含まれていたウェブ フレームワークが webapp から webapp2 にアップグレードされました。特に webapp2 には、URI ルーティングや例外処理の改善、本格的なレスポンス オブジェクト、より柔軟なディスパッチ メカニズムが追加されています。

現在、webapp テンプレートは非推奨です。代わりに、Jinja2Django、または任意のテンプレート生成システム(ピュアな Python で記述されている場合)を使用できます。

App Engine では、webapp2webapp にエイリアスされており、webapp2 が下位互換となっています。ただし、下位互換性に依存し続けるのではなく、アップグレード後もアプリケーションのテストを十分に行い、webapp2新しい構文と機能を習得する必要があります。

そうすればアップグレードが完了します。

アプリケーションをアップロードしたら、下位互換性を確保するためにアプリケーションを広範にわたってテストする必要があります。問題が発生している場合は、フォーラムを参照してください。