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 も十分速いみたいなんだが。