web-dev-qa-db-ja.com

プロパティをクラスに動的に追加する方法は?

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

したがって、たとえば、{'ab':100, 'cd':200} dict式を使用してデータベースクエリが返された場合、次のようになります。

>>> 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 __== "__main__":
    c = C(ks, vs)
    print c.ab

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

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

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

追伸 __getattribute__メソッドはどのように使用されますか?

181
Anthony Kong

私は年長で賢く、何が起こっているかを知っているので、この答えを広げるべきだと思います。絶対に遅れることはありません。

canプロパティをクラスに動的に追加します。しかし、それはキャッチです:classに追加する必要があります。

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

propertyは、実際には descriptor と呼ばれるものの単純な実装です。これは、特定の属性特定のクラスのカスタム処理を提供するオブジェクトです。 __getattribute__から巨大なifツリーを分解する方法が好きです。

上記の例でfoo.bを要求すると、Pythonはクラスで定義されたbdescriptor protocolを実装していることを確認します。つまり、__get____set____delete__メソッド。記述子はその属性を処理する責任を主張するため、PythonはFoo.b.__get__(foo, Foo)を呼び出し、戻り値は属性の値として返されます。 propertyの場合、これらの各メソッドはfgetコンストラクターに渡したfsetfdel、またはpropertyを呼び出すだけです。

記述子は、実際にOO実装全体の配管を公開するPythonの方法です。実際、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.bを読み取ろうとすると、property.__get__が呼び出されます。記述子がクラス属性としてアクセスされたときに自分自身を返すことは単なる慣用​​です。

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

ああ、そして、私は 記述子に関する言葉の多いブログ投稿 興味があるならしばらく前に書いた。

288
Eevee

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

それで、a ['b']をa.bとして綴ることができる辞書が必要ですか?

簡単だ:

class atdict(dict):
    __getattr__= dict.__getitem__
    __setattr__= dict.__setitem__
    __delattr__= dict.__delitem__
51
bobince

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()はインスタンスでは機能しません。

33
Eevee

そのためにプロパティを使用する必要はありません。 __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!
28
Ryan

プロパティをpythonクラスに動的に追加する方法は?

プロパティを追加するオブジェクトがあるとします。通常、一貫性のあるAPIを維持できるように、ダウンストリームで使用されるコード内の属性へのアクセスの管理を開始する必要があるときにプロパティを使用します。通常、オブジェクトが定義されているソースコードにそれらを追加しますが、そのアクセス権がないか、プログラムで関数を本当に動的に選択する必要があると仮定しましょう。

クラスを作成する

propertyのドキュメント に基づいた例を使用して、「hidden」属性を持つオブジェクトのクラスを作成し、そのインスタンスを作成しましょう。

class C(object):
    '''basic class'''
    _x = None

o = C()

Pythonでは、物事を行うための1つの明らかな方法があると期待しています。ただし、この場合、デコレータ表記法を使用する場合と使用しない場合の2つの方法を示します。まず、デコレータ表記なし。これは、getter、setter、またはdeleterの動的割り当てに役立つ場合があります。

ダイナミック(別名モンキーパッチング)

クラス用にいくつか作成しましょう:

def getx(self):
    return self._x

def setx(self, value):
    self._x = value

def delx(self):
    del self._x

そして、これらをプロパティに割り当てます。ここでは、動的な質問に答えて、プログラムで関数を選択できることに注意してください。

C.x = property(getx, setx, delx, "I'm the 'x' property.")

そして使用法:

>>> o.x = 'foo'
>>> o.x
'foo'
>>> del o.x
>>> print(o.x)
None
>>> help(C.x)
Help on property:

    I'm the 'x' property.

デコレータ

上記のデコレータ表記法と同じことができますが、この場合は、mustメソッドにすべて同じ名前を付けます(そして、属性と同じにすることをお勧めします)。そのため、プログラムによる割り当ては上記の方法を使用するほど簡単ではありません。

@property
def x(self):
    '''I'm the 'x' property.'''
    return self._x

@x.setter
def x(self, value):
    self._x = value

@x.deleter
def x(self):
    del self._x

そして、プロビジョンされたセッターと削除機能を持つプロパティオブジェクトをクラスに割り当てます。

C.x = x

そして使用法:

>>> help(C.x)
Help on property:

    I'm the 'x' property.

>>> o.x
>>> o.x = 'foo'
>>> o.x
'foo'
>>> del o.x
>>> print(o.x)
None
5
Aaron Hall

同様の質問をしました このStack Overflowの投稿 単純型を作成するクラスファクトリを作成します。結果は この回答 であり、クラスファクトリの作業バージョンがありました。これが答えの断片です。

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)
>>>

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

5
kjfletch

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

5
Alex Gaynor

検索エンジンから来た人のために、dynamicプロパティについて話すときに私が探していた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() 組み込み関数をご覧ください。

3
tleb

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

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


if __== "__main__":
    ks = ['ab', 'cd']
    vs = [12, 34]
    c = C(ks, vs)
    print(c.ab) # 12
3
Crescent Fresh

達成する最良の方法は、__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 __== "__main__":
    c = C(ks, vs)
    print c.ab

それは12を出力します

    c.ab = 33

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

2
nosklo

望ましい効果を達成する方法の別の例

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
2
lehins

次のコードを使用して、辞書オブジェクトを使用してクラス属性を更新できます。

class ExampleClass():
    def __init__(self, argv):
        for key, val in argv.items():
            self.__dict__[key] = val

if __== '__main__':
    argv = {'intro': 'Hello World!'}
    instance = ExampleClass(argv)
    print instance.intro
1

これは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

紛らわしいですか?はい、申し訳ありませんが、意味のある実世界の例が思いつきませんでした。また、これは心の明るい人のためではありません。

0
Javier Buzzi

これは動作するようです(ただし、以下を参照):

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__)
0
David X

あなたの質問の主な目的に答えるために、不変のデータソースとして辞書からの読み取り専用属性が必要です:

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

たとえば、データベースクエリが返された場合、dict式{'ab':100, 'cd':200}を使用すると、

>>> dummy.ab
100

namedtupleモジュールのcollectionsを使用して、これを実現する方法を示します。

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を返します

0
Aaron Hall

多くの回答がありますが、満足できるものは見つかりませんでした。 propertyを動的なケースで機能させる独自のソリューションを見つけました。元の質問に答えるソース:

#!/usr/local/bin/python3

INITS = { 'ab': 100, 'cd': 200 }

class DP(dict):
  def __init__(self):
    super().__init__()
    for k,v in INITS.items():
        self[k] = v 

def _dict_set(dp, key, value):
  dp[key] = value

for item in INITS.keys():
  setattr(
    DP,
    item,
    lambda key: property(
      lambda self: self[key], lambda self, value: _dict_set(self, key, value)
    )(item)
  )

a = DP()
print(a)  # {'ab': 100, 'cd': 200}
a.ab = 'ab100'
a.cd = False
print(a.ab, a.cd) # ab100 False
0
Rob L

私のために働くものはこれです:

class C:
    def __init__(self):
        self._x=None

    def g(self):
        return self._x

    def s(self, x):
        self._x = x

    def d(self):
        del self._x

    def s2(self,x):
        self._x=x+x

    x=property(g,s,d)


c = C()
c.x="a"
print(c.x)

C.x=property(C.g, C.s2)
C.x=C.x.deleter(C.d)
c2 = C()
c2.x="a"
print(c2.x)

出力

a
aa
0
prosti

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>
0
class atdict(dict):
  def __init__(self, value, **kwargs):
    super().__init__(**kwargs)
    self.__dict = value

  def __getattr__(self, name):
    for key in self.__dict:
      if type(self.__dict[key]) is list:
        for idx, item in enumerate(self.__dict[key]):
          if type(item) is dict:
            self.__dict[key][idx] = atdict(item)
      if type(self.__dict[key]) is dict:
        self.__dict[key] = atdict(self.__dict[key])
    return self.__dict[name]



d1 = atdict({'a' : {'b': [{'c': 1}, 2]}})

print(d1.a.b[0].c)

出力は次のとおりです。

>> 1
0
Serhii Khachko