Hyper-VとVirtual Serverの相互運用

ちょっとご無沙汰になっていましたが、今回はちょっと毛色の変わった話で、Pythonから離れたインフラの話題をやろうと思います。

物理サーバのレベルで仮想化するのが、流行ってきました。Hyper-VXenですが、Citrixが管理コンソールを発表したXenと、シングルベンダで頑張っているマイクロソフトHyper-V(こちらはWindows Server 2008 R2で高機能化しますね)を話題にします。実際のプロジェクトではHyper-Vを使っているのですが、Hyper-Vとその他の環境を移行させなければいけない困った事態に遭遇して、一応解決できたので、まとめてみます。

続きを読む

Pythonの文字列連結(join)にまつわる議論 まとめと考察(2)

だいぶ時間が開いてしまったけれど、Pythonの文字列joinの不思議について、さらに突っ込んだ解釈を試みてみましょう。

あくまでBetaNews的な解釈だけれど、


", ".join([...])
というのは、元々は次の形をしていると考えるべきなんじゃないか、っていうこと。

str(", ").join([...])
この解釈に基づいて、セパレータを省略すると、次の書き方が初期に存在したのではないか、と推測できる。

"".join([...])

続きを読む

Pythonでシングルトンを実装するには?

クラスが使える言語の場合、クラス インスタンスが1つしか生成されないシングルトン(Singleton)が便利なケースがあります。ある解説書(『Expert Python Programming』:Tarek Ziade著)によれば、


Singleton restricts instantiation of a class to one object.

シングルトンは、クラスのインスタンス化を単一オブジェクトに規制する。
と書かれています。では、Pythonでどのように実装するのか? というのが今回のお話。

  1. 最低限のクラス定義

まずは、題材にする最低限の機能しか持たないクラスを定義してみましょう。

class Singleton(object):
    def __new__(clsObj, *args, **kwargs):
        instance = super(Singleton, object).__new__(clsObj, *args, **kwargs)
        return instance

    def __init__(self, *args, **kwargs):
        pass
  1. シングルトンの初歩

次に、このクラスを名実ともにシングルトン化してみましょう。

class Singleton(object):
    def __new__(clsObj, *args, **kwargs):
        if not hasattr(clsObj, "__instance__"):
            clsObj.__instance__ = super(Singleton, object).__new__(clsObj, *args, **kwargs)
        return clsObj.__instance__
    
    def __init__(self, *args, **kwargs):
        pass

これで、最小限のシングルトン実装になりました。__new__()メソッドのhasattr(...)部分は、Python 2.5以前では

    def __new__(clsObj, *args, **kwargs):
        if "__instance__" not in vars():
            clsObj.__instance__ = super(Singleton, object).__new__(clsObj, *args, **kwargs)
        return clsObj.__instance__

という書き方もアリな方法でしたが、Python 3ではvars()やglobals()、locals()といったグローバル関数の使用が推奨されない(少なくともスコープにより推奨されない場合が出てくる)ため、今後は使わないのがバージョン依存性を減らすためにも、よいと思われます。

ただこの方法は、このSingletonクラスが継承されない前提でしか成り立たない実装です。というのも、基底クラスのコンストラクタが呼ばれる際に再帰処理が暗黙で実行されることが原因です。どういうことが起きるかというと、Singletonクラスのインスタンスであろうが、Singletonクラスのサブクラスのインスタンスであろうと、合わせて1つのインスタンスしか生成されない、という現象が起きます。

  1. 継承を考慮したシングルトン実装

そこで、基本的な仕組みは変えずに、もうちょっと賢い方法を考えましょう(多少冗長ではありますが、理解しやすさを優先しました)。

class sbase(object):
    def __new__(clsObj, *args, **kwargs):
        tmpInstance = None
        if not hasattr(clsObj, "_instanceDict"):
            clsObj._instanceDict = {}
            clsObj._instanceDict[hash(clsObj)] = super(sbase, clsObj).__new__(clsObj, *args, **kwargs)
            tmpInstance = clsObj._instanceDict[str(hash(clsObj))]
        elif not hasattr(clsObj._instanceDict, str(hash(clsObj))):
            clsObj._instanceDict[str(hash(clsObj))] = super(sbase, clsObj).__new__(clsObj, *args, **kwargs)
            tmpInstance = clsObj._instanceDict[str(hash(clsObj))]
        else:
            tmpInstance = clsObj._instanceDict[str(hash(clsObj))]
        return tmpInstance

先ほどまでの実装にあった__instance__アトリビュートはなくなり、_instanceDictというディクショナリ型のアトリビュートを使っています。hash()グローバル関数を使い、クラス オブジェクト(インスタンス生成前のクラス設計図)の一意なハッシュ コード(整数値)を取得し、辞書のキーにすることにより、クラスごとのインスタンス一意性を確保しています(getattr()グローバル関数を使うためにstr型にキャストしています)。

では、sbaseクラスによる実装をIDLEで軽く検証してみることにします。

>>> i1 = sbase()
>>> i1
<__main__.sbase object at 0x01FB8330>
>>> i2 = sbase()
>>> i2
<__main__.sbase object at 0x01FB8330>
>>> class s1(sbase):pass

>>> i3 = s1()
>>> i3
<__main__.s1 object at 0x01FB8E70>
>>> i3._instanceDict
{'33007512': <__main__.sbase object at 0x01FB8330>, '27442784': <__main__.s1 object at 0x01FB8E70>}
>>> i4 = s1()
>>> i1._instanceDict
{'33007512': <__main__.sbase object at 0x01FB8330>, '27442784': <__main__.s1 object at 0x01FB8E70>}

i1とi2は基底クラスであるsbaseクラス、i3とi4はsbaseクラスを継承したs1クラスの、それぞれインスタンスです。IDLEの結果から、sbaseクラスのシングル インスタンス

'33007512': <__main__.sbase object at 0x01FB8330>
として、s1クラスのシングル インスタンス
'27442784': <__main__.s1 object at 0x01FB8E70>
として、それぞれ保持されていることが分かります。

デザインパターンは、GoFによる整理された思考ツール群です。BetaNewsは、思考ツールすべてを使う必要はなく、またそれらすべてを記憶している必要もないと考えています。とはいえ、使いこなすと便利であることは確かで、かつどの言語であっても(言語実装の制約はあったとしても)普遍的に利用できるものばかりです。Pythonでのデザインパターンを実装論まで含めてきちんと解説した書籍は非常に少ない(日本語では皆無に近い?)ので、引き続き、便利なデザパタにも焦点をあてていこうと思います。

Expert Python Programming: Learn Best Practices to Designing, Coding, and Distributing Your Python Software

Expert Python Programming: Learn Best Practices to Designing, Coding, and Distributing Your Python Software

Apacheのアクセス ログをPythonで解析するには?

Apacheログを効率よく解析するのは、SEO対策の面でも、パフォーマンス チューニングの面でも、かなり有効です。Apacheで一般的に使われるのはcommonとcombined形式のアクセスログで、かつcombinedio形式を独自にカスタマイズしたものなども使われます。とりあえず、一般的なcommon形式とcombined形式を正規表現化してみましょう。

  • commonの場合、

    ^([0-9]{,3}\.[0-9]{,3}\.[0-9]{,3}\.[0-9]{,3}) ([^ ]{1,}) ([^ ]{1,}|\-) \[([0-9]{2}\/[A-Za-z]{3}\/[0-9]{1,4}:[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2} [+\-][0-9]{4})\] "([A-Z ]+) ([^"]*) ([^"]*)" ([0-9]{3}) ([0-9]{1,}|\-) "([^"]*|\-)" "([^"]+)"$
  • combinedの場合、

    ^([0-9]{,3}\.[0-9]{,3}\.[0-9]{,3}\.[0-9]{,3}) ([^ ]{1,}) ([^ ]{1,}|\-) \[([0-9]{2}\/[A-Za-z]{3}\/[0-9]{1,4}:[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2} [+\-][0-9]{4})\] "([A-Z ]+) ([^"]*) ([^"]*)" ([0-9]{3}) ([0-9]{1,}|\-) "([^"]*|\-)" "([^"]+)"$
といった感じになります。がしかし、このままではあまりに融通が利かないので、Pythonで次のようなLogLineクラス(文字列を分析するファクトリ)とLogRowクラス(ログ要素に解析済みのオブジェクト)、FORMATクラス(定数を入れるためだけ用)を作ってみました。


import re#スクリプト ファイルの冒頭部分に記述するのがよい

class FORMAT(object):
_UNSET_ = "unset"
_UNDEFINED_ = "undefined"
_UNKNOWN_ = "unknown"
_COMMON_ = "common"
_COMBINED_ = "combined"

class logRow(object):
ipaddr = ""
clientID = ""
userID = ""
requestTimeStr = ""
requestTimeTzStr = ""
requestTimeTuple = False
method = ""
resource = ""
protocol = ""
statusCode = ""
responseBodySize = 0L
referer = ""
userAgent = ""
options = {}
def __init__(self, cols, **kwargs):
if isinstance(cols, list) or isinstance(cols, tuple):
if len(cols)>8:
self.ipaddr = cols[0]
self.clientID = cols[1]
self.userID = cols[2]
self.requestTimeStr = cols[3]
self.requestTimeTzStr = self.requestTimeStr[-5:]
self.requestTimeTuple = time.strptime(cols[3][:-6], "%d/%b/%Y:%H:%M:%S")
self.method = cols[4]
self.resource = cols[5]
self.protocol = cols[6]
self.statusCode = cols[7]
try:
self.responseBodySize = long(cols[8])
except ValueError:
self.responseBodySize = 0L
if len(cols)>10:
self.referer = cols[9]
self.userAgent = cols[10]
if len(cols)==12:
if isinstance(cols[11], dict):
self.options = cols[11]
else:
self.options = kwargs

def __dict__(self):
resDict = {}
resDict['ipaddr'] = self.ipaddr
resDict['clientID'] = self.clientID
resDict['userID'] = self.userID
resDict['requestTimeStr'] = self.requestTimeStr
resDict['requestTimeTzStr'] = self.requestTimeTzStr
resDict['requestTimeTuple'] = self.requestTimeTuple
resDict['method'] = self.method
resDict['resource'] = self.resource
resDict['protocol'] = self.protocol
resDict['statusCode'] = self.statusCode
resDict['responseBodySize'] =self.responseBodySize
resDict['referer'] = self.referer
resDict['userAgent'] = self.userAgent
resDict['options'] = self.options
return resDict

def __repr(self):
return repr(self.__dict__)

class LogLine(object):
"""
Apache log parser class. Parsed field is set on field attribute.
Parsed field is the instance of structure class named as logRow.
"""
_FMT_COMMON = re.compile("""^([0-9]{,3}\.[0-9]{,3}\.[0-9]{,3}\.[0-9]{,3}) ([^ ]{1,}) ([^ ]{1,}|\-) \[([0-9]{2}\/[A-Za-z]{3}\/[0-9]{1,4}:[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2} [+\-][0-9]{4})\] "([A-Z ]+) ([^"]*) ([^"]*)" ([0-9]{3}) ([0-9]{1,}|\-) "([^"]*|\-)" "([^"]+)"$""")
_FMT_COMBINED = re.compile("""^([0-9]{,3}\.[0-9]{,3}\.[0-9]{,3}\.[0-9]{,3}) ([^ ]{1,}) ([^ ]{1,}|\-) \[([0-9]{2}\/[A-Za-z]{3}\/[0-9]{1,4}:[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2} [+\-][0-9]{4})\] "([A-Z ]+) ([^"]*) ([^"]*)" ([0-9]{3}) ([0-9]{1,}|\-) "([^"]*|\-)" "([^"]+)"$""")
_format = FORMAT._UNSET_
_rawString = ""
__FORMATS__ = {}
field = False
def __init__(self, line="", format=None):
"""

"""
self.__FORMATS__[FORMAT._COMMON_] = self._FMT_COMMON
self.__FORMATS__[FORMAT._COMBINED_] = self._FMT_COMBINED
if isinstance(line, basestring):
self._format = self.judgeFormat(line, format)
self._rawString = line
else:
self._format = FORMAT._UNSET_
if self._rawString!="" and self._format!=FORMAT._UNSET_ and self._format!=FORMAT._UNKNOWN_ and self._format!=FORMAT._UNKNOWN_:
self.parseLine()

@classmethod
def judgeFormat(clsObj, line="", format=""):
if format is None:
if clsObj._FMT_COMBINED.match(line):
format = FORMAT._COMBINED_
elif clsObj._FMT_COMMON.match(line):
format = FORMAT._COMMON_
else:
format = FORMAT._UNDEFINED_
if format=="common":
format = FORMAT._COMMON_
elif format=="combined":
format = FORMAT._COMBINED_
else:
format = FORMAT._UNDEFINED_
return format

def parseLine(self, rawLine=None):
if rawLine is None and self._format in self.__FORMATS__.keys() and self._rawString!="":
parsedTuple = self.__FORMATS__[self._format].findall(self._rawString)
if parsedTuple:
self.field = logRow(parsedTuple[0])
return True
elif rawLine is not None and isinstance(rawLine, basestring):
self._format = self.judgeFormat(rawLine)
result = self.parseLine()
return result
else:
return False

def getFormat(self):
return self._format

def setFormat(self, format):
if isinstance(format, basestring):
if format=="common":
self._format = FORMAT._COMMON_
elif format=="combined":
self._format = FORMAT._COMBINED_

def getRawString(self):
return self._rawString

def setRawString(self, line=None):
if isisntance(line, basestring):
self._rawString = line

LogLineクラスのコンストラクタにApacheのログ行(文字列)を食わせると、LogLineインスタンスのfieldアトリビュートに、解析されたログ行のキャプチャできた部分がLogRowクラス インスタンスとして保存されます。その際に、Apacheのアクセス ログ形式がcommonとcombined形式のどちらかでありさえすれば、行ごとに自動解析する仕組みにしました。

たとえば、


210.155.149.140 - - [06/Apr/2009:22:11:19 +0900] "POST /wordpress/wp-cron.php?check=83d0c18ebeedfebf737edad33a3142f1 HTTP/1.0" 200 - "-" "WordPress/2.7.1"
といったアクセス ログ行があった場合、fieldアトリビュート
  • ['requestTimeStr'] : 06/Apr/2009:22:11:19 +0900

  • ['resource'] : /wordpress/wp-cron.php?check=83d0c18ebeedfebf737edad33a3142f1

  • ['ipaddr'] : 210.155.149.140

  • ['responseBodySize'] : 0

  • ['userID'] : -

  • ['clientID'] : -

  • ['requestTimeTzStr'] : +0900

  • ['options'] : {}

  • ['referer'] : -

  • ['userAgent'] : WordPress/2.7.1

  • ['protocol'] : HTTP/1.0

  • ['requestTimeTuple'] : (2009, 4, 6, 22, 11, 19, 0, 96, -1)

  • ['method'] : POST

  • ['statusCode'] : 200

というように解析されます。そのままDBに入れるもヨシ、LogRowインスタンスだけを配列系統にまとめて分析にかけるもよし、という具合がよいかと思います。

Pythonコードをよく読まれた方は気付かれると思うのですが、LogLineクラスの_FMT_*アトリビュートに任意の正規表現オブジェクトを追加し、FORMATクラスの定数定義を併せて拡張することで、アクセス ログの解析方法をカスタマイズできます(継承してもいいし、継承しにくければ、インスタンス化してから直接属性追加してもいいかと)。

ということで、Python関連のテクニックやら発見したことなどを、今後とも不定期で書いていこうと思います。