【问题标题】:Python: How do I dynamically alter methods of dict and list objects?Python:如何动态更改 dict 和 list 对象的方法?
【发布时间】:2010-12-24 23:09:11
【问题描述】:

这是我想做的模型:

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

这适用于语法应尽可能接近普通 python 的 DSL 项目,因此子类化列表并让用户调用类似 alist = MyList(1,2,3,4,5) 的东西是不可取的。此外,由于 DSL 需要与其他库共存,全局更改 list 和 dict 不是一种选择...

我尝试过创建实例方法并将它们直接设置在 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 语言实现的对象(如 dict 和 list),这两种方法都会失败:

>>> 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) 的方法在这里不起作用,因为可以像这样从 __setattribute__ 调用中调用 replacef:

alist = [1,2,3,4,5]
aDSLObject.items = alist #  __setattribute__ calls replacef on alist
alist[0] = ....

因此,虽然确实可以动态更改对象方​​法,但似乎无法更改用 C 实现的对象的行为 - 还是我遗漏了什么?

【问题讨论】:

    标签: python list dynamic methods dictionary


    【解决方案1】:

    也许您可以在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

    【讨论】:

    • +1:一个奇怪问题的甜蜜解决方案。稍有不同的构造函数语法(MyList vs. [)并不是接受的障碍,那么为什么要担心呢?但这解决了它。
    • 感谢您的回答。我想这确实是一个奇怪的问题:) 理想情况下,DSL 的用户不必了解底层实现。我喜欢这种方法。也许有一种方法可以在不直接分配的情况下更改“alist”所指向的内容,例如 C 中的“指向指针的指针”?
    • 不,我认为这不可能。最接近的可能是更改可变变量的内容,例如列表。顺便说一句,如果您设法在不更改类的情况下用新行为增加列表实例——那会感觉有点可怕。如果有代码使用 type() 和 isinstance() 等,它不会注意到差异,可能会导致意想不到的副作用......
    • @__mme__:没有理由去搞乱“改变alist 指向的东西”。 (A) 在 Python 中是不可能的。 (B) 没必要。 DSL 是语言,而不是实现。只要语法看起来正确,实现就是无名小卒。例如,查看 Python 的 re 包。该实现不称为re,因此这些类与您对文档的期望不完全相同。
    • @S.Lott:确实可以用'inspect'包和currentframe函数来改变它,但这容易出现各种问题......我不明白什么你想说语法,但也许我应该让自己更清楚:我提供的代码模型是我对 DSL 的问题归结为,而不是 DSL 本身的实际语法。 DSL 的想法是修改对象,包括传递给 DSL 函数的字典和列表,以静默记录它们的状态更改并将它们复制到另一台机器。
    【解决方案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) 尝试将它变成一个列表。这将适用于迭代器和生成器,强制它们展开为列表。

    【讨论】:

    • 感谢您的回答。你确实有一点,有时让事情更明确更容易理解。我正在考虑对字典和列表进行显式包装调用的后果。但是,鉴于 Python 提供了许多扩展语法的选项,我仍然很好奇我所要求的是否可行。
    • Python 并不能完全重新映射语法。例如,很容易设置一个类,使得该类的成员是只读的,任何分配给该类成员的尝试都会失败并引发异常;但是不可能让一个简单的变量表现得一样。您可以执行类似foo = dict(whatever); foo = do_magic(foo) 的操作,并且由于dicts 是可变的,您可以浏览所有条目并对它们执行某些操作。但是没有办法非显式地重新绑定一个变量,所以你不能偷偷改变对象的类型。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-20
    • 2019-08-07
    • 2013-06-02
    • 2019-09-04
    • 1970-01-01
    • 1970-01-01
    • 2014-06-02
    相关资源
    最近更新 更多