269

目標は、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

代わりc.abにプロパティ オブジェクトを返します。

setattr行を置き換えてk = property(lambda x: vs[i])もまったく役に立ちません。

では、実行時にインスタンス プロパティを作成する正しい方法は何でしょうか?

PS私は方法がどのように__getattribute__使用されるかで提示された代替案を知っていますか?

4

25 に答える 25

403

私は年をとって賢くなり、何が起こっているのかを知ったので、この答えを拡大する必要があると思います。遅いよりはましです。

プロパティをクラスに動的に追加できますしかし、それが問題です。それをclassに追加する必要があります。

>>> class Foo(object):
...     pass
... 
>>> foo = Foo()
>>> foo.a = 3
>>> Foo.b = property(lambda self: self.a + 1)
>>> foo.b
4

Aは実際には記述子propertyと呼ばれるものの単純な実装です。特定のクラスで、特定の属性のカスタム処理を提供するオブジェクトです。から巨大なツリーを因数分解する方法のようなものです。if__getattribute__

上記の例で要求するとfoo.b、Python はb、クラスで定義された が記述子プロトコル__get__を実装していることを認識します。これは、 、__set__、または__delete__メソッドを持つオブジェクトであることを意味します。記述子はその属性を処理する責任があると主張するため、Python は を呼び出しFoo.b.__get__(foo, Foo)、戻り値は属性の値として返されます。の場合、これらの各メソッドは、コンストラクターに渡した、、またはをproperty呼び出すだけです。fgetfsetfdelproperty

記述子は、実際には Python の OO 実装全体の配管を公開する方法です。実際、より一般的な別のタイプの記述子がありpropertyます。

>>> class Foo(object):
...     def bar(self):
...         pass
... 
>>> Foo().bar
<bound method Foo.bar of <__main__.Foo object at 0x7f2a439d5dd0>>
>>> Foo().bar.__get__
<method-wrapper '__get__' of instancemethod object at 0x7f2a43a8a5a0>

謙虚な方法は、単なる別の種類の記述子です。最初__get__の引数として呼び出し元のインスタンスを追跡します。実際には、これは次のことを行います。

def __get__(self, instance, owner):
    return functools.partial(self.function, instance)

とにかく、これが記述子がクラスでのみ機能する理由だと思います。記述子は、そもそもクラスに力を与えるものの形式化です。それらは規則の例外でさえあります: 明らかにディスクリプタをクラスに割り当てることができ、クラスはそれ自体がtype!のインスタンスです。Foo.bar実際、まだ呼び出しを読み取ろうとしていますproperty.__get__。クラス属性としてアクセスされたときに記述子が自分自身を返すのは慣用的です。

Python の OO システムのほぼすべてを Python で表現できるというのは、かなりクールだと思います。:)

ああ、興味があるなら、私はしばらく前に記述子について長文のブログ投稿を書きました。

于 2009-08-31T01:30:05.797 に答える
64

目標は、db 結果セットのように動作するモック クラスを作成することです。

では、a['b'] を ab と綴ることができる辞書が必要ですか?

簡単だ:

class atdict(dict):
    __getattr__= dict.__getitem__
    __setattr__= dict.__setitem__
    __delattr__= dict.__delitem__
于 2009-08-25T14:33:07.500 に答える
41

そのためにプロパティを使用する必要はありません。オーバーライド__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!")

多田。

>>> 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!
于 2009-08-25T02:41:46.710 に答える
40

namedtupleフィールドのリスト全体を前もって知っているので、この問題は を使ってもっと簡単に解決できるようです。

from collections import namedtuple

Foo = namedtuple('Foo', ['bar', 'quux'])

foo = Foo(bar=13, quux=74)
print foo.bar, foo.quux

foo2 = Foo()  # error

どうしても独自のセッターを作成する必要がある場合は、クラス レベルでメタプログラミングを行う必要があります。property()インスタンスでは機能しません。

于 2009-08-25T02:28:05.873 に答える
6

検索エンジンから来た人のために、動的プロパティについて話すときに私が探していた 2 つのことを次に示します。

class Foo:
    def __init__(self):
        # we can dynamically have access to the properties dict using __dict__
        self.__dict__['foo'] = 'bar'

assert Foo().foo == 'bar'


# or we can use __getattr__ and __setattr__ to execute code on set/get
class Bar:
    def __init__(self):
        self._data = {}
    def __getattr__(self, key):
        return self._data[key]
    def __setattr__(self, key, value):
        self._data[key] = value

bar = Bar()
bar.foo = 'bar'
assert bar.foo == 'bar'

__dict__動的に作成されたプロパティを配置する場合に適しています。__getattr__データベースのクエリなど、値が必要な場合にのみ何かを行うのに適しています。set/get コンボは、クラスに格納されたデータへのアクセスを簡素化するのに適しています (上記の例のように)。

動的プロパティが 1 つだけ必要な場合は、組み込み関数property()を参照してください。

于 2016-08-10T12:58:15.457 に答える
5

このスタック オーバーフローの投稿で同様の質問をして、単純な型を作成するクラス ファクトリを作成しました。結果は、クラスファクトリの動作バージョンを持つこの回答でした。ここに答えのスニペットがあります:

def Struct(*args, **kwargs):
    def init(self, *iargs, **ikwargs):
        for k,v in kwargs.items():
            setattr(self, k, v)
        for i in range(len(iargs)):
            setattr(self, args[i], iargs[i])
        for k,v in ikwargs.items():
            setattr(self, k, v)

    name = kwargs.pop("name", "MyStruct")
    kwargs.update(dict((k, None) for k in args))
    return type(name, (object,), {'__init__': init, '__slots__': kwargs.keys()})

>>> Person = Struct('fname', 'age')
>>> person1 = Person('Kevin', 25)
>>> person2 = Person(age=42, fname='Terry')
>>> person1.age += 10
>>> person2.age -= 10
>>> person1.fname, person1.age, person2.fname, person2.age
('Kevin', 35, 'Terry', 32)
>>>

これのいくつかのバリエーションを使用して、目標であるデフォルト値を作成できます(これを扱う質問にも回答があります)。

于 2009-08-26T09:15:12.857 に答える
5

質問を完全に理解しているかどうかはわかりませんが__dict__、クラスの組み込みを使用して実行時にインスタンス プロパティを変更できます。

class C(object):
    def __init__(self, ks, vs):
        self.__dict__ = dict(zip(ks, vs))


if __name__ == "__main__":
    ks = ['ab', 'cd']
    vs = [12, 34]
    c = C(ks, vs)
    print(c.ab) # 12
于 2009-08-25T02:19:32.473 に答える
4

property()プロパティはデータ記述子であるため、実行時にインスタンスにnew を追加することはできません。__getattribute__代わりに、インスタンスのデータ記述子を処理するために、新しいクラスまたはオーバーロードを動的に作成する必要があります。

于 2009-08-25T02:23:36.490 に答える
2

達成する最善の方法は、 を定義すること__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

それは与える:AttributeError: 'C' object has no attribute 'ab'

于 2009-08-25T03:01:35.260 に答える
2

これはOPが望んでいたものとは少し異なりますが、実用的な解決策が得られるまで頭を悩ませたので、次の男/ギャルのためにここに入れます.

動的なセッターとゲッターを指定する方法が必要でした。

class X:
    def __init__(self, a=0, b=0, c=0):
        self.a = a
        self.b = b
        self.c = c

    @classmethod
    def _make_properties(cls, field_name, inc):
        _inc = inc

        def _get_properties(self):
            if not hasattr(self, '_%s_inc' % field_name):
                setattr(self, '_%s_inc' % field_name, _inc)
                inc = _inc
            else:
                inc = getattr(self, '_%s_inc' % field_name)

            return getattr(self, field_name) + inc

        def _set_properties(self, value):
            setattr(self, '_%s_inc' % field_name, value)

        return property(_get_properties, _set_properties)

私は自分のフィールドを前もって知っているので、自分のプロパティを作成します。注: この PER インスタンスを実行することはできません。これらのプロパティはクラスに存在します!!!

for inc, field in enumerate(['a', 'b', 'c']):
    setattr(X, '%s_summed' % field, X._make_properties(field, inc))

今すぐすべてをテストしましょう..

x = X()
assert x.a == 0
assert x.b == 0
assert x.c == 0

assert x.a_summed == 0  # enumerate() set inc to 0 + 0 = 0
assert x.b_summed == 1  # enumerate() set inc to 1 + 0 = 1
assert x.c_summed == 2  # enumerate() set inc to 2 + 0 = 2

# we set the variables to something
x.a = 1
x.b = 2
x.c = 3

assert x.a_summed == 1  # enumerate() set inc to 0 + 1 = 1
assert x.b_summed == 3  # enumerate() set inc to 1 + 2 = 3
assert x.c_summed == 5  # enumerate() set inc to 2 + 3 = 5

# we're changing the inc now
x.a_summed = 1 
x.b_summed = 3 
x.c_summed = 5

assert x.a_summed == 2  # we set inc to 1 + the property was 1 = 2
assert x.b_summed == 5  # we set inc to 3 + the property was 2 = 5
assert x.c_summed == 8  # we set inc to 5 + the property was 3 = 8

紛らわしいですか?はい、申し訳ありませんが、意味のある実際の例を思いつくことができませんでした。また、これは気楽な人向けではありません。

于 2019-07-31T20:32:24.180 に答える
2

望ましい効果を達成する方法のもう 1 つの例

class Foo(object):

    _bar = None

    @property
    def bar(self):
        return self._bar

    @bar.setter
    def bar(self, value):
        self._bar = value

    def __init__(self, dyn_property_name):
        setattr(Foo, dyn_property_name, Foo.bar)

これで、次のようなことができます。

>>> foo = Foo('baz')
>>> foo.baz = 5
>>> foo.bar
5
>>> foo.baz
5
于 2014-05-09T21:41:06.130 に答える
0

これは機能しているようです(ただし、以下を参照してください)。

class data(dict,object):
    def __init__(self,*args,**argd):
        dict.__init__(self,*args,**argd)
        self.__dict__.update(self)
    def __setattr__(self,name,value):
        raise AttributeError,"Attribute '%s' of '%s' object cannot be set"%(name,self.__class__.__name__)
    def __delattr__(self,name):
        raise AttributeError,"Attribute '%s' of '%s' object cannot be deleted"%(name,self.__class__.__name__)

より複雑な動作が必要な場合は、自由に回答を編集してください。

編集

大規模なデータセットの場合、おそらく次の方がメモリ効率が高くなります。

class data(dict,object):
    def __init__(self,*args,**argd):
        dict.__init__(self,*args,**argd)
    def __getattr__(self,name):
        return self[name]
    def __setattr__(self,name,value):
        raise AttributeError,"Attribute '%s' of '%s' object cannot be set"%(name,self.__class__.__name__)
    def __delattr__(self,name):
        raise AttributeError,"Attribute '%s' of '%s' object cannot be deleted"%(name,self.__class__.__name__)
于 2009-08-26T08:50:30.597 に答える
0

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>
于 2019-02-13T09:56:15.677 に答える
-1

私は最近、同様の問題に遭遇しました。私が思いついた解決策は、使用し__getattr____setattr__処理したいプロパティに対して、他のすべてがオリジナルに渡されます。

class C(object):
    def __init__(self, properties):
        self.existing = "Still Here"
        self.properties = properties

    def __getattr__(self, name):
        if "properties" in self.__dict__ and name in self.properties:
            return self.properties[name] # Or call a function, etc
        return self.__dict__[name]

    def __setattr__(self, name, value):
        if "properties" in self.__dict__ and name in self.properties:
            self.properties[name] = value
        else:
            self.__dict__[name] = value

if __name__ == "__main__":
    my_properties = {'a':1, 'b':2, 'c':3}
    c = C(my_properties)
    assert c.a == 1
    assert c.existing == "Still Here"
    c.b = 10
    assert c.properties['b'] == 10
于 2014-04-29T17:52:55.890 に答える
-2

プロパティを動的にアタッチする唯一の方法は、新しいプロパティで新しいクラスとそのインスタンスを作成することです。

class Holder: p = property(lambda x: vs[i], self.fn_readonly)
setattr(self, k, Holder().p)
于 2009-08-25T02:29:19.117 に答える