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
を弄れば何か出来た気もするけど、
遠い昔のことで、あんまりよく覚えてない。
へー。 nedmalloc は tcmalloc よりも速いと主張しているけど、 実際のところどうなんだろ。 しかもその結果を信じるならば、 ptmalloc3 も十分速いみたいなんだが。