Seite 1 von 2
Objekt teilweise updaten
Verfasst: Dienstag 26. November 2024, 15:03
von mechanicalStore
Hallo Zusammen,
ich suche einen einfacheren Weg, nur bestimmte Attribute in einem Objekt zu aktualisieren, alle anderen Attribute sollen ihre Werte behahalten. Welche ich aendere und welche nicht, entscheidet sich ebenfalls erst zur Laufzeit. Mit default-werten und optionalen Argumenten komme ich nicht weiter, da dann alles andere ueberschrieben wird. Folgender (sehr umstaendlicher) Ansatz geht zwar, aber gibt es einen einfacheren Weg?
from pprint import pprint
Code: Alles auswählen
class Foo:
def __init__(self, bar, bridge, street):
self.bar = bar
self.bridge = bridge
self.street = street
def update(self, d: dict):
for key, value in d.items():
# print(key)
setattr(self, key, value)
def main():
f = Foo('Test-Bar', 'Test-Bridge', 'Test-Street')
pprint(vars(f))
d = dict(bar = "new_bar", bridge = "new-bridge")
f.update(d)
pprint(vars(f))
if __name__ == '__main__':
main()
Re: Objekt teilweise updaten
Verfasst: Dienstag 26. November 2024, 15:30
von __blackjack__
@mechanicalStore: Ich sehe jetzt gerade nicht wo das ”sehr umständlich” sein soll?
Re: Objekt teilweise updaten
Verfasst: Dienstag 26. November 2024, 15:36
von pillmuncher
@mechanicalStore:
Code: Alles auswählen
from pprint import pprint
class Foo:
def __init__(self, bar, bridge, street):
self.bar = bar
self.bridge = bridge
self.street = street
def main():
f = Foo("Test-Bar", "Test-Bridge", "Test-Street")
pprint(vars(f))
d = dict(bar="new_bar", bridge="new-bridge")
f.__dict__.update(d) # <-------------------
pprint(vars(f))
if __name__ == "__main__":
main()
Siehe auch
hier.
Re: Objekt teilweise updaten
Verfasst: Dienstag 26. November 2024, 15:53
von __blackjack__
Dazu muss es aber auch ein `__dict__` geben. Das haben nicht alle in C implementierten Datentypen und auch nicht wenn man `__slots__` verwendet. Alternative Python-Implementierungen müssen das auch nicht haben. Ich würde da bei Schleife und `setattr()` bleiben.
Re: Objekt teilweise updaten
Verfasst: Dienstag 26. November 2024, 16:10
von Sirius3
`dataclass` bringt bereits eine `replace`-Methode mit, die zusätzlich den Vorteil hat, dass sie das Objekt nicht ändert, sondern ein geändertes Objekt zurückgibt.
Re: Objekt teilweise updaten
Verfasst: Dienstag 26. November 2024, 16:34
von pillmuncher
__blackjack__ hat geschrieben: Dienstag 26. November 2024, 15:53
Dazu muss es aber auch ein `__dict__` geben. Das haben nicht alle in C implementierten Datentypen und auch nicht wenn man `__slots__` verwendet.
Ich weiß. Deswegen hatte ich ja die Doku zu
object.__dict__ verlinkt.
Re: Objekt teilweise updaten
Verfasst: Dienstag 26. November 2024, 17:35
von __blackjack__
Wenn man `dataclasses` in Erwägung zieht: `attrs` hat eine `evolve()`-Funktion, die das gleiche macht.

Re: Objekt teilweise updaten
Verfasst: Donnerstag 23. Januar 2025, 15:20
von mechanicalStore
Jetzt habe ich einen aehnlichen Fall. Kann man Attribute zur Laufzeit hinzu fuegen? Z.B. durch ein Dictionary?
Code: Alles auswählen
#!/usr/bin/env python
class Foo:
def __init__(self, test_var):
self.the_only_defined_var_in_this_class = test_var
def main():
d = dict(variable_1 = 'test', variable_2 = 'foo', variable_3 = 'bar')
f = Foo('the one and only')
# connect d to f ????
if __name__ == '__main__':
main()
Re: Objekt teilweise updaten
Verfasst: Donnerstag 23. Januar 2025, 16:30
von __blackjack__
@mechanicalStore: Ich verstehe die Frage nicht ganz. Suchst Du jetzt eine *weitere* Möglichkeit? Denn *zwei* kennst Du ja bereits, wobei die eine davon direkt von Dir selbst im ersten Beitrag steht.
Re: Objekt teilweise updaten
Verfasst: Donnerstag 23. Januar 2025, 18:34
von mechanicalStore
@__blackjack__: Bei der ersten Anfrage ging es darum, beliebige Attribute upzudaten. Bei der heutigen Frage ging es darum, einer Instanz waehrend der Laufzeit neue Attribute hinzu zu fuegen. Ich dachte bisher, dass sowas nicht geht. Aber offenbar kann man mit setattr nicht nur vorhandene Attribute updaten, sondern auch beliebig neue Attribute anhaengen. Jedoch ob das ein idiomatischer Weg ist, konnte ich nirgends nachlesen.
Re: Objekt teilweise updaten
Verfasst: Donnerstag 23. Januar 2025, 21:18
von __blackjack__
@mechanicalStore: Idiomatisch ist beides nicht zu machen.

Re: Objekt teilweise updaten
Verfasst: Freitag 24. Januar 2025, 05:02
von snafu
In diesem Fall würde ich kein Dict übergeben, sondern mit einer variablen Anzahl an Keyword-Argumenten in der Signatur arbeiten:
Code: Alles auswählen
class Foo:
def __init__(self, bar, bridge, street):
self.bar = bar
self.bridge = bridge
self.street = street
def update(self, **changes):
for key, value in changes.items():
setattr(self, key, value)
def main():
foo = Foo('Test-Bar', 'Test-Bridge', 'Test-Street')
foo.update(bar="new_bar", bridge="new-bridge")
print(vars(f))
if __name__ == '__main__':
main()
Aber wie schon geschrieben wurde: Im dataclasses-Modul wird diese Möglichkeit auch angeboten. Sogar mit einer hübschen Repräsentation der jeweiligen Instanz, damit die Ausgabe via pprint() wegfallen kann.
Re: Objekt teilweise updaten
Verfasst: Freitag 24. Januar 2025, 09:11
von __blackjack__
Vielleicht ist ja auch etwas wie
https://github.com/python-attrs/cattrs interessant wenn man aus Wörterbüchern Objekte machen möchte. Oder das Python Wörterbuch das besser als Heroin ist:
https://github.com/mewwts/addict
Hier noch mal konkret in welche Richtung man beim ersten Beispiel gehen könnte:
Code: Alles auswählen
from attrs import evolve, field, frozen
@frozen
class Foo:
bar = field()
bridge = field()
street = field()
def main():
foo = Foo("Test-Bar", "Test-Bridge", "Test-Street")
print(foo)
changes = dict(bar="new_bar", bridge="new-bridge")
foo = evolve(foo, **changes)
print(foo)
if __name__ == "__main__":
main()
Ausgabe:
Code: Alles auswählen
Foo(bar='Test-Bar', bridge='Test-Bridge', street='Test-Street')
Foo(bar='new_bar', bridge='new-bridge', street='Test-Street')
Dein letztes Beispiel sollte es nicht geben. Nach der `__init__()` sollten alle Attribute existieren. Wenn man noch keine Werte hat, dann halt mit einem Default-Wert, beispielsweise `None`, aber sie sollte da sein, damit man weiss welche Attribute es geben kann. Das muss ja möglich sein, denn sonst stellt sich die Frage wie man auf die Attribute zugreifen soll im Code, wenn man gar nicht weiss welche es gibt.
Drei Varianten, wobei die mit `cattrs` hier wohl Kanonen auf Spatzen ist, aber sobald sich heraus stellt, dass man das auch verschachtelt haben möchte, lohnt sich ein Blick auf `cattrs`. So eine Bibliothek sollte man im Hinterkopf haben (es gibt da noch andere):
Code: Alles auswählen
import cattrs
from attrs import evolve, field, frozen
@frozen
class Foo:
the_only_defined_var_in_this_class = field()
variable_1 = field(default=None)
variable_2 = field(default=None)
variable_3 = field(default=None)
def main():
changes = dict(variable_1="test", variable_2="foo", variable_3="bar")
foo = Foo("the one and only")
print(foo)
foo = evolve(foo, **changes)
print(foo)
# Oder:
mapping = dict(variable_1="test", variable_2="foo", variable_3="bar")
mapping["the_only_defined_var_in_this_class"] = "the one and only"
foo = Foo(**mapping)
print(foo)
# Oder:
mapping = dict(variable_1="test", variable_2="foo", variable_3="bar")
mapping["the_only_defined_var_in_this_class"] = "the one and only"
foo = cattrs.structure(mapping, Foo)
print(foo)
if __name__ == "__main__":
main()
Re: Objekt teilweise updaten
Verfasst: Samstag 25. Januar 2025, 11:20
von mechanicalStore
Sehr abstract erklärt geht es darum, flexibel auf eine Datenquelle zu reagieren, sobald diese sich ändert, ohne dass man überall im Code eingreifen muss (soweit das möglich ist!). Die Datenquelle kann irgendwas sein, Textdatei, json, Datenbank, csv oder sonstwas. Die Daten werden gesammelt und je nach Art der Daten "geschieht" damit was. Sobald sich nun die Datenquelle ändert, soll der Code ohne viel Aufwand daran angepasst werden.
Ich habe dazu mal ein sehr abstraktes Beispiel erstellt. Hier die die Datenquelle der Einfachheit halber ein csv-file. Nach Einlesen werden daraus Objekte gebaut (mit der umstrittenen Methode, um die es eigentlich in diesem thread geht). In
work_with_objects werden die Objekte dann weiter verarbeitet und
Bar Objekte daraus erstellt. In dem Fall hier, werden diese nach einfacher Regel kombiniert, jedoch wird es später viel komplexer, auch mit verschiedenen Objektstrukturen. Wenn sich nun irgendwas an der (in dem Falle csv, Header umbenennen, Spalten anfügen, etc.) ändert, braucht man nur die anfängliche dict anzupassen.
Code: Alles auswählen
Beispiel csv:
description_1;description_2;bar_description_1
desc_1_test1;desc_2_test1;bdesc_1_test1
desc_1_test2;desc_2_test2;bdesc_1_test2
desc_1_test3;desc_2_test3;bdesc_1_test3
desc_1_test4;desc_2_test4;bdesc_1_test4
Code: Alles auswählen
#!/usr/bin/env/python
import csv
d = dict(foo_var_1 = 'description_1', foo_var_2 = 'description_2',
bar_var_1 = 'bar_description_1')
class Foo():
def __init__(self):
pass
def read_csv_file() -> list:
with open('test.csv', newline = '', encoding = 'utf-8') as csvfile:
line = csv.DictReader(csvfile, delimiter=';')
l = []
for row in line:
f = Foo()
for key, value in d.items():
setattr(f, key, row[value])
l.append(f)
return l
class Bar():
def __init__(self):
pass
def work_with_objects(list_of_foo) -> tuple:
a_list = []
b_list = []
for n in list_of_foo:
a = Bar()
b = Bar()
for key, value in d.items():
if 'foo' in key:
setattr(a, key, getattr(n, key))
if 'bar' in key:
setattr(b, key, getattr(n, key))
a_list.append(a)
b_list.append(b)
return (a_list, b_list)
def main():
list_of_foo = read_csv_file()
works = work_with_objects(list_of_foo)
if __name__ == '__main__':
main()
Re: Objekt teilweise updaten
Verfasst: Samstag 25. Januar 2025, 11:35
von snafu
Na dann schau dir doch wirklich cattrs mal näher an. Das scheint ja genau für dein Vorhaben geeignet zu sein. Würde dir wahrscheinlich viel Hirnschmalz & Arbeit (Zeit) ersparen.
Re: Objekt teilweise updaten
Verfasst: Sonntag 26. Januar 2025, 16:33
von __blackjack__
@mechanicalStore: Das ist doch Murks weil unbenutzbar. Jetzt hast Du `Bar`-Objekte bei denen die Objekte unterschiedliche Attribute (nicht) haben. Das geht so nicht. Wenn man den Datentyp kennt, dann weiss man welche Attribute und Methoden Objekte von dem Typ haben. Das ist doch der ganze Sinn warum man Datentypen definiert und nicht einfach Wörterbücher für alles nimmt.
Auch sinnlos ist diese beiden Listen zu erstellen, weil man einfach in beiden Fällen mit der ursprünglichen Liste arbeiten kann, denn die Objekte dort haben ja *alle* Attribute, also auch die nötigen Attribute wenn eine Funktion nur von Teilen davon Gebrauch macht.
Da bleibt letztlich das hier übrig:
Code: Alles auswählen
#!/usr/bin/env python3
import csv
NAME_TO_COLUMN_NAME = {
"var_1": "description_1",
"var_2": "description_2",
"bar_var_1": "bar_description_1",
}
class Foo:
def __init__(self, var_1, var_2, bar_var_1):
self.var_1 = var_1
self.var_2 = var_2
self.bar_var_1 = bar_var_1
def load_csv_file(filename):
with open(filename, newline="", encoding="utf-8") as file:
return [
Foo(
**{
argument_name: row[column_name]
for argument_name, column_name in NAME_TO_COLUMN_NAME.items()
}
)
for row in csv.DictReader(file, delimiter=";")
]
def main():
foos = load_csv_file("test.csv")
...
if __name__ == "__main__":
main()
Man könnte das erstellen eines `Foo`-Objekts aus einem CSV-Datensatz noch auf das `Foo`-Objekt verschieben wenn man mag:
Code: Alles auswählen
class Foo:
def __init__(self, var_1, var_2, bar_var_1):
self.var_1 = var_1
self.var_2 = var_2
self.bar_var_1 = bar_var_1
@classmethod
def from_row(cls, row):
cls(
**{
argument_name: row[column_name]
for argument_name, column_name in NAME_TO_COLUMN_NAME.items()
}
)
def load_csv_file(filename):
with open(filename, newline="", encoding="utf-8") as file:
return list(map(Foo.from_row, csv.DictReader(file, delimiter=";")))
Re: Objekt teilweise updaten
Verfasst: Sonntag 26. Januar 2025, 20:02
von Dennis89
Da `cattrs` schon angesprochen wurde:
Code: Alles auswählen
#!/usr/bin/env/python
import csv
from pathlib import Path
from attrs import field, frozen
from cattrs import structure
NAMES = ["var_1", "var_2", "bar_var_1"]
CSV_FILE = Path(__file__).parent / "test.csv"
@frozen
class Foo:
var_1 = field()
var_2 = field()
bar_var_1 = field()
def load_csv_file(file):
with open(file, newline="", encoding="utf-8") as csvfile:
return [
structure(dict(zip(NAMES, row.values())), Foo)
for row in csv.DictReader(csvfile, delimiter=";")
]
def main():
foos = load_csv_file(CSV_FILE)
if __name__ == "__main__":
main()
Ich geh davon aus, dass die Spalten des Wörterbuchs sich nicht irgendwie verschieben und habe deswegen die Namen nur in eine Liste gepackt.
Grüße
Dennis
Re: Objekt teilweise updaten
Verfasst: Sonntag 26. Januar 2025, 21:29
von snafu
Würde das mit cattrs nicht einfach so gehen?
Code: Alles auswählen
def load_csv_file(file):
with open(file, newline="", encoding="utf-8") as csvfile:
reader = csv.DictReader(csvfile, delimiter=";")
return structure(list(reader), list[Foo])
Natürlich vorausgesetzt, dass die Attribute von Foo die gleichen Namen haben wie die Spalten der CSV-Datei.
Re: Objekt teilweise updaten
Verfasst: Montag 27. Januar 2025, 18:21
von mechanicalStore
__blackjack__ hat geschrieben: Sonntag 26. Januar 2025, 16:33
... Jetzt hast Du `Bar`-Objekte bei denen die Objekte unterschiedliche Attribute (nicht) haben. Das geht so nicht.
Es waeren nicht nur Bar-Objekte, sondern auch andere. Daher (und weil ich weiss, in welche Liste sie gepackt sind) wuerde ich schon wissen, was drin ist. Aber ich habe den Plan verworfen, Du hast mich ueberzeugt, dass das eine schlechte Loesung ist.
Trotzdem konnte ich aus Deinen Antworten (sowie die der Anderen) einiges neues mitnehmen, das mir bei anderen Sachen hilft. Daher an Dich und alle Anderen meinen herzlichen Dank fuer die nuetzlichen Beitraege.
Gruss
Re: Objekt teilweise updaten
Verfasst: Dienstag 28. Januar 2025, 03:35
von snafu
Hier übrigens ein Beispiel mit Personen und Adressen bei Nutzung von cattrs (legt eine JSON-Datei im aktuellen Pfad an):
Code: Alles auswählen
#!/usr/bin/env python3
from datetime import date
from pathlib import Path
from pprint import pprint
from attrs import define
from cattrs.preconf.json import make_converter
_CONVERTER = make_converter()
JSON_FILE = Path("persons.json")
@define
class Address:
street: str
house_no: str
zip_code: str
city: str
@define
class Person:
name: str
birthday: date
address: Address
def save_json(
persons: list[Person],
json_file: Path,
encoding: str = "utf-8"
) -> None:
json_file.write_text(_CONVERTER.dumps(persons), encoding)
def load_json(
json_file: Path,
encoding: str = "utf-8"
) -> list[Person]:
return _CONVERTER.loads(json_file.read_text(encoding), list[Person])
def main():
address = Address("Spämstraße", "42 B", "01234", "Spämdorf")
man = Person("Jürgen Müller", date(1957, 5, 2), address)
woman = Person("Gertrud Müller", date(1957, 7, 18), address)
save_json([man, woman], JSON_FILE)
pprint(load_json(JSON_FILE))
if __name__ == "__main__":
main()