Python:如何动态修改字典和列表对象的方法?
这里是我想做的一个示意图:
alist = [1,2,3,4,5] # create a vanilla python list object
replacef (alist) # replace __setitem__, extend,... with custom functions
alist[0]=2 # now the custom __setitem__ is called
这是一个DSL(领域特定语言)项目,语法应该尽量接近普通的Python,所以不希望通过子类化列表来实现,比如让用户写成 alist = MyList(1,2,3,4,5) 这样的方式。而且,由于这个DSL需要和其他库一起使用,全球性地改变列表和字典的行为也是不现实的……
我尝试过创建实例方法,并直接在对象上设置它们,比如 alist.append = myFunc,但Python说这些属性是只读的。改变 __class__
属性似乎也不被允许。
我想做的事情在Python中真的可能吗?
更新:这里是一些关于对象子类可以做的事情的发现:
假设:
class y(object):
def __init__(self,a,b,c):
self.a = a
self.b = b
self.c = c
def f(self):
print self
print self.a
print self.b
print self.c
class x(object):
def __init__(self,a,b,c):
self.a = a
self.b = b
self.c = c
def f(self):
print "x.f()"
>>> objy = y(1,2,3)
>>> objx = x(4,5,6)
>>> objy.f()
<__main__.y object at 0x02612650>
1
2
3
>>> objx.f()
x.f()
可以像这样动态地改变 objx 的类:
>>> objx.__class__ = y
>>> objx.f()
<__main__.y object at 0x02612D90>
4
5
6
此外,还可以像在JavaScript中一样,动态添加或改变对象的方法:
>>> def g(self,p):
print self
print p
>>> import new
>>> objy.g = new.instancemethod(g,objy,y)
>>> objy.g(42)
<__main__.y object at 0x02612650>
42
然而,这两种方法在像字典和列表这样的C语言实现的对象上会失败:
>>> def mypop(self):
print "mypop"
list.mypop(self)
>>> alist.pop = new.instancemethod(mypop,alist,list)
AttributeError: 'list' object attribute 'pop' is read-only
>>> x = [1,2,3,4,5]
>>> x.__class__ = mylist
TypeError: __class__ assignment: only for heap types
建议的做法是使用类似 alist = replacef(alist) 的方式,但在这里行不通,因为 replacef 可能会在 __setattribute__
调用中被调用,像这样:
alist = [1,2,3,4,5]
aDSLObject.items = alist # __setattribute__ calls replacef on alist
alist[0] = ....
所以,虽然确实可以动态改变对象的方法,但似乎无法改变C语言实现的对象的行为——或者我是不是漏掉了什么?
2 个回答
"明确比隐含更好。" 你应该清楚地告诉用户,他们需要使用 MyList()
而不是 list()
。这样你和你的用户在长远来看都会更开心。
顺便说一下,Ruby 语言是允许你这样做的。在 Ruby 中,你可以打开全局对象并添加新的功能。我觉得这有点吓人,而且 Ruby 的运行速度比 Python 慢,所以我并不是在鼓励你转向 Ruby。
所以,提供 MyList()
,并提供一种方法来将现有的列表包装成 MyList()
。
你可以让 MyList()
接受一堆参数,这样就能很方便地创建一个列表:
class MyList(object):
def __init__(self, *args):
self.lst = args
# add other methods to MyList() here
然后你可以这样调用它:MyList(1, 2, 3, 4)
,这个实例会有一个叫做 lst 的成员,值是:[1, 2, 3, 4]
但是现在,如果你想把一个列表变成 MyList
的实例,该怎么做呢?如果你这样做:
new_list = [1, 3, 5]
x = MyList(new_list)
那么你就把 x 设置成了:[[1, 3, 5]]
所以我建议你让 MyList()
只接受一个列表作为参数并保存它:
def ensure_list(seq):
try:
seq[0]
return seq
except TypeError:
return list(seq)
class MyList(object):
def __init__(self, seq):
self.lst = ensure_list(seq)
# add other methods to MyList() here
我刚刚编辑了这个回答。最开始,我在这里调用了 list()
,现在我改成了调用 ensure_list()
。ensure_list()
会检查你能否对它的参数进行索引;如果可以,它要么已经是一个列表,要么是可以像列表一样使用的东西,这样就会原封不动地返回。如果不能索引,那么 ensure_list()
会调用 list(seq)
来尝试把它变成一个列表。这对于迭代器和生成器也有效,强制它们展开成一个列表。
也许你可以在 replaceref
函数里面做类似 "alist = MyList(1,2,3,4,5)" 这样的事情?
def replacef(l):
return MyList(l) # Return instance of list subclass that has custom __setitem__
alist = [1,2,3,4,5]
alist = replaceref(alist)
这样用户在定义列表的时候仍然可以使用标准的列表语法,但在调用 replaceref
之后就能得到新的 setitem 功能。