目标是创建一个行为类似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__方法中提出了一个替代方案?


当前回答

如果需求是基于某些实例属性动态生成属性,那么下面的代码可能会有用:

import random  

class Foo:
    def __init__(self, prop_names: List[str], should_property_be_zero: bool = False) -> None:
        self.prop_names = prop_names
        self.should_property_be_zero = should_property_be_zero
        
    def create_properties(self):
        for name in self.prop_names:
            setattr(self.__class__, name, property(fget=lambda self: 0 if self.should_property_be_zero else random.randint(1, 100)))

需要注意的重要一点是使用setattr(self。__class__进行名称,…),而不是setattr(自我、名称…)

使用的例子:

In [261]: prop_names = ['a', 'b']

In [262]: ff = Foo(prop_names=prop_names, should_property_be_zero=False)

In [263]: ff.create_properties()

In [264]: ff.a
Out[264]: 10

In [265]: ff.b
Out[265]: 37

In [266]: ft = Foo(prop_names=prop_names, should_property_be_zero=True)

In [267]: ft.create_properties()

In [268]: ft.a
Out[268]: 0

In [269]: ft.b
Out[269]: 0

设置属性将引发AttributeError:不能按预期设置属性:

In [270]: ff.a = 5
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-270-5f9cad5b617d> in <module>
----> 1 ff.a = 5

AttributeError: can't set attribute

In [271]: ft.a = 5
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-271-65e7b8e25b67> in <module>
----> 1 ft.a = 5

AttributeError: can't set attribute

其他回答

最好的方法是定义__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'

对我有用的是:

class C:
    def __init__(self):
        self._x=None

    def g(self):
        return self._x

    def s(self, x):
        self._x = x

    def d(self):
        del self._x

    def s2(self,x):
        self._x=x+x

    x=property(g,s,d)


c = C()
c.x="a"
print(c.x)

C.x=property(C.g, C.s2)
C.x=C.x.deleter(C.d)
c2 = C()
c2.x="a"
print(c2.x)

输出

a
aa

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

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

这很简单:

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

为了回答你的问题,你需要一个来自dict的只读属性作为不可变数据源:

目标是创建一个行为类似db结果集的模拟类。 例如,如果一个数据库查询返回一个dict表达式, {'ab':100, 'cd':200},那么我将看到 > > > dummy.ab One hundred.

我将演示如何使用collections模块中的namedtuple来实现这一点:

import collections

data = {'ab':100, 'cd':200}

def maketuple(d):
    '''given a dict, return a namedtuple'''
    Tup = collections.namedtuple('TupName', d.keys()) # iterkeys in Python2
    return Tup(**d)

dummy = maketuple(data)
dummy.ab

返回100

这里有一个解决方案:

允许将属性名指定为字符串,因此它们可以来自一些外部数据源,而不是全部列在程序中。 在定义类时添加属性,而不是每次创建对象时添加属性。

定义类之后,你只需动态地向它添加一个属性:

setattr(SomeClass, 'propertyName', property(getter, setter))

下面是一个完整的例子,用Python 3测试过:

#!/usr/bin/env python3

class Foo():
  pass

def get_x(self):
  return 3

def set_x(self, value):
  print("set x on %s to %d" % (self, value))

setattr(Foo, 'x', property(get_x, set_x))

foo1 = Foo()
foo1.x = 12
print(foo1.x)