使用动态创建的函数动态添加属性

时间:2014-03-11 12:16:45

标签: python dynamic properties lambda setattr

我想实现一些像这样工作的东西:

memo = Note("memo",5)
report = Note("report",20)

notebook = Notebook(memo,report)

print str(notebook.memo) # 5 expected
print str(notebook.report) # 20 expected

灵感来自: http://znasibov.info/blog/html/2010/03/10/python-classes-dynamic-properties.htmlHow to implement property() with dynamic name (in python) ,我实现了以下代码:

class Note:
    def __init__(self,name,size):
        self.name = name
        self.size = size

class Notebook(object):
    def __new__(cls,*notes):
        notebook = object.__new__(cls)
        setattr(notebook,'_notes',{note.name : note.size for note in notes})
        functions = [lambda notebook : notebook._notes[note.name] for note in notes]
        for note,function in zip(notes,functions) :
            #version 1
            setattr(notebook.__class__, note.name, property(function))
            #version 2 -- note : __class__ removed
            #setattr(notebook, note.name, property(function))
        return notebook

注意:我知道这个最小代码使用__new__而不是__init__是不合理的,但是当我使用Notebook

的子类时,这将需要使用

如果我使用版本1: 1.而不是打印520,而是打印2020。我不明白为什么。打印功能显示具有不同地址的一系列功能。 2.我使用上面给出的博客条目启发的__class__,但我不确定它的作用。它使该属性成为一个类属性? (在我的情况下这将是非常糟糕的)

如果我使用版本2: 打印类似property object at 0x7fb86a9d9b50的内容。 这似乎是有道理的,但我不确定我理解为什么它不会为版本1打印相同的东西。

有没有办法解决这个问题,使用任一版本(或其他完全不同的方法)?


修改

提出了一个解决这个问题的有趣答案。这里有相应的代码:

class Note:
    def __init__(self,name,value):
        self.name = name
        self.size = value
    def _get_size(self,notebook_class=None): return self.size+1

class Notebook(object):
    def __new__(cls,*notes):
        notebook = object.__new__(cls)
        notebook._notes = {note.name : note.size for note in notes}
        for note in notes : setattr(notebook.__class__, note.name, property(note._get_size))
        return notebook

问题是:现在这个测试代码没有提供所需的输出:

memo1 = Note("memo",5)
report1 = Note("report",20)
notebook1 = Notebook(memo1,report1)
print str(notebook1.memo) # print 6 as expected (function of Note return size+1)
print str(notebook1.report) # print 21 as expected

memo2 = Note("memo",35)
report2 = Note("report",40)
notebook2 = Notebook(memo2,report2)
print str(notebook2.memo) # print 36 as expected
print str(notebook2.report) # print 41 expected

print str(notebook1.memo) # does not print 6 but 36 !
print str(notebook1.report) # does not print 21 but 41 !

我想这是因为该物业被添加到班级...... 无论如何要克服这个问题?

2 个答案:

答案 0 :(得分:1)

在循环中创建函数很棘手:

>>> lambdas = [(lambda: i) for i in range(5)]
>>> for lamb in lambdas:
...     print(lamb())
... 
4
4
4
4
4

请注意,所有lambda都指的是i在上一次迭代中假设的值。 当你创建一个函数时,python将一个闭包关联到它,它告诉解释器函数应该使用哪些非局部变量:

>>> lambdas[0].__closure__[0]
<cell at 0x7f675ab2dc90: int object at 0x9451e0>

但它指的是变量,而不是定义函数时包含的实际对象。这将需要更复杂的功能框架处理。

这意味着以下迭代会更改此单元格中包含的值,最后只有最后一次迭代才有意义:

>>> lambdas[0].__closure__[0].cell_contents
4

如果要引用先前的值,可以使用参数的默认值:

>>> lambdas = [(lambda i=i: i) for i in range(5)]
>>> for lamb in lambdas:
...     print(lamb())
... 
0
1
2
3
4

关于第二个版本。 property已实现为descriptor(另请参阅this答案),因此必须在课程中设置必须才能使其正常工作。其他装饰者也是如此,例如staticmethodclassmethod。如您所见,将它们放在实例中只会返回property对象。


该行:

setattr(notebook,'_notes',{note.name : note.size for note in notes})

可以安全地更改为更简单,更易读:

notebook._notes = {note.name : note.size for note in notes}

答案 1 :(得分:1)

虽然有更多的食物。要在第一组代码中简单地获取您想要执行的操作,您可以在没有所有额外技巧的情况下执行此操作。

最简单的方法是直接将属性设置为所需的属性。 (代码合并在不正当的庄园只是为了节省空间)

class Note:
    def __init__(self, name, value): self.name, self._size = name, value
    size = property(lambda x: x._size+1)

class Notebook(object):
    def __new__(cls, *notes):
        notebook = object.__new__(cls)
        notebook._notes = {note.name: note.size for note in notes}
        for note in notes: setattr(notebook, note.name, note.size)
        return notebook


memo1, report1 = Note("memo", 5), Note("report", 20)
notebook1 = Notebook(memo1, report1)

print(notebook1.memo, notebook1.report) # 6 21

memo2, report2 = Note("memo", 35), Note("report", 40)
notebook2 = Notebook(memo2,report2)

print(notebook2.memo, notebook2.report) # 36 41
print(notebook1.memo, notebook1.report) # 6 21
notebook1.memo += 5
print(notebook1.memo) # 11
print(memo1.size) # 6
memo1.size += 5 # AttributeError: can't set attribute

第二种方法是让笔记本成为你传递给它的所有笔记的容器。这样它只会更新原始的类对象,基本上只是它们的持有者。

class Note2(object):
    def __init__(self, name, value): self.name, self._size = name, value
    def _set_size(self, value): self._size = value
    size = property(lambda x: x._size+1, _set_size)
    def __repr__(self): return str(self.size) #simple trick to gain visual access to .size

class Notebook2(object):
    def __new__(cls, *notes):
        notebook = object.__new__(cls)
        notebook._notes = {note.name: note.size for note in notes}
        for note in notes: setattr(notebook, note.name, note)
        return notebook

memo1, report1 = Note2("memo", 5), Note2("report", 20)
notebook1 = Notebook2(memo1, report1)
print(notebook1.memo, notebook1.report) # 6 21
memo2, report2 = Note2("memo", 35), Note2("report", 40)
notebook2 = Notebook2(memo2, report2)
print( notebook2.memo, notebook2.report) # 36 41
print(notebook1.memo, notebook1.report) # 6 21
notebook1.memo.size += 16
print(notebook1.memo) # 23
print(memo1) # 23, Notice this will also set the original objects value to the new value as well
notebook1.memo += 15 # TypeError: unsupported operand type(s) for +=: 'Note2' and 'int' - It is true without making it as a property does make it less effective to work with

也应该可以像你提供的链接那样建议使每个Note类成为Notebook的成员,带有前导下划线(即notebook._memo),然后为Notebook创建一个属性,将Note名称链接到size(即notebook.memo将是notebook._memo.size的链接。希望这些例子有所帮助。


原始答案。

有趣的想法,简单地让它在这里工作是你的原始版本的黑客:

class Note(object):
    def __init__(self,name, size):
        self.name = name
        self._size = size

    def _get_size(self, notebook_class=None):
        return self._size

    def _set_size(self, notebook_class=None, size=0):
        self._size = size

class Notebook(object):
    def __new__(cls,*notes):
        notebook = object.__new__(cls)
        for note in notes:
            setattr(notebook.__class__, note.name, property(note._get_size, note._set_size))
        return notebook

然而,当你将它们摄入到Notebook中时,你似乎正在删除每个Note类,这样你就可以更轻松地做一些事情了:

class Note(object):
    def __init__(self, name, size):
        self.name = name
        self.size = size

class Notebook(object):
    def __new__(cls, *notes):
        notebook = object.__new__(cls)
        for note in notes:
            setattr(notebook.__class__, note.name, note.size)
        return notebook

为了更有帮助,我真的需要知道你想要把它带到哪里的目标或一般概念。以这种奇怪的方式设置属性似乎令人困惑,但是在创建类时只执行一次,而不是动态添加和删除它们的示例。

希望这有帮助