基于__init__值的Python继承魔法方法

5

假设我有一个名为X的类。X的目的是封装一个listdict并提供事件监听能力。一切运行良好。

class X(object):
    def __init__(self, obj)
        self._obj = obj

    def __getattr__(self, name):
        # do stuff with self._obj

    def __getitem__(self, key):
        return self._obj[key]

    def __setitem__(self, key, val):
        self._obj[key] = val

    # rest of functionality ...

因此,您可以像这样使用它来包装一个dict

x = X({
    'foo' : False
})

x.listen('foo', callback)

X['foo'] = True         # triggers event
X.update({
    'foo' : False       # triggers event
})

或者一个列表:
x = X([1,2])

x.listen(callback)

X.append(1)        # triggers event
X[0] = 10          # triggers event

很好,接近我想要实现的目标了...

现在的问题是,因为X既适用于list对象又适用于dict对象,所以它不能从任何一个对象继承。这意味着我没有魔术类函数,例如__contains__

这导致了以下代码:

d = X({
        'foo' : True    
    })

    if 'foo' in d:
        print 'yahoo!'

抛出 KeyError 错误。

我如何解决这个问题,而不需要在 X 中定义每个魔法方法。如果我采用这种方式,对于每个定义,我都必须写两个返回值,基于 self._obj 是否是一个 list 或者 dict

起初,我以为可以使用元类来解决这个问题,但似乎并不是最好的解决方案,因为我需要访问传递的值来检查它是否是 dict 或者 list


你确定程序需要同时使用列表和字典吗?这听起来像是一个很好的分成两个类的地方。 - ollien
https://dev59.com/-Gkw5IYBdhLWcg3wXpac如何在Python中伪造代理类https://dev59.com/yV8e5IYBdhLWcg3wF3SVPython中的代理对象 - Josh Lee
也许这种方法可行?如何拦截新式类中Python“魔法”方法的调用?。示例DictWrapper包括对__contains__的测试。 - jq170727
1
如果你继承它们,它们将访问self而不是(所需的)self._obj - Davis Herring
1
魔术方法只会在类上查找,而不是实例。没有办法动态地为每个实例选择一个魔术方法。只需在您的类上定义所有魔术方法即可。 - BrenBarn
显示剩余8条评论
3个回答

7

一个简单的方法是使用代理类,例如wrapt.ObjectProxy。它将完全像“代理”类一样运作,除了重写的方法外。但是,您可以简单地使用self.__wrapped__来访问“未代理”的对象,而不是self._obj

from wrapt import ObjectProxy

class Wrapper(ObjectProxy):
    def __getattr__(self, name):
        print('getattr')
        return getattr(self.__wrapped__, name)

    def __getitem__(self, key):
        print('getitem')
        return self.__wrapped__[key]

    def __setitem__(self, key, val):
        print('setitem')
        self.__wrapped__[key] = val

    def __repr__(self):
        return repr(self.__wrapped__)

如果你用字典封装它,它就像一个字典一样运作:

>>> d = Wrapper({'foo': 10})
>>> d['foo']
getitem
10
>>> 'foo' in d   # "inherits" __contains__
True

就像列表一样,如果一个列表被包装:

>>> d = Wrapper([1,2,3])
>>> d[0]
getitem
1
>>> for i in d:   # "inherits" __iter__
...     print(i)
1
2
3

4
wrapt.ObjectProxy 的作用只是提供所有特殊方法和属性(后者作为property对象),并将它们代理到被包装的对象上。因此,def __contains__(self, value): return self.__wrapped__.__contains__(value) - Martijn Pieters

-1

你可以使用 collections 中的 UserListUserDict

from collections import UserList


class X(UserList):
    def __init__(self, obj):
        super().__init__()
        self.data = obj

    def __getattr__(self, name):
        pass
        # do stuff with self.data

    def __getitem__(self, key):
        return self.data[key]

    def __setitem__(self, key, val):
        self.data[key] = val


x0 = X([1, 2, 3])
x1 = X({1, 2, 3})
x2 = X({1: 1, 2: 2})

print(1 in x0)
print(1 in x1)
print(1 in x2)

-2

尝试类似这样的东西

def contains (self, item):
    If isinstance (self._obj, list):
        return list.contains (self._obj, item)
    If isinstance (self._obj, dict):
        return dict.contains (self._obj, item)

或者你想要继承的任何方法。当调用实例方法时,实例会自动传递self。但是如果你从类声明中调用该方法,则需要传递一个对象,因为self需要一个值。

Class Test:
    def print_cls (self):
        print self.__class__.__name__


t = Test ()

t.print_cls () # self is teat

Test.print_cls (5) 
# we replace self with another class object
# as long as the object you pass has the
# variables used in the method you call all should be good.

这将打印出Testint

2
看起来目标是X的一个实例将完全像传递给它的任何对象一样,而不是您必须调用某些特殊函数,如contains(),而不是使用in运算符。您也不希望特别处理特定类别,如listdict。我不确定您的Test类片段是关于什么的,但显然与问题没有太大关系。 - Arthur Tacca

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接