1

次のように機能するものを実装したいと思います。

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.html および 動的な名前で property() を実装する方法 (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. を使用すると、印刷される代わりに、5and20が印刷され20ます20。理由がわかりません。関数を印刷すると、アドレスが異なる関数の配列が表示されます。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 !

プロパティがクラスに追加されたので、これは予想されていたことだと思います.... とにかく、この問題を克服するには?

4

2 に答える 2

1

ループ内で関数を作成するのは難しい:

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

すべての s は、最後の反復で想定され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として実装されているため(この回答も参照)、適切に機能させるにはクラスに設定する必要があります。やなどの他のデコレータについても同様です。あなたが観察したように、それらをインスタンスに入れると、オブジェクトが返されます。staticmethodclassmethodproperty


この線:

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

よりシンプルで読みやすいものに安全に変更できます。

notebook._notes = {note.name : note.size for note in notes}
于 2014-03-11T13:01:32.993 に答える
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

2 番目の方法は、ノートブックを文字どおり、渡されたすべてのノートのコンテナーにすることです。この方法では、元のクラス オブジェクトを単純に更新し、基本的にそれらのホルダーにすぎません。

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) を行うことも可能である必要があります。 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

もっと役に立つためには、あなたがこれをどこに取りたいかという目標または一般的な考えを本当に知る必要があります. このような奇妙な方法でプロパティを設定するのは混乱しているように見えますが、動的に追加および削除できる例とは対照的に、クラスの作成時に一度だけ設定します。

これが役に立ったことを願っています

于 2014-03-11T13:30:21.303 に答える