it-swarm.com.de

Wie füge ich einer Klasse dynamisch eine Eigenschaft hinzu?

Ziel ist es, eine Mock-Klasse zu erstellen, die sich wie eine Db-Ergebnismenge verhält.

Wenn zum Beispiel eine Datenbankabfrage mit einem Dict-Ausdruck {'ab':100, 'cd':200} zurückgegeben wird, möchte ich Folgendes sehen: 

>>> dummy.ab
100

Zuerst dachte ich, vielleicht könnte ich es so machen:

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 gibt stattdessen ein Eigenschaftsobjekt zurück.

Das Ersetzen der setattr-Zeile durch k = property(lambda x: vs[i]) ist überhaupt nicht von Nutzen.

Was ist also der richtige Weg, um eine Instanz-Eigenschaft zur Laufzeit zu erstellen?

P.S. Ich kenne eine Alternative aus Wie wird die __getattribute__-Methode verwendet?

167
Anthony Kong

Ich nehme an, ich sollte diese Antwort erweitern, jetzt wo ich älter und weiser bin und weiß, was los ist. Besser spät als nie.

Sie können einer Klasse dynamisch eine Eigenschaft hinzufügen. Aber das ist der Haken: Sie müssen es der Klasse hinzufügen.

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

Ein property ist eigentlich eine einfache Implementierung eines Dings, das als Deskriptor bezeichnet wird. Es ist ein Objekt, das eine benutzerdefinierte Behandlung für ein bestimmtes Attribut bietet, für eine bestimmte Klasse. Ein bisschen wie ein Weg, einen riesigen if Baum aus __getattribute__ zu faktorisieren.

Wenn ich im obigen Beispiel nach foo.b frage, sieht Python, dass das in der Klasse definierte b das Deskriptorprotokoll - implementiert, was nur bedeutet, dass es sich um ein Objekt mit handelt eine __get__, __set__ oder __delete__ Methode. Der Deskriptor übernimmt die Verantwortung für die Behandlung dieses Attributs. Daher ruft Python Foo.b.__get__(foo, Foo) auf und der Rückgabewert wird als Wert des Attributs an Sie zurückgegeben. Im Fall von property ruft jede dieser Methoden nur den Konstruktor fget, fset oder fdel auf, den Sie an den Konstruktor property übergeben haben.

Deskriptoren sind in Wirklichkeit Pythons Methode, die Installation der gesamten OO -Implementierung aufzudecken. In der Tat gibt es eine andere Art von Deskriptor, die noch häufiger vorkommt als 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>

Die bescheidene Methode ist nur eine andere Art von Deskriptor. Sein __get__ greift die aufrufende Instanz als erstes Argument an; in der Tat macht es das:

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

Ich vermute jedenfalls, dass Deskriptoren deshalb nur für Klassen funktionieren: Sie sind eine Formalisierung des Stoffes, der Klassen überhaupt antreibt. Sie sind sogar die Ausnahme von der Regel: Sie können offensichtlich einer Klasse Deskriptoren zuweisen, und Klassen sind selbst Instanzen von type! Der Versuch, Foo.b zu lesen, ruft property.__get__ auf. Es ist nur idiomatisch, dass Deskriptoren sich selbst zurückgeben, wenn sie als Klassenattribute aufgerufen werden.

Ich finde es ziemlich cool, dass praktisch das gesamte System von Python OO in Python ausgedrückt werden kann. :)

Oh, und ich habe vor einiger Zeit einen wortreichen Blog-Post über Deskriptoren geschrieben, wenn Sie interessiert sind.

299
Eevee

Ziel ist es, eine Mock-Klasse zu erstellen, die sich wie eine Db-Ergebnismenge verhält.

Also wollen Sie ein Wörterbuch, in dem Sie ein ['b'] als a.b buchstabieren können?

Das ist einfach:

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

Es scheint, dass Sie dieses Problem mit einer namedtuple viel einfacher lösen könnten, da Sie die gesamte Liste der Felder im Voraus kennen.

from collections import namedtuple

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

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

foo2 = Foo()  # error

Wenn Sie unbedingt einen eigenen Setter schreiben müssen, müssen Sie die Metaprogrammierung auf Klassenebene durchführen. property() funktioniert nicht bei Instanzen.

33
Eevee

Sie müssen dafür keine Eigenschaft verwenden. Überschreiben Sie einfach __setattr__, um sie schreibgeschützt zu machen.

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

Tada.

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

Wie füge ich eine Eigenschaft dynamisch zu einer Python-Klasse hinzu?

Angenommen, Sie haben ein Objekt, dem Sie eine Eigenschaft hinzufügen möchten. Normalerweise möchte ich Eigenschaften verwenden, wenn ich den Zugriff auf ein Attribut in Code mit nachgeschalteter Verwendung verwalten muss, um eine konsistente API zu erhalten. Jetzt füge ich sie normalerweise dem Quellcode hinzu, in dem das Objekt definiert ist. Nehmen wir jedoch an, Sie haben diesen Zugriff nicht oder Sie müssen Ihre Funktionen wirklich dynamisch programmgesteuert auswählen.

Erstellen Sie eine Klasse

Anhand eines Beispiels basierend auf der Dokumentation für property erstellen wir eine Objektklasse mit einem "hidden" -Attribut und erstellen eine Instanz davon:

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

o = C()

In Python erwarten wir eine naheliegende Vorgehensweise. In diesem Fall zeige ich jedoch zwei Wege: mit der Notation des Dekorators und ohne. Erstens ohne Dekoratorennotation. Dies kann für die dynamische Zuweisung von Gettern, Setters oder Deleters sinnvoller sein.

Dynamisch (a.k.a. Monkey Patching)

Lassen Sie uns einige für unsere Klasse erstellen:

def getx(self):
    return self._x

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

def delx(self):
    del self._x

Und jetzt weisen wir diese der Immobilie zu. Beachten Sie, dass wir unsere Funktionen hier programmatisch auswählen können, um die dynamische Frage zu beantworten:

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

Und Verwendung:

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

    I'm the 'x' property.

Dekorateure

Wir könnten dasselbe wie oben mit der Decorator-Notation tun, aber in diesem Fall müssen must den Methoden alle den gleichen Namen geben (und ich würde empfehlen, den gleichen Wert wie das Attribut beizubehalten) nicht so trivial wie es die obige Methode verwendet:

@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

Und weisen Sie das Eigenschaftsobjekt mit seinen bereitgestellten Setters und Deleters der Klasse zu:

C.x = x

Und Verwendung:

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

Ich stellte eine ähnliche Frage zu diesem Stack Overflow post , um eine Klassenfabrik zu erstellen, die einfache Typen erstellt. Das Ergebnis war diese Antwort die eine funktionierende Version der Klassenfabrik hatte ..__ Hier ist ein Ausschnitt der Antwort:

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

Sie können einige Variationen davon verwenden, um Standardwerte zu erstellen, die Ihr Ziel ist (in dieser Frage gibt es auch eine Antwort, die sich damit befasst).

5
kjfletch

Sie können einer Instanz zur Laufzeit kein neues property() hinzufügen, da es sich bei den Eigenschaften um Datendeskriptoren handelt. Stattdessen müssen Sie dynamisch eine neue Klasse erstellen oder __getattribute__ überladen, um Datenbeschreibungen für Instanzen verarbeiten zu können.

5
Alex Gaynor

Für diejenigen, die von Suchmaschinen kommen, sind hier die zwei Dinge, nach denen ich gesucht habe, wenn sie über dynamic - Eigenschaften sprechen:

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__ ist gut, wenn Sie dynamisch erstellte Eigenschaften einfügen möchten. __getattr__ ist gut, um nur dann etwas zu tun, wenn der Wert benötigt wird, beispielsweise eine Datenbank abfragen. Die Set/Get-Kombination erleichtert den Zugriff auf in der Klasse gespeicherte Daten (wie im obigen Beispiel).

Wenn Sie nur eine dynamische Eigenschaft wünschen, schauen Sie sich die integrierte Funktion property () an.

3
tleb

Ich bin mir nicht sicher, ob ich die Frage vollständig verstanden habe, aber Sie können die Instanzeigenschaften zur Laufzeit mit dem integrierten __dict__ Ihrer Klasse ändern:

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

Am besten erreichen Sie dies durch die Definition von __slots__. Auf diese Weise können Ihre Instanzen keine neuen Attribute haben.

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

Das druckt 12

    c.ab = 33

Das gibt: AttributeError: 'C' object has no attribute 'ab'

2
nosklo

Nur ein weiteres Beispiel, um den gewünschten Effekt zu erzielen

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)

Also können wir jetzt Sachen machen wie:

>>> foo = Foo('baz')
>>> foo.baz = 5
>>> foo.bar
5
>>> foo.baz
5
2
lehins

Das ist ein bisschen anders als das, was OP wollte, aber ich habe mein Gehirn durcheinander gebracht, bis ich eine funktionierende Lösung gefunden habe, also setze ich mich hier für den nächsten Typ ein/gal

Ich brauchte eine Möglichkeit, dynamische Setter und Getter anzugeben.

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)

Ich kenne meine Felder im Voraus, also werde ich meine Eigenschaften schaffen. HINWEIS: Sie können diese PER-Instanz nicht ausführen, diese Eigenschaften sind in der Klasse vorhanden !!!

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

Lass es uns jetzt testen ..

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

Ist es verwirrend? Ja, tut mir leid, ich konnte mir keine aussagekräftigen Beispiele aus der Praxis einfallen lassen. Auch dies ist nichts für Unbeschwerte.

0
Javier Buzzi

Das scheint zu funktionieren (aber siehe unten):

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

Wenn Sie komplexeres Verhalten benötigen, können Sie Ihre Antwort bearbeiten.

bearbeiten

Das Folgende wäre wahrscheinlich für große Datensätze speichereffizienter:

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

Um die Hauptaussage Ihrer Frage zu beantworten, möchten Sie ein schreibgeschütztes Attribut aus einem Dict als unveränderliche Datenquelle:

Ziel ist es, eine Mock-Klasse zu erstellen, die sich wie eine Db-Ergebnismenge verhält.

Wenn beispielsweise eine Datenbankabfrage zurückgegeben wird und ein Dikt-Ausdruck verwendet wird, {'ab':100, 'cd':200}, dann würde ich sehen

>>> dummy.ab
100

Ich zeige Ihnen, wie Sie eine namedtuple aus dem collections-Modul verwenden, um genau dies zu erreichen:

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

gibt 100 zurück

0
Aaron Hall

Sie können den folgenden Code verwenden, um Klassenattribute mithilfe eines Wörterbuchobjekts zu aktualisieren:

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
0

Obwohl viele Antworten gegeben werden, konnte ich keine finden, mit der ich glücklich bin. Ich habe meine eigene Lösung gefunden, die property für den dynamischen Fall geeignet macht. Die Quelle zur Beantwortung der ursprünglichen Frage:

#!/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

Für mich funktioniert Folgendes:

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)

Ausgabe

a
aa
0
prosti

Erweiterung der Idee von 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)

Ausgabe:

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

Und die Ausgabe ist:

>> 1
0
Serhii Khachko