2006-12-10

_ 適当にC++風のアクセス制限をPythonに付けてみる

PythonでMultiThreadServer より:

Rubyと比べると、classメンバにprivate等のアクセス権を指定できなかったり、selfが氾濫したり、アンダースコアが氾濫する点が気持ち悪いように感じます。

C++では必ず this を付けてしまう私には self はさっぱり気にならないし(って言うか、Perl5で嫌でも慣らされた)、 アンダースコアはRubyのアットマークや end の群れと比べれば、どっこいどっこいだと思っている。 その辺は多分に慣れの問題なのだろう。

アクセス権については、いろいろ言いたいことはあるのだけれど、 確かに制御したくなることは度々ある。 Pythonでも多少のオーバーヘッドを我慢できるなら、 それほど実装するのは難しくない。 御託を並べるよりも、C++風味で適当に作ってみる。

import sys

class AccessControl(object):
  """Abstract access control."""
  def __init__(self):
    self.acl_dict = {}

  def public(self, name):
    self.acl_dict[name] = (None, 0)

  def protected(self, name):
    self.acl_dict[name] = (None, 1)

  def private(self, name):
    self.acl_dict[name] = (None, 2)

  def update(self, access_control):
    for k, v in access_control.acl_dict.iteritems():
      self.acl_dict.setdefault(k, v)

def restrictAccess(klass):
  """Return a restricted version of a class."""
  access_control = getattr(klass, 'access_control', None)
  if access_control is None:
    return klass

  for k, (dummy, level) in access_control.acl_dict.items():
    if level == 0:
      del access_control.acl_dict[k]
    else:
      access_control.acl_dict[k] = (klass, level)

  try:
    original_access_control = super(klass).access_control
    access_control.update(original_access_control)
  except AttributeError:
    pass

  class wrapper(klass):
    def __init__(self, *args, **kwargs):
      original_init = getattr(klass, '__init__', None)
      if original_init is not None:
        original_init(self, *args, **kwargs)

    def __getattribute__(self, name):
      safe_getattribute = super(wrapper, self).__getattribute__
      access_control = safe_getattribute('access_control')
      if name not in access_control.acl_dict:
        return safe_getattribute(name)

      klass, level = access_control.acl_dict[name]
      caller_self = sys._getframe(1).f_locals.get('self')
      if caller_self is not self:
        raise AttributeError, name
      if level == 1:
        # protected
        if not isinstance(caller_self, klass):
          raise AttributeError, name
      else:
        # private
        if getattr(caller_self, '__class__', None) is not wrapper:
          raise AttributeError, name

      return safe_getattribute(name)

  return wrapper

クラスを動的に作って、制御用クラスで覆ってしまう。 ちなみにこいつはnew-style classでないと上手く行かない。 old-styleでやろうと思うと、Zope2みたいに姑息な手段を講じざるを得ない。

本当にうまく行くかテストしてみる。

class Foo(object):
  """A demonstration of the access control."""
  access_control = AccessControl()

  access_control.private('private_attr')
  access_control.protected('protected_attr')
  access_control.public('public_attr')

  def __init__(self):
    self.private_attr = 'private'
    self.protected_attr = 'protected'
    self.public_attr = 'public'

  def print_private_attr(self):
    print self.private_attr

  def print_protected_attr(self):
    print self.protected_attr

  def print_public_attr(self):
    print self.public_attr

  access_control.private('private_method')
  def private_method(self):
    print 'calling private'

  access_control.protected('protected_method')
  def protected_method(self):
    print 'calling protected'
    self.private_method()

  access_control.public('public_method')
  def public_method(self):
    print 'calling public'
    self.protected_method()

Foo = restrictAccess(Foo)

foo = Foo()
foo.print_private_attr()
foo.print_protected_attr()
foo.print_public_attr()

try:
  print foo.private_attr
except AttributeError:
  print 'cannot touch private_attr'

try:
  print foo.protected_attr
except AttributeError:
  print 'cannot touch protected_attr'

try:
  print foo.public_attr
except AttributeError:
  print 'cannot touch public_attr'

try:
  foo.private_method()
except AttributeError:
  print 'cannot call private_method'

try:
  foo.protected_method()
except AttributeError:
  print 'cannot call protected_method'

try:
  foo.public_method()
except AttributeError:
  print 'cannot call public_method'

いろいろ外や内から呼べるかどうかテストするだけ。 結果はこうなる。

private
protected
public
cannot touch private_attr
cannot touch protected_attr
public
cannot call private_method
cannot call protected_method
calling public
calling protected
calling private

このやり方は実のところZope3と同じ手法だったりする。 ただし、私のは即席思慮不足的実装なので、 きっとZope3はもうちょっとマシに作ってあるに違いない。

もちろんこの適当実装には抜け道がいろいろある。 外からでも self と名付けて呼び出してやれば、内からの呼び出しだと勘違いする。 どっちにしろ、__dict__ とか object.__getattribute__ とか使われたら、お終いである。

でもまあこの辺はコーディング規約です、という他ない。 他の言語でも大抵いくらでも迂回する道はある。 Rubyなら後から勝手にメソッド作れるし、 C++ならポインタの中身を参照とか、 世の中に完全無敵というのは存在しないらしい。 シリアライズするって手もある訳だし。

Zopeの場合、制限された環境とそうでない環境を分けることで、 この問題に対処している。 Rubyでも $SAFE を弄れば何か出来た気もするけど、 遠い昔のことで、あんまりよく覚えてない。

_ Squid memory fragmentation problem

へー。 nedmalloctcmalloc よりも速いと主張しているけど、 実際のところどうなんだろ。 しかもその結果を信じるならば、 ptmalloc3 も十分速いみたいなんだが。

[]

トップ «前の日記(2006-11-30) 最新 次の日記(2007-01-01)»