星期一, 六月 18, 2007

python redefined __setattr__

现在在定义新的 python 数据结构 Tree,其目标是希望其在 python 中的操作非常简单,象 builtins 的变量那样,例如 root.branch1.branch11() 能返回这个节点的值。

现在,在对一个树的节点赋值时,如:
root.branch1.branch11 = value
或:
root.branch1.branch12 = Tree(value)
对于这个节点的名字要有一点限制(也就是前面讨论的要避免名字空间冲突的问题),就是对于一个 Tree instance 已经保留使用的 name,如 method 名:__repr__, __getitem__, __setitem__,以及作为节点值的 __node_value 和作为键值索引的 __node_items。这时,调用的方法是 Tree.__setattr__,为了达到保留字的目的,最初的做法是这样的:
class Tree:
_("""A Tree instance can only contains sub Tree instances as its attributes""")
# (1)
__used_names = None
__used_names = Tree.__dict__.copy()
__used_names.update(object.__dict__.copy())
__used_names = used_names.keys()
def __init__(self, value=None, **kwargs):
self.__node_value = value
# __node_value should always be Non-Tree
self.__node_items = {}
for k, v in kwargs.items(): self.__setattr__(k, v)
...
def __setattr__(self, attr_name, value):
_("""The operation 'root.br1.br2 = value' works on root.br1 rather than root.br1.br2""")
if attr_name in self.__used_names:
raise TreeExc(_("Attribute name '%s' is reserved" % attr_name))
try:
existed = getattr(self, attr_name)
# self.attribute exists
if isinstance(value, Tree):
subtree = value
if not subtree._Tree__node_value:
subtree._Tree__node_value = existed._Tree__node_value
setattr(self, attr_name, subtree)
# replace directly after the tree __node_value has been reserved
else:
setattr(self, '%s._Tree__node_value' % attr_name, value)
except AttributeError:
# if self.attribute does not exists, assign it a EMPTY node
if isinstance(value, Tree):
subtree = value
setattr(self, attr_name, subtree)
else:
setattr(self, attr_name, Tree(value))
然后运行:
sh$ python -c "import tree; t = tree.Tree(1, data='head')"
Traceback (most recent call last):
File "", line 1, in ?
File "tree.py", line 16, in ?
class Tree:
File "tree.py", line 20, in Tree
__used_names = Tree.__dict__.copy()
NameError: name 'Tree' is not defined
用 self 也是一样的报告没有 difined 的错误。这是很好理解的,在 Tree 完成所有的 attributes 的 assign 之前,它当然是没有定义的。所以这种办法不可行。

于是重新定义:
class Tree:
_("""A Tree instance can only contains sub Tree instances as its attributes""")
# (1)
# __used_names = None
# __used_names = Tree.__dict__.copy()
# __used_names.update(object.__dict__.copy())
# __used_names = used_names.keys()
# ------------------------------------------------------------------------
def __init__(self, value=None, **kwargs):
# (2)
self.__used_names = None
self.__used_names = Tree.__dict__.copy()
self.__used_names.update(object.__dict__)
self.__used_names = self.__used_names.keys()
self.__node_value = value
# __node_value should always be Non-Tree
self.__node_items = {}
for k, v in kwargs.items(): self.__setattr__(k, v)
def __getattr__(self, attr_name): pass
def __setattr__(self, attr_name, value):
_("""The operation 'root.br1.br2 = value' works on root.br1 rather than root.br1.br2""")
if attr_name in self.__used_names:
raise TreeExc(_("Attribute name '%s' is reserved" % attr_name))
try:
existed = getattr(self, attr_name)
# self.attribute exists
if isinstance(value, Tree):
subtree = value
if not subtree._Tree__node_value:
subtree._Tree__node_value = existed._Tree__node_value
setattr(self, attr_name, subtree)
# replace directly after the tree __node_value has been reserved
else:
setattr(self, '%s._Tree__node_value' % attr_name, value)
except AttributeError:
# if self.attribute does not exists, assign it a EMPTY node
if isinstance(value, Tree):
subtree = value
setattr(self, attr_name, subtree)
else:
setattr(self, attr_name, Tree(value))
运行结果:
sh$ python -c "import tree; t = tree.Tree(1, data='head')"
Traceback (most recent call last):
File "", line 1, in ?
File "tree.py", line 30, in __init__
self.__used_names = None
File "tree.py", line 55, in __setattr__
if attr_name in self.__used_names:
TypeError: iterable argument required
可以看到,__setattr__ 被调用了,说明当重新定义了 __setattr__ 之后,不仅仅是外部调用如 root.branch1.branch11 = value 这样的情况会收到影响,而且自身的使用如 self.attribute = value 也会受到影响,都会把要操作的对象当作一个 Tree Node 来处理。同时这也说明接下来的两个语句:
self.__node_value = value
# __node_value should always be Non-Tree
self.__node_items = {}
也是有问题的。实际上,这会导致赋值语句的死循环,最后在运行时可能会报这样的错误:
......
File "tree.py", line 67, in __setattr__
setattr(self, '%s._Tree__node_value' % attr_name, value)
File "tree.py", line 67, in __setattr__
setattr(self, '%s._Tree__node_value' % attr_name, value)
File "tree.py", line 67, in __setattr__
setattr(self, '%s._Tree__node_value' % attr_name, value)
File "tree.py", line 67, in __setattr__
setattr(self, '%s._Tree__node_value' % attr_name, value)
File "tree.py", line 67, in __setattr__
setattr(self, '%s._Tree__node_value' % attr_name, value)
File "tree.py", line 67, in __setattr__
setattr(self, '%s._Tree__node_value' % attr_name, value)
File "tree.py", line 67, in __setattr__
setattr(self, '%s._Tree__node_value' % attr_name, value)
File "tree.py", line 67, in __setattr__
setattr(self, '%s._Tree__node_value' % attr_name, value)
File "tree.py", line 48, in __setattr__
_("""The operation 'root.br1.br2 = value' works on root.br1 rather than root.br1.br2""")
File "/usr/lib/python2.3/gettext.py", line 472, in gettext
return dgettext(_current_domain, message)
File "/usr/lib/python2.3/gettext.py", line 454, in dgettext
t = translation(domain, _localedirs.get(domain, None))
File "/usr/lib/python2.3/gettext.py", line 403, in translation
mofiles = find(domain, localedir, languages, all=1)
File "/usr/lib/python2.3/gettext.py", line 366, in find
val = os.environ.get(envar)
File "/usr/lib/python2.3/UserDict.py", line 51, in get
if not self.has_key(key):
RuntimeError: maximum recursion depth exceeded


那么如果使用内置的 setattr() 函数呢?将上面的(2)改成(3):
        # (3)
setattr(self, '__used_names', None)
setattr(self, '__used_names', Tree.__dict__.copy())
self.__used_names.update(object.__dict__)
setattr(self, '__used_names', self.__used_names.keys())
运行:
sh$ python -c "import tree; t = tree.Tree(1, data='head')"
Traceback (most recent call last):
File "", line 1, in ?
File "tree.py", line 35, in __init__
setattr(self, '__used_names', None)
File "tree.py", line 55, in __setattr__
if attr_name in self.__used_names:
TypeError: iterable argument required
同样的错误,说明 setattr(self, attr_name, value) 与 self.attribute = value 一样收到 self.__setattr__() 的影响。

可以考虑的另一种办法是在每个 __init__ 中增加相应的初始化语句:
class Tree:
_("""A Tree instance can only contains sub Tree instances as its attributes""")
# (1)
# __used_names = None
# __used_names = Tree.__dict__.copy()
# __used_names.update(object.__dict__.copy())
# __used_names = used_names.keys()
# ------------------------------------------------------------------------
# __used_names = object.__dict__.copy().keys() + [
# '__used_names', '__node_value', '__node_items',
# '__getattr__', '__setitem__', '__getitem__', '__call__',
# 'tree', '__traverse__' ]
def __init__(self, value=None, **kwargs):
# (2)
# self.__used_names = None
# self.__used_names = Tree.__dict__.copy()
# self.__used_names.update(object.__dict__)
# self.__used_names = self.__used_names.keys()
# (3)
# setattr(self, '__used_names', None)
# setattr(self, '__used_names', Tree.__dict__.copy())
# self.__used_names.update(object.__dict__)
# setattr(self, '__used_names', self.__used_names.keys())
# --------------------------------------------------------------------
# self.__node_value = value
self.__dict__['__node_value'] = value
# __node_value should always be Non-Tree
# self.__node_items = {}
self.__dict__['__node_items'] = {}
for k, v in kwargs.items(): self.__setattr__(k, v)
def __setattr__(self, attr_name, value):
_("""The operation 'root.br1.br2 = value' works on root.br1 rather than root.br1.br2""")
# (4)
__used_names = Tree.__dict__.copy()
print __used_names
__used_names.update(object.__dict__.copy())
__used_names = __used_names.keys() + ['__node_value', '__node_items']

if attr_name in __used_names:
# --------------------------------------------------------------------
# if attr_name in self.__used_names:
raise TreeExc(_("Attribute name '%s' is reserved" % attr_name))
try:
existed = getattr(self, attr_name)
# self.attribute exists
if isinstance(value, Tree):
subtree = value
if not subtree._Tree__node_value:
subtree._Tree__node_value = existed._Tree__node_value
setattr(self, attr_name, subtree)
# replace directly after the tree __node_value has been reserved
else:
setattr(self, '%s._Tree__node_value' % attr_name, value)
except AttributeError:
# if self.attribute does not exists, assign it a EMPTY node
if isinstance(value, Tree):
subtree = value
# setattr(self, attr_name, subtree)
self.__dict__[attr_name] = subtree
else:
# setattr(self, attr_name, Tree(value))
self.__dict__[attr_name] = Tree(value)
可见,在重定义了 __setattr__ 之后,赋值只能使用 self.__dict__ 来完成了。

这样做的潜在的问题是开销可能会比较大,因为每次创建 Tree instance 的时候到要重复一遍。还是回到定义 Tree class static variable 的思路上来的话,最终是这样定义的:
class Tree:
_("""A Tree instance can only contains sub Tree instances as its attributes""")
# (1)
# __used_names = None
# __used_names = Tree.__dict__.copy()
# __used_names.update(object.__dict__.copy())
# __used_names = used_names.keys()
# ------------------------------------------------------------------------
__used_names = object.__dict__.copy().keys() + [
'__used_names', '__node_value', '__node_items',
'__getattr__', '__setitem__', '__getitem__', '__call__',
'tree', '__traverse__' ]
def __init__(self, value=None, **kwargs):
# (2)
# self.__used_names = None
# self.__used_names = Tree.__dict__.copy()
# self.__used_names.update(object.__dict__)
# self.__used_names = self.__used_names.keys()
# (3)
# setattr(self, '__used_names', None)
# setattr(self, '__used_names', Tree.__dict__.copy())
# self.__used_names.update(object.__dict__)
# setattr(self, '__used_names', self.__used_names.keys())
# --------------------------------------------------------------------
# self.__node_value = value
self.__dict__['__node_value'] = value
# __node_value should always be Non-Tree
# self.__node_items = {}
self.__dict__['__node_items'] = {}
for k, v in kwargs.items(): self.__setattr__(k, v)
def __setattr__(self, attr_name, value):
_("""The operation 'root.br1.br2 = value' works on root.br1 rather than root.br1.br2""")
# (4)
# __used_names = Tree.__dict__.copy()
# __used_names.update(object.__dict__.copy())
# __used_names = __used_names.keys() + ['__node_value', '__node_items']
# if attr_name in __used_names:
# --------------------------------------------------------------------
if attr_name in self.__used_names:
raise TreeExc(_("Attribute name '%s' is reserved" % attr_name))
try:
existed = getattr(self, attr_name)
# self.attribute exists
if isinstance(value, Tree):
subtree = value
if not subtree._Tree__node_value:
subtree._Tree__node_value = existed._Tree__node_value
# setattr(self, attr_name, subtree)
self.__dict__[attr_name] = subtree
# replace directly after the tree __node_value has been reserved
else:
# setattr(self, '%s._Tree__node_value' % attr_name, value)
self.__dict__[attr_name]._Tree__node_value = value
# self.__dict__[attr_name] = Tree(value)
except AttributeError:
# if self.attribute does not exists, assign it a EMPTY node
if isinstance(value, Tree):
subtree = value
# setattr(self, attr_name, subtree)
self.__dict__[attr_name] = subtree
else:
# setattr(self, attr_name, Tree(value))
self.__dict__[attr_name] = Tree(value)

没有评论: