JavaScriptでクイックソート

■実装

var n = 10;
var a = [];
for(var i = 0; i < n; i++){
    a[i] = Math.round(Math.random() * n);
}
array = a.concat();


/**
 * qsort
 * 配列の左端と右端を指定してソートし分割位置を返す
 * @param {array} ary
 * @param {int} l
 * @param {int} r
 */
var qsort = function(ary, l, r){
    var pi = partition(ary, l, r);// 分割位置
    if(l < r){
        qsort(ary, l, pi - 1);// 左半分のqsort
        qsort(ary, pi + 1, r);// 右半分のqsort
    }
}

/**
 * swap
 * 配列の要素を指定のインデックスで入れ替える
 * @param {array} ary
 * @param {int} x
 * @param {int} y
 */
var swap = function(ary, x, y){
    console.log(ary.toString());
    var a = ary[x];
    var b = ary[y];
    ary[x] = b;
    ary[y] = a;
    console.log(ary.toString());
    console.log('---');
    return true;
}

/**
 * partition
 * 
 * @param {array} ary
 * @param {int} l
 * @param {int} r
 */
var partition = function(ary, l, r){
    var index = l + Math.floor(Math.random() * (r - l + 1));// lからrの範囲でランダム位置
    swap(ary, index, r);// 右端に軸値を移動
    //================================================
    // 以下の部分において軸値以下の数値を左から詰めていく
    var store = l;//置き場所
    for(var i = l; i < r; i++){
        if(ary[i] <= ary[r]){
            if(swap(ary, i, store)){
                store++;
            }
        }
    }
    //================================================
    swap(ary, store, r);// 軸値を軸値以下が並んでる列のすぐ右側に配置
    return store;
}


qsort(a, 0, a.length - 1);
console.log("before : " + array.toString());
console.log("after  : " + a.toString());

■特性

  • 常に軸値が最小値か最大値だった場合にO(n ^ 2)の計算量となる
  • 平均時性能はO(n log n)である

androidのリソース

■定義できるデータやファイル

Color(res/values)
fontやbackgroundの色を指定する数値
String(res/values)
表示する文字列
Dimensions
単位付きのサイズ
Drawable(res/drawable/)
画像
Animation(res/anim/)
SVG的な感じ
Layout(res/layout/)
Viewの位置や大きさを定義。HTML的なXML
Style, Theme(res/values)
レイアウト用の属性値を抜き出したようなもの

androidでは無理してcodeに書かない限り、Viewは分離される。

res/values/
文字列
res/xml/
任意のXMLファイル
res/raw
デバイスにコピーされるファイル

■リソースへのアクセス

eclipseのPackeage Explorer上でファイルを追加する。Rクラスに自動的に追加されたidentifer(staticフィールド)を通してアクセスする。

プログラムからアクセス

setContentView(R.layout.main);
spec = tabHost.newTabSpec("tab1").setIndicator(getString(R.string.favorite), res.getDrawable(R.drawable.star)).setContent(intent);

つまり以下のようになる。

String title = getString(R.string.favorite);
//String title = getResources().getString(R.string.favorite);
Drawable icon = getResources().getDrawable(R.drawable.ic_launcher);

リソースファイル間でのアクセス

以下のような形式でアクセスすることができる。

@[package:]type/name

res/values/color.xml

以下のようにアプリケーション共通で使う色を定義する。

<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="red">#ff0000</color>
</resources>
res/layout/mail.xml

以下のように使用する。

<TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:textColor="@color/red" />

またプログラムから取得するには以下のようにする。

getResouce().getColor(R.color.red);

■ローカライズ

表示する文字は以下のようにして多言語対応ができる。

res/values/string.xml
英語文字列を定義したファイル
res/values-ja/string.xml
日本語文字列を定義したファイル

■マルチデバイス

android端末は画面のサイズがまちまちだ。本当にまちまちだ。とりあえず以下のようにしてディスプレイの解像度に合わせた画像を用意することができる。

res/drawable-hdpi/*.(png|jpg|gif)
高解像度用画像ファイル
res/drawable-mdpi/*.(png|jpg|gif)
中解像度用画像ファイル
res/drawable-ldpi/*.(png|jpg|gif)
低解像度用画像ファイル

但し、androidではpngが推奨される。

■アニメーション

以下のようにしてアニメーションを定義することができる。

res/anim/rotate.xml

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
    android:toDegrees="180"
    android:pivotX="100"
    android:pivotY="100"
    android:duration="1000"
    android:repeatCount="10">
</rotate>

以下のようなレイアウトがあった場合、

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher"
        android:id="@+id/icon" />
</LinearLayout>

以下のようにする事でアニメーションを適用できる。

Animation animation = AnimationUtils.loadAnimation(this, R.anim.rotate);
ImageView imageView = (ImageView) findViewById(R.id.icon);
imageView.startAnimation(animation);

■Shape

以下のようにして形状を定義することもできる。

<?xml version="1.0" encoding="utf-8"?>
<shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#ccffffff" />
    <corners
        android:topLeftRadius="10.0dip"
        android:topRightRadius="10.0dip"
        android:bottomLeftRadius="10.0dip"
        android:bottomRightRadius="10.0dip" />
</shape>

以下のようにして使用する。

<LinearLayout
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/round" >
</LinearLayout>

androidでSharedPreferencesを使う

■読取

数値

SharedPreferences sp = getSharedPreferences("KEY", Context.MODE_PRIVATE);
int value = sp.getInt("KEY", -1);

文字列

SharedPreferences sp = getSharedPreferences("KEY", Context.MODE_PRIVATE);
int value = sp.getString("KEY", null);

■書込

数値

SharedPreferences sp = getSharedPreferences("KEY", Context.MODE_PRIVATE);
Editor editor = sp.edit();
editor.putInt("KEY", 1);
editor.commit();

文字列

SharedPreferences sp = getSharedPreferences("KEY", Context.MODE_PRIVATE);
Editor editor = sp.edit();
editor.putString("KEY", "hoge");
editor.commit();

getSharedPreferencesの第二引数について

MODE_PRIVATE
設定したアプリケーションからのみアクセス可能
MODE_WORLD_READABLE
読取のみ他のアプリケーションに許可する
MODE_WORLD_WRITEABLE
書込も他のアプリケーションに許可する

さくらのVPSをもう少しセットアップしてみる

今回はメール関連のお話。実はお試し期間中はメールが送信できない。クレジット支払いを選択してる方は「クレジットカードによるお支払いについてのお知らせ」というメールが届くまでメールが送信できないと思われる。

■sendmail

昔っからあるメール送信サーバ。さくらのVPSにデフォルトでインストールされている。

設定ツールのインストール

yum install sendmail-cf

設定ツールの編集

vi /etc/mail/sendmail.mc
接続を受けるアドレスを編集

以下の部分から

DAEMON_OPTIONS(`Port=smtp,Addr=127.0.0.1, Name=MTA')dnl

Addr=127.0.0.1の部分を削除し

DAEMON_OPTIONS(`Port=smtp, Name=MTA')dnl

外部からのメールを送受信できるようにする。

ドメインの変更

以下の部分を

LOCAL_DOMAIN(`localhost.localdomain')dnl

以下のようにホスト名に変更する。

LOCAL_DOMAIN(`www1234u.sakura.ne.jp')dnl
ホスト名の追加

以下の部分を

vi /etc/mail/local-host-names

以下のようにホスト名を追記する。

www1234u.sakura.ne.jp

設定変更を反映させた後、サーバを再起動する。

cd /etc/mail
make
/sbin/service sendmail restart

■postfix

postfixをインストールしたい場合もあるかもしれない。通常はsendmailかpostfixのどちらかがあれば十分だ。

yum install postfix

設定

設定ファイルを編集する。

vi /etc/postfix/main.cf
ホスト名の設定

以下の部分に

#myhostname = host.domain.tld
#myhostname = virtual.domain.tld

以下のようにメールサーバ名を追記する。

#myhostname = host.domain.tld
#myhostname = virtual.domain.tld
myhostname = www1234u.sakura.ne.jp
ドメイン名の設定

以下の部分に

#mydomain = domain.tld

以下のようにドメインを追記する。

#mydomain = domain.tld
mydomain = www1234u.sakura.ne.jp
送信アドレスにおける@マーク以降の部分の設定

以下の部分に

#myorigin = $mydomain

以下のように送信者の@マーク以降の部分を記述してあげる。

#myorigin = $mydomain
myorigin = $mydomain
待ち受けるべき全てのネットワークインターフェースの設定

実は初期設定ではインターネットからのメールが受信できないので、以下の部分を

inet_interface = localhost

以下のように変更する。

inet_interface = all
自ホスト宛てとして保存すべきメールアドレスの @以降を設定

また、以下の部分を

mydestination = $myhostname, localhost.$mydomain, localhost

以下のように変更する。

mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
ホームディレクトリの設定

また、以下の部分を

#home_mailbox = Maildir/

以下のように変更しメールボックスのディレクトリを設定する。

#home_mailbox = Maildir/
home_mailbox = Maildir/
サーバソフト名の設定

また、以下の部分を

#smtpd_banner = $myhostname ESMTP $mail_name
#smtpd_banner = $myhostname ESMTP $mail_name ($mail_version)

以下のように変更しメールサーバーソフト名を隠す。

#smtpd_banner = $myhostname ESMTP $mail_name
#smtpd_banner = $myhostname ESMTP $mail_name ($mail_version)
smtpd_banner = $myhostname ESMTP unknown
サーバソフト名の設定

以下をファイルの最終行に追記する。

# SMTP-Auth configuration
smtpd_sasl_auth_enable = yes
smtpd_sasl_local_domain = $myhostname
smtpd_recipient_restrictions =
    permit_mynetworks
    permit_sasl_authenticated
    reject_unauth_destination

# limit
message_size_limit = 10485760

認証デーモンを起動

システムアカウント情報を認証情報として利用したいので、以下のコマンドで認証デーモンを起動する。

/etc/rc.d/init.d/saslauthd start
chkconfig saslauthd on
chkconfig --list saslauthd

メールボックスの設定

mkdir -p /etc/skel/Maildir/{new,cur,tmp}
chmod -R 700 /etc/skel/Maildir/

sendmailの停止

/etc/rc.d/init.d/sendmail stop
chkconfig sendmail off

メールサーバの切替

alternatives --config mta

postfixの起動

/etc/rc.d/init.d/postfix start
chkconfig postfix on

■dovecot

メールの送信はできるようになったはずだが、受信が今のままではできないはずだ。以下のコマンドでdovecotをインストールする。

yum install dovecot

設定

以下のコマンドで設定ファイルを編集する。

vi /etc/dovecot.conf
プロトコルの設定

以下のコメントアウト部分を

#protocols = imap imaps pop3 pop3s

以下のようにコメントを外してあげる。

protocols = imap imaps pop3 pop3s
メールボックスディレクトリの設定

以下の部分に

#mail_location =

以下のような追記を加える。

#mail_location =
mail_location = maildir:~/Maildir

起動

/etc/rc.d/init.d/dovecot start
chkconfig dovecot on

info宛のメールがrootに転送される

以下のコマンドを実行し

vim /etc/aliases

以下の部分をコメントアウトして

info:          postmaster

以下のようにする。

#info:          postmaster

Djangoでセッションを使う

ビュー関数の引数のrequestオブジェクトのsessionプロパティを使用する。ちなみにsessionを利用するには、MIDDLEWARE_CLASSESに「django.contrib.sessions.middleware.SessionMiddleware」が含まれている必要がある。

■取得

以下のようにしてセッションに格納した情報を取得することができる。

def get(request):
    data = request.session.get('key','')

第二引数はデフォルト値であり、省略することが可能である。

■保存

以下のようにしてセッションに値を保存することができる。

def set(request):
    request.session['key'] = [1, 2, 3]

デフォルトのセッション保存先はDBである。

Djangoで管理画面を作る

■設定ファイル編集

vi setting.py

以下のように記述する。

INSTALLED_APPS = (
    'dj.article',
    'django.contrib.admin',
)

以下のコマンドでDBを同期する。

./manage.py syncdb

■管理項目追加

以下のディレクトリにadmin.pyを追加する。

cd article
vi admin.py

admin.pyには以下のように記述する。

# -*- coding: utf-8 -*-
from django.contrib import admin
from dj.article.models import Article

admin.site.register(Article)

たったこれだけでarticleの追加ができるようになる。

■URL設定

vi urls.py

以下のように記述する。

from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
    (r'^admin/', include(admin.site.urls)),
    (r'^article/(?P<id>[0-9]+)/$', 'dj.article.views.article'),
)

以下のコマンドで開発サーバを起動してブラウザで/adminにアクセスする。

./manage.py runserver 192.168.1.123:8080

そう!管理画面はもうできてるんですな。

■つくり込む

dj/article/model.py

以下のように__unicode__メソッドを定義すると一覧の表示名がタイトルになる。

# -*- coding: utf-8 -*-
from django.db import models

# Create your models heire.
class Article(models.Model):
    id      = models.AutoField(primary_key = True)
    title   = models.CharField(max_length = 256)
    content = models.TextField()
    date    = models.DateField()

    def __unicode__(self):
        return self.title

    class Meta:
        db_table = 'article'

dj/article/admin.py

表示項目を増やしたい場合は以下のように記述する。

# -*- coding: utf-8 -*-
from django.contrib import admin
from dj.article.models import Article

class ArticleAdmin(admin.ModelAdmin):
    list_display = ('title', 'date')# 表示項目

admin.site.register(Article, ArticleAdmin)

Pythonの条件節

■比較の連鎖

Pythonでは以下のように比較を連続して記述できる。

a = 3
b = 5
c = 7
if(a < b < c):
    print('hoge');
#hoge

ちなみにPHPではできない。

$a = 3;
$b = 5;
$c = 7;
if($a < $b < $c){
    print('hoge');
}
//Parse error: syntax error, unexpected '&lt;'

■条件節の中の代入文

Pythonでは以下のように条件節で代入文を記述することはできない。

b = [0, 3, 5]
if((a = b[1]) > 0):
    print('hoge')
#SyntaxError: invalid syntax

これについては公式マニュアルで以下のように説明されている。

Python では、C 言語と違って、式の内部で代入を行えないので注意してください。 C 言語のプログラマは不満を呈するかもしれませんが、この仕様は、 C 言語 プログラムで遭遇する、式の中で == のつもりで = とタイプ してしまうといったありふれた問題を回避します。

ちなみにPHPではできる。

$b = array(0, 3, 5);
if(($a = $b[1]) > 0){
    print('hoge');
}
#hoge

androidでradioボタンを使う

■レイアウトファイル

以下のようにすることで画面にラジオボタンを配置できる。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <RadioGroup
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:id="@+id/RadioGroup">
        <RadioButton
            android:text="1"
            android:id="@+id/btn1"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content" />
        <RadioButton
            android:text="2"
            android:id="@+id/btn2"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content" />
        <RadioButton
            android:text="3"
            android:id="@+id/btn3"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content" />
        </RadioGroup>
</LinearLayout>

■状態の取得

殆どの場合はコードからラジオボタンの状態を検知したいはずだ。

package info.justoneplanet.android.sample.radiobutton;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;

public class RadioButtonActivity extends Activity implements OnCheckedChangeListener {
    private RadioGroup mRadioGroup;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        mRadioGroup = (RadioGroup) findViewById(R.id.RadioGroup);
        mRadioGroup.setOnCheckedChangeListener(this);
    }
    
    // チェックの状態が変更されるごとに発火する
    @Override
    public void onCheckedChanged(RadioGroup group, int checkedId) {
        if (-1 == checkedId) {
            // 何もチェックされていないとき
        }
        else {
            // チェックされているとき
            Log.e("checked", getResources().getResourceEntryName(checkedId));// viewのID名を表示
        }
    }
    
    // 保存ボタンなどで取得したい場合は以下のメソッドを利用する
    @Override
    public void onPause() {
        mRadioGroup.getCheckedRadioButtonId();
    }
}

Djangoをインストールする

以下のコマンドでインストールする。

wget http://www.djangoproject.com/download/1.2.3/tarball/
tar xzvf Django-1.2.3.tar.gz
cd Django-1.2.3
python setup.py install

以下のコマンドでインストールできたか確認する。

python
import django
print(django.VERSION)
#(1, 2, 3, 'final', 0)

■プロジェクト作成

以下のコマンドでプロジェクトを作成する。

django-admin.py startproject dj

■開発サーバ起動

以下のコマンドで開発サーバを起動する。

cd dj
chmod 0744 manage.py
./manage.py runserver

以下のようにポートを指定することもできる。

cd dj
chmod 0744 manage.py
python ./manage.py runserver 0.0.0.0:80

■設定ファイル

以下のコマンドで設定ファイルを編集する。

vi setting.py

試しに編集してみる。

タイムゾーン

以下の項目を

TIME_ZONE = 'America/Chicago'

以下のように変更する。

TIME_ZONE = 'Asia/Tokyo'

言語

以下の項目を

LANGUAGE_CODE = 'en-us'

以下のように変更する。

LANGUAGE_CODE = 'ja'

データストア

以下のように記述して設定する。

DATABASES = {
    'default': {
        'ENGINE': 'sqlite3',
        'NAME': os.path.dirname(__file__) + os.sep + 'dj.sqlite',
        'USER': '',
        'PASSWORD': '',
        'HOST': '',
        'PORT': '',
    }
}

MySQLならば以下のようになる。

DATABASES = {
    'default': {
        'ENGINE': 'mysql',
        'NAME': 'dbname',
        'USER': 'admin',
        'PASSWORD': 'hogehoge',
        'HOST': 'localhost',
        'PORT': '3306',
    }
}

■DBセットアップ

このまま初期状態を生成しようとするとエラーが出るはずだ。

sqlite

以下のようなエラーが表示される。

ImproperlyConfigured("Error loading %s: %s" % (module, exc))
django.core.exceptions.ImproperlyConfigured: Error loading pysqlite2 module: No module named pysqlite2

pythonからsqliteに接続するドライバが無いらしいので、以下のコマンドでインストールする。

wget http://pypi.python.org/packages/source/p/pysqlite/pysqlite-2.6.0.tar.gz
tar xzvf pysqlite-2.6.0.tar.gz
cd pysqlite-2.6.0
python setup.py install

MySQL

以下のようなエラーが表示される。

raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e)
django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module: No module named MySQLdb

pythonからMySQLに接続するドライバが無いらしいので、以下のコマンドでインストールする。

setuptools

MySQL-pythonをインストールする際に必要なので、先にインストールする。

wget http://pypi.python.org/packages/source/s/setuptools/setuptools-0.6c11.tar.gz
tar xzvf setuptools-0.6c11.tar.gz
cd setuptools-0.6c11
python setup.py install
MySQL-python

以下のコマンドでMySQL-pythonをインストールする。

wget http://pypi.python.org/packages/source/M/MySQL-python/MySQL-python-1.2.3.tar.gz
tar xzvf MySQL-python-1.2.3.tar.gz
cd MySQL-python-1.2.3
python setup.py install

エラー1

インストールの途中で以下のようなエラーが発生するかもしれない。

_mysql.c:2444: error: '_mysql_ConnectionObject' has no member named 'open'
error: command 'gcc' failed with exit status 1
対策

以下のコマンドでmysql-develをインストールする。

yum --enablerepo=remi,epel install mysql-devel.x86_64

■アプリケーション生成

以下のコマンドでアプリケーション(の一部)を生成する。

./manage.py startapp article

以下のコマンドを実行してsetting.pyにも反映させる。

vi setting.py

以下の部分を

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
)

以下のように変更する。

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'dj.article',
)

■モデル

以下のコマンドでモデルを編集する。

cd article
vi model.py

編集前は以下のように記述されている。

from django.db import models

# Create your models here.

例えば以下のようにモデル部分を記述する。

# -*- coding: utf-8 -*-
from django.db import models

# Create your models here.
class Article(models.Model):
    id      = models.AutoField(primary_key = True)
    title   = models.CharField(max_length = 256)
    content = models.TextField()
    date    = models.DateField()
    class Meta:
        db_table = 'article'# table name

■コマンドラインから操作

なんでも生成したクラスを使ってデータの挿入ができるらしいぞ!

./manage.py shell

以下を入力する。

from article.models import Article
article = Article()
article.title = 'hogehoge'
article.content = 'this is a hoge pen'
article.date = '2010-10-10'
article.save()

なんと直感的なデータ挿入でござんしょう!

■テンプレートの生成

以下のコマンドを入力し設定ファイルを編集する。

vi setting.py

以下のように記述しテンプレートファイルの格納場所を指定する。

TEMPLATE_DIRS = (
    '/var/www/vhosts/domain/subdomains/dj/templates',
)

/var/www/vhosts/domain/subdomains/dj/templates/page/article.html

以下のようにテンプレートファイルを記述する。

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>{{article.title}}</title>
<head>

<body>
<h2>{{article.title}}</h2>
<nav class="date">{{article.date}}</nav>
<article>
{{article.content}}
</article>
</body>
</html>

分かると思うけど、{{}}の部分は変数が展開されるところですな。アサインした変数は自動でエスケープされるが、その処理を無効化したい場合は以下のように記述する。

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>{{article.title}}</title>
<head>

<body>
<h2>{{article.title}}</h2>
<nav class="date">{{article.date}}</nav>
<article>
{{article.content|safe}}
</article>
</body>
</html>

これでHTMLを含んだコンテンツを反映することができる。但し、XSSには注意すること。

■URLディスパッチャの設定

vi urls.py

以下のように記述する。

urlpatterns = patterns('',
    (r'^article/(?P<id>[0-9]+)/$', 'dj.article.views.article'),
)

■ビュー関数

以下のコマンドでビュー関数を付加する。

vi article/views.py

以下のように記述する。

from django.http import HttpResponse
from django.template import Context, loader
from models import Article

# Create your views here.
def article(request, id):
    article = Article.objects.get(id = id)
    tpl     = loader.get_template('page/article.html')
    context = Context({'article' : article})
    return HttpResponse(tpl.render(context))

もう直感的にガシガシくる感じだね!

■確認

以下のコマンドを入力して開発サーバを起動しブラウザで確認する。

./manage.py runserver 192.168.1.123:8080

Pythonで文字列の変数を展開する

以下のようにして文字列内の変数を展開する。

a = 'This'
b = 'pen'
print "%(a)s is a %(b)s" % locals()
#This is a pen

以下のように書いたほうが分かりやすい気がする。

print "%(a)s is a %(b)s" % {"a" : "This", "b" : "pen"}
#This is a pen

Pythonでは本来出来ないものなので、この辺はPHPの方が気持ちいいね。