目标是创建一个行为类似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

其他回答

为了回答你的问题,你需要一个来自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__,使其为只读。

class C(object):
    def __init__(self, keys, values):
        for (key, value) in zip(keys, values):
            self.__dict__[key] = value

    def __setattr__(self, name, value):
        raise Exception("It is read only!")

Tada。

>>> c = C('abc', [1,2,3])
>>> c.a
1
>>> c.b
2
>>> c.c
3
>>> c.d
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'C' object has no attribute 'd'
>>> c.d = 42
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in __setattr__
Exception: It is read only!
>>> c.a = 'blah'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in __setattr__
Exception: It is read only!

扩展了kjfletch的想法

# This is my humble contribution, extending the idea to serialize
# data from and to tuples, comparison operations and allowing functions
# as default values.

def Struct(*args, **kwargs):
    FUNCTIONS = (types.BuiltinFunctionType, types.BuiltinMethodType, \
                 types.FunctionType, types.MethodType)
    def init(self, *iargs, **ikwargs):
        """Asume that unamed args are placed in the same order than
        astuple() yields (currently alphabetic order)
        """
        kw = list(self.__slots__)

        # set the unnamed args
        for i in range(len(iargs)):
            k = kw.pop(0)
            setattr(self, k, iargs[i])

        # set the named args
        for k, v in ikwargs.items():
            setattr(self, k, v)
            kw.remove(k)

        # set default values
        for k in kw:
            v = kwargs[k]
            if isinstance(v, FUNCTIONS):
                v = v()
            setattr(self, k, v)

    def astuple(self):
        return tuple([getattr(self, k) for k in self.__slots__])

    def __str__(self):
        data = ['{}={}'.format(k, getattr(self, k)) for k in self.__slots__]
        return '<{}: {}>'.format(self.__class__.__name__, ', '.join(data))

    def __repr__(self):
        return str(self)

    def __eq__(self, other):
        return self.astuple() == other.astuple()

    name = kwargs.pop("__name__", "MyStruct")
    slots = list(args)
    slots.extend(kwargs.keys())
    # set non-specific default values to None
    kwargs.update(dict((k, None) for k in args))

    return type(name, (object,), {
        '__init__': init,
        '__slots__': tuple(slots),
        'astuple': astuple,
        '__str__': __str__,
        '__repr__': __repr__,
        '__eq__': __eq__,
    })


Event = Struct('user', 'cmd', \
               'arg1', 'arg2',  \
               date=time.time, \
               __name__='Event')

aa = Event('pepe', 77)
print(aa)
raw = aa.astuple()

bb = Event(*raw)
print(bb)

if aa == bb:
    print('Are equals')

cc = Event(cmd='foo')
print(cc)

输出:

<Event: user=pepe, cmd=77, arg1=None, arg2=None, date=1550051398.3651814>
<Event: user=pepe, cmd=77, arg1=None, arg2=None, date=1550051398.3651814>
Are equals
<Event: user=None, cmd=foo, arg1=None, arg2=None, date=1550051403.7938335>

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

#!/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
'''

动态附加属性的唯一方法是用新属性创建一个新类及其实例。

class Holder: p = property(lambda x: vs[i], self.fn_readonly)
setattr(self, k, Holder().p)