目标是创建一个行为类似db结果集的模拟类。

例如,如果一个数据库查询返回,使用dict表达式,{'ab':100, 'cd':200},那么我想看到:

>>> dummy.ab
100

一开始我想也许我可以这样做:

ks = ['ab', 'cd']
vs = [12, 34]
class C(dict):
    def __init__(self, ks, vs):
        for i, k in enumerate(ks):
            self[k] = vs[i]
            setattr(self, k, property(lambda x: vs[i], self.fn_readyonly))

    def fn_readonly(self, v)
        raise "It is ready only"

if __name__ == "__main__":
    c = C(ks, vs)
    print c.ab

但是cab返回一个属性对象。

用k = property(lambda x: vs[i])替换setattr行根本没有用。

那么,在运行时创建实例属性的正确方法是什么呢?

附注:我知道在如何使用__getattribute__方法中提出了一个替代方案?


当前回答

最好的方法是定义__slots__。这样你的实例就不能有新的属性。

ks = ['ab', 'cd']
vs = [12, 34]

class C(dict):
    __slots__ = []
    def __init__(self, ks, vs): self.update(zip(ks, vs))
    def __getattr__(self, key): return self[key]

if __name__ == "__main__":
    c = C(ks, vs)
    print c.ab

打印12

    c.ab = 33

'C'对象没有属性'ab'

其他回答

目标是创建一个行为类似db结果集的模拟类。

所以你想要的是一本可以把a['b']拼成a.b的字典?

这很简单:

class atdict(dict):
    __getattr__= dict.__getitem__
    __setattr__= dict.__setitem__
    __delattr__= dict.__delitem__

最好的方法是定义__slots__。这样你的实例就不能有新的属性。

ks = ['ab', 'cd']
vs = [12, 34]

class C(dict):
    __slots__ = []
    def __init__(self, ks, vs): self.update(zip(ks, vs))
    def __getattr__(self, key): return self[key]

if __name__ == "__main__":
    c = C(ks, vs)
    print c.ab

打印12

    c.ab = 33

'C'对象没有属性'ab'

不确定我是否完全理解这个问题,但你可以在运行时使用类的内置__dict__修改实例属性:

class C(object):
    def __init__(self, ks, vs):
        self.__dict__ = dict(zip(ks, vs))


if __name__ == "__main__":
    ks = ['ab', 'cd']
    vs = [12, 34]
    c = C(ks, vs)
    print(c.ab) # 12

下面是以编程方式创建属性对象的简单示例。

#!/usr/bin/python3

class Counter:
    def __init__(self):
        cls = self.__class__
        self._count = 0
        cls.count = self.count_ref()

    def count_get(self):
        print(f'count_get: {self._count}')
        return self._count

    def count_set(self, value):
        self._count = value
        print(f'count_set: {self._count}')

    def count_del(self):
        print(f'count_del: {self._count}')

    def count_ref(self):
        cls = self.__class__
        return property(fget=cls.count_get, fset=cls.count_set, fdel=cls.count_del)

counter = Counter()

counter.count
for i in range(5):
    counter.count = i
del counter.count

'''
output
======
count_get: 0
count_set: 0
count_set: 1
count_set: 2
count_set: 3
count_set: 4
count_del: 4
'''

我最近遇到了一个类似的问题,我提出的解决方案使用__getattr__和__setattr__为我想要它处理的属性,其他一切都传递给原始。

class C(object):
    def __init__(self, properties):
        self.existing = "Still Here"
        self.properties = properties

    def __getattr__(self, name):
        if "properties" in self.__dict__ and name in self.properties:
            return self.properties[name] # Or call a function, etc
        return self.__dict__[name]

    def __setattr__(self, name, value):
        if "properties" in self.__dict__ and name in self.properties:
            self.properties[name] = value
        else:
            self.__dict__[name] = value

if __name__ == "__main__":
    my_properties = {'a':1, 'b':2, 'c':3}
    c = C(my_properties)
    assert c.a == 1
    assert c.existing == "Still Here"
    c.b = 10
    assert c.properties['b'] == 10