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

Python 使用动态创建的函数动态添加属性,python,dynamic,properties,lambda,setattr,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 灵感来自: 和 ,我实现了以下代码: class Note: def __init__(self,name,size): s

我想实现如下工作方式:

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

notebook = Notebook(memo,report)

print str(notebook.memo) # 5 expected
print str(notebook.report) # 20 expected
灵感来自: 和 ,我实现了以下代码:

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
注意:我知道对于这种最低限度的代码,使用
\uuuuu new\uuuuuu
而不是
\uuuu init\uuuuuu
是不合理的,但这将在以后我使用
Notebook
的子类时需要

如果我使用版本1: 1.它不打印
5
20
,而是打印
20
20
。我不明白为什么。打印函数将显示具有不同地址的函数数组。 2.我使用的
\uuuuu class\uuuuuuu
灵感来自上面给出的博客条目,但我不确定它的作用。它使属性成为类属性?(对我来说,这真的很糟糕)

如果我使用版本2: 在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 !
我猜这是意料之中的,因为属性被添加到了类中。。。。
无论如何,要克服这个问题?

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

>>> lambdas = [(lambda: i) for i in range(5)]
>>> for lamb in lambdas:
...     print(lamb())
... 
4
4
4
4
4
请注意,所有
lambda
s均指上次迭代中假定的
i
值。 创建函数时,python会将闭包与之关联,这会告诉解释器函数应该使用哪些非局部变量:

>>> lambdas[0].__closure__[0]
<cell at 0x7f675ab2dc90: int object at 0x9451e0>
如果要引用以前的值,可以对参数使用默认值:

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

关于第二个版本<代码>属性
是作为一个(另请参见答案)实现的,因此必须在类中设置它才能正常工作。其他装饰器也是如此,例如
staticmethod
classmethod
。正如您所观察到的,将它们放在实例中只会返回
属性
对象


该行:

setattr(notebook,'_notes',{note.name : note.size for note in notes})
可以安全地更改为更简单、更可读的:

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

不过要多吃点东西。为了简单地在第一组代码中获得您想要做的事情,您可以不需要所有额外的技巧就可以做到这一点

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

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

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
也可以按照您提供的链接中的建议,使每个笔记类成为带有前导下划线的笔记本的成员(即Notebook.\u memo),然后为Notebook创建一个属性,将笔记名称链接到大小(即Notebook.memo将链接到Notebook.\u 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
然而,当你将笔记类放入笔记本时,你似乎正在删除笔记类,这样你就可以做一些更简单的事情:

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
为了更有用,我真的需要知道你的目标,或者你想把这个带到哪里的大致想法。以这种奇怪的方式设置属性似乎令人困惑,但在创建类时只做一次,而不是动态添加和删除属性


希望这有所帮助

非常感谢您对该功能的解释。我第一次使用lambda时,这确实很棘手,非常感谢。第一个版本非常有希望,因为它不直接返回大小,我仍然可以对它应用一些代码(并且会)。问题是,我无法管理多个笔记本实例。我会尝试编辑我的问题来指出这个问题。我看到了这些问题,我应该在发布之前考虑一下。用其他解决方案和想法更新上述答案。
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
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