Ich finde es bequemer, als obj.foo
auf ictcode statt auf obj['foo']
zuzugreifen, also habe ich diesen Ausschnitt geschrieben:
class AttributeDict(dict):
def __getattr__(self, attr):
return self[attr]
def __setattr__(self, attr, value):
self[attr] = value
Ich gehe jedoch davon aus, dass es einen Grund geben muss, dass Python diese Funktionalität nicht standardmäßig bereitstellt. Was wären die Vorbehalte und Fallstricke beim Zugriff auf Diktierschlüssel auf diese Weise?
Der beste Weg dies zu tun ist:
class AttrDict(dict):
def __init__(self, *args, **kwargs):
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self
Einige Profis:
.keys()
einwandfrei).AttributeError
anstelle von KeyError
ausgelöst.Nachteile:
.keys()
funktionieren not gut, wenn sie durch eingehende Daten überschrieben werdenE1123(unexpected-keyword-arg)
und E1103(maybe-no-member)
Bananen__dict__
.__dict__
"nur ein einfaches Diktat" sein muss, sodass wir dem internen Wörterbuch jede Unterklasse von dict()
zuordnen können.AttrDict()
zu, die wir instanziieren (wie in __init__
).super()
-Methode __init__()
haben wir sichergestellt, dass sie sich (bereits) genau wie ein Wörterbuch verhält, da diese Funktion den gesamten Dictionary Instantiation-Code aufruft.Wie in der "cons" -Liste erwähnt, kombiniert dies den Namespace gespeicherter Schlüssel (die von beliebigen und/oder nicht vertrauenswürdigen Daten stammen können) mit dem Namespace eingebauter dict-Methodenattribute. Zum Beispiel:
d = AttrDict()
d.update({'items':["jacket", "necktie", "trousers"]})
for k, v in d.items(): # TypeError: 'list' object is not callable
print "Never reached!"
Sie können alle zulässigen Zeichenfolgezeichen als Teil des Schlüssels verwenden, wenn Sie die Array-Notation ..__ verwenden. Beispiel: obj['!#$%^&*()_']
Von Diese andere SO - Frage Es gibt ein großartiges Implementierungsbeispiel, das Ihren vorhandenen Code vereinfacht. Wie wäre es mit:
class AttributeDict(dict):
__getattr__ = dict.__getitem__
__setattr__ = dict.__setitem__
Viel prägnanter und lässt keinen Raum für zusätzliche Möglichkeiten, in Zukunft in Ihre __getattr__
- und __setattr__
-Funktionen zu gelangen.
Ich vermute, dass es mit dem Zen of Python zu tun hat: "Es sollte einen - und am besten nur einen - offensichtlichen Weg geben, dies zu tun." Dies würde zwei offensichtliche Möglichkeiten schaffen, auf Werte aus Wörterbüchern zuzugreifen: obj['key']
Und obj.key
.
Dazu gehören mögliche Unklarheiten und Unklarheiten im Code. Das folgende kann für jemanden verwirrend sein , der Ihren Code zu einem späteren Zeitpunkt pflegen wird, oder sogar für Sie, wenn Sie es sind Ich werde für eine Weile nicht darauf zurückkommen. Wieder aus Zen : "Lesbarkeit zählt!"
>>> KEY = 'spam'
>>> d[KEY] = 1
>>> # Several lines of miscellaneous code here...
... assert d.spam == 1
Wenn d
instanziiert ist oder KEY
ist definiert oder d[KEY]
Wird weit entfernt von dem Ort zugewiesen, an dem d.spam
Verwendet wird. Dies kann leicht zu Verwirrung darüber führen, was getan wird, da dies keine allgemein gebräuchliche Redewendung ist. Ich weiß, es könnte mich verwirren.
Wenn Sie zusätzlich den Wert von KEY
wie folgt ändern (aber die Änderung von d.spam
Verpassen), erhalten Sie jetzt:
>>> KEY = 'foo'
>>> d[KEY] = 1
>>> # Several lines of miscellaneous code here...
... assert d.spam == 1
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
AttributeError: 'C' object has no attribute 'spam'
IMO, die Mühe nicht wert.
Wie andere angemerkt haben, können Sie jedes Hash-Objekt (nicht nur eine Zeichenfolge) als Diktierschlüssel verwenden. Zum Beispiel,
>>> d = {(2, 3): True,}
>>> assert d[(2, 3)] is True
>>>
ist legal, aber
>>> C = type('C', (object,), {(2, 3): True})
>>> d = C()
>>> assert d.(2, 3) is True
File "<stdin>", line 1
d.(2, 3)
^
SyntaxError: invalid syntax
>>> getattr(d, (2, 3))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: getattr(): attribute name must be string
>>>
ist nicht. Auf diese Weise erhalten Sie Zugriff auf den gesamten Bereich druckbarer Zeichen oder anderer Hash-Objekte für Ihre Wörterbuchschlüssel, die Sie beim Zugriff auf ein Objektattribut nicht haben. Dies ermöglicht solche Magie wie eine zwischengespeicherte Objekt-Metaklasse, wie das Rezept aus dem Python Cookbook (Ch. 9) .
Ich bevorzuge die Ästhetik von spam.eggs
Gegenüber spam['eggs']
(Ich denke, es sieht sauberer aus) und habe wirklich Lust auf diese Funktionalität bekommen, als ich namedtuple
. Aber die Bequemlichkeit, das Folgende tun zu können, übertrifft es.
>>> KEYS = 'spam eggs ham'
>>> VALS = [1, 2, 3]
>>> d = {k: v for k, v in Zip(KEYS.split(' '), VALS)}
>>> assert d == {'spam': 1, 'eggs': 2, 'ham': 3}
>>>
Dies ist ein einfaches Beispiel, aber ich verwende Dikte häufig in anderen Situationen als in der Notation obj.key
(D. H. Wenn ich Prefs in einer XML-Datei einlesen muss). In anderen Fällen, in denen ich versucht bin, eine dynamische Klasse zu instanziieren und aus ästhetischen Gründen einige Attribute darauf zu legen, verwende ich weiterhin ein Diktat zur Konsistenz, um die Lesbarkeit zu verbessern.
Ich bin mir sicher, dass das OP dies schon lange zu seiner Zufriedenheit gelöst hat, aber wenn er diese Funktionalität immer noch haben möchte, dann schlage ich vor, dass er eines der Pakete von pypi herunterlädt, die es bereitstellen:
dict
, damit Sie alle diese Funktionen haben.Um jedoch die Lesbarkeit seines Codes zu verbessern, empfehle ich nachdrücklich, dass er seine Notationsstile nicht mischt . Wenn er diese Schreibweise bevorzugt, sollte er einfach ein dynamisches Objekt instanziieren, seine gewünschten Attribute hinzufügen und es einen Tag nennen:
>>> C = type('C', (object,), {})
>>> d = C()
>>> d.spam = 1
>>> d.eggs = 2
>>> d.ham = 3
>>> assert d.__dict__ == {'spam': 1, 'eggs': 2, 'ham': 3}
In den Kommentaren (unten) fragt Elmo :
Was ist, wenn Sie tiefer gehen wollen? (Bezogen auf Typ (...))
Obwohl ich diesen Anwendungsfall noch nie verwendet habe (ich neige dazu, aus Konsistenzgründen geschachteltes dict
zu verwenden), funktioniert der folgende Code:
>>> C = type('C', (object,), {})
>>> d = C()
>>> for x in 'spam eggs ham'.split():
... setattr(d, x, C())
... i = 1
... for y in 'one two three'.split():
... setattr(getattr(d, x), y, i)
... i += 1
...
>>> assert d.spam.__dict__ == {'one': 1, 'two': 2, 'three': 3}
Vorsichtsmaßnahme: Aus einigen Gründen scheinen Klassen wie diese das Multiprocessing-Paket zu durchbrechen. Ich hatte gerade eine Weile mit diesem Fehler zu kämpfen, bevor ich diese SO fand: Suche nach Ausnahme in Python Multiprocessing
Was wäre, wenn Sie einen Schlüssel wünschen, der eine Methode ist, beispielsweise __eq__
oder __getattr__
?
Und Sie könnten keinen Eintrag haben, der nicht mit einem Buchstaben begann. Daher ist es nicht möglich, 0343853
als Schlüssel zu verwenden.
Und was ist, wenn Sie keine Zeichenfolge verwenden möchten?
Sie können eine praktische Containerklasse aus der Standardbibliothek abrufen:
from argparse import Namespace
um zu vermeiden, dass Code-Bits kopiert werden müssen. Kein Standardwörterbuchzugriff, aber einfach wieder zu bekommen, wenn Sie es wirklich wollen. Der Code in argparse ist einfach,
class Namespace(_AttributeHolder):
"""Simple object for storing attributes.
Implements equality by attribute names and values, and provides a simple
string representation.
"""
def __init__(self, **kwargs):
for name in kwargs:
setattr(self, name, kwargs[name])
__hash__ = None
def __eq__(self, other):
return vars(self) == vars(other)
def __ne__(self, other):
return not (self == other)
def __contains__(self, key):
return key in self.__dict__
tupel können Diktasten verwendet werden. Wie würden Sie in Ihrem Konstrukt auf Tuple zugreifen?
namedtuple ist eine praktische Struktur, die Werte über den Attributzugriff bereitstellen kann.
Im Allgemeinen funktioniert es nicht. Nicht alle gültigen Diktierschlüssel machen adressierbare Attribute ("den Schlüssel"). Sie müssen also vorsichtig sein.
Python-Objekte sind im Wesentlichen alle Wörterbücher. Ich bezweifle also, dass es viel Leistung oder andere Strafen gibt.
Hier ist ein kurzes Beispiel für unveränderliche Datensätze, die integrierte collections.namedtuple
verwenden:
def record(name, d):
return namedtuple(name, d.keys())(**d)
und ein Anwendungsbeispiel:
rec = record('Model', {
'train_op': train_op,
'loss': loss,
})
print rec.loss(..)
Dies bezieht sich nicht auf die ursprüngliche Frage, sollte aber für Leute nützlich sein, die wie ich hier enden, wenn sie nach einer Bibliothek suchen, die diese Funktionalität bietet.
Addict es ist eine großartige lib für dieses: https://github.com/mewwts/addict es kümmert sich um viele Anliegen, die in früheren Antworten erwähnt wurden.
Ein Beispiel aus den Dokumenten:
body = {
'query': {
'filtered': {
'query': {
'match': {'description': 'addictive'}
},
'filter': {
'term': {'created_by': 'Mats'}
}
}
}
}
Mit Süchtigen:
from addict import Dict
body = Dict()
body.query.filtered.query.match.description = 'addictive'
body.query.filtered.filter.term.created_by = 'Mats'
Wie wäre es mit Prodict , der kleinen Python-Klasse, die ich schrieb um sie alle zu beherrschen :)
Außerdem erhalten Sie Auto-Code-Vervollständigung, rekursive Objekt-Instantiierungen und Auto-Typkonvertierung!
Sie können genau das tun, wonach Sie gefragt haben:
p = Prodict()
p.foo = 1
p.bar = "baz"
class Country(Prodict):
name: str
population: int
turkey = Country()
turkey.name = 'Turkey'
turkey.population = 79814871
germany = Country(name='Germany', population='82175700', flag_colors=['black', 'red', 'yellow'])
print(germany.population) # 82175700
print(type(germany.population)) # <class 'int'>
print(germany.flag_colors) # ['black', 'red', 'yellow']
print(type(germany.flag_colors)) # <class 'list'>
Anscheinend gibt es jetzt eine Bibliothek für diese - https://pypi.python.org/pypi/attrdict - die diese exakte Funktionalität plus rekursives Mischen und Json-Laden implementiert. Vielleicht einen Blick wert.
Ich habe dies basierend auf den Eingaben aus diesem Thread erstellt. Ich muss allerdings Odict verwenden, also musste ich get und attr setzen. Ich denke, dass dies für die meisten Sonderanwendungen funktionieren sollte.
Die Nutzung sieht so aus:
# Create an ordered dict normally...
>>> od = OrderedAttrDict()
>>> od["a"] = 1
>>> od["b"] = 2
>>> od
OrderedAttrDict([('a', 1), ('b', 2)])
# Get and set data using attribute access...
>>> od.a
1
>>> od.b = 20
>>> od
OrderedAttrDict([('a', 1), ('b', 20)])
# Setting a NEW attribute only creates it on the instance, not the dict...
>>> od.c = 8
>>> od
OrderedAttrDict([('a', 1), ('b', 20)])
>>> od.c
8
Die Klasse:
class OrderedAttrDict(odict.OrderedDict):
"""
Constructs an odict.OrderedDict with attribute access to data.
Setting a NEW attribute only creates it on the instance, not the dict.
Setting an attribute that is a key in the data will set the dict data but
will not create a new instance attribute
"""
def __getattr__(self, attr):
"""
Try to get the data. If attr is not a key, fall-back and get the attr
"""
if self.has_key(attr):
return super(OrderedAttrDict, self).__getitem__(attr)
else:
return super(OrderedAttrDict, self).__getattr__(attr)
def __setattr__(self, attr, value):
"""
Try to set the data. If attr is not a key, fall-back and set the attr
"""
if self.has_key(attr):
super(OrderedAttrDict, self).__setitem__(attr, value)
else:
super(OrderedAttrDict, self).__setattr__(attr, value)
Dies ist ein ziemlich cooles Muster, das bereits im Thread erwähnt wurde. Wenn Sie jedoch nur ein Diktat in ein Objekt konvertieren möchten, das mit Auto-Completion in einer IDE usw. funktioniert, gehen Sie wie folgt vor:
class ObjectFromDict(object):
def __init__(self, d):
self.__dict__ = d
Sie müssen keine eigenen as setattr () schreiben und getattr () existiert bereits.
Der Vorteil von Klassenobjekten kommt wahrscheinlich bei der Klassendefinition und Vererbung zum Tragen.
Um der Antwort etwas Abwechslung zu verleihen, hat sci-kit learn dies als Bunch
implementiert:
class Bunch(dict):
""" Scikit Learn's container object
Dictionary-like object that exposes its keys as attributes.
>>> b = Bunch(a=1, b=2)
>>> b['b']
2
>>> b.b
2
>>> b.c = 6
>>> b['c']
6
"""
def __init__(self, **kwargs):
super(Bunch, self).__init__(kwargs)
def __setattr__(self, key, value):
self[key] = value
def __dir__(self):
return self.keys()
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(key)
def __setstate__(self, state):
pass
Sie benötigen lediglich die setattr
- und getattr
-Methoden. Die getattr
sucht nach Diktiertasten und die Suche nach tatsächlichen Attributen. Die setstaet
ist ein Fix für das Beizen/Entpacken von "Bündeln" - bei eingehenden Prüfungen https://github.com/scikit-learn/scikit-learn/issues/6196
Lassen Sie mich eine weitere Implementierung posten, die auf der Antwort von Kinvais aufbaut, aber Ideen aus dem in http://databio.org/posts/python_AttributeDict.html vorgeschlagenen AttributeDict integriert.
Der Vorteil dieser Version ist, dass sie auch für verschachtelte Wörterbücher funktioniert:
class AttrDict(dict):
"""
A class to convert a nested Dictionary into an object with key-values
that are accessible using attribute notation (AttrDict.attribute) instead of
key notation (Dict["key"]). This class recursively sets Dicts to objects,
allowing you to recurse down nested dicts (like: AttrDict.attr.attr)
"""
# Inspired by:
# http://stackoverflow.com/a/14620633/1551810
# http://databio.org/posts/python_AttributeDict.html
def __init__(self, iterable, **kwargs):
super(AttrDict, self).__init__(iterable, **kwargs)
for key, value in iterable.items():
if isinstance(value, dict):
self.__dict__[key] = AttrDict(value)
else:
self.__dict__[key] = value
class AttrDict(dict):
def __init__(self):
self.__dict__ = self
if __== '____main__':
d = AttrDict()
d['ray'] = 'hope'
d.Sun = 'shine' >>> Now we can use this . notation
print d['ray']
print d.Sun
Sie können dict_to_obj https://pypi.org/project/dict-to-obj/ verwenden. Es macht genau das, wonach Sie gefragt haben
From dict_to_obj import DictToObj
a = {
'foo': True
}
b = DictToObj(a)
b.foo
True
Lösung ist:
DICT_RESERVED_KEYS = vars(dict).keys()
class SmartDict(dict):
"""
A Dict which is accessible via attribute dot notation
"""
def __init__(self, *args, **kwargs):
"""
:param args: multiple dicts ({}, {}, ..)
:param kwargs: arbitrary keys='value'
If ``keyerror=False`` is passed then not found attributes will
always return None.
"""
super(SmartDict, self).__init__()
self['__keyerror'] = kwargs.pop('keyerror', True)
[self.update(arg) for arg in args if isinstance(arg, dict)]
self.update(kwargs)
def __getattr__(self, attr):
if attr not in DICT_RESERVED_KEYS:
if self['__keyerror']:
return self[attr]
else:
return self.get(attr)
return getattr(self, attr)
def __setattr__(self, key, value):
if key in DICT_RESERVED_KEYS:
raise AttributeError("You cannot set a reserved name as attribute")
self.__setitem__(key, value)
def __copy__(self):
return self.__class__(self)
def copy(self):
return self.__copy__()
Wie von Doug erwähnt, gibt es ein Bunch-Paket, mit dem Sie die obj.key
-Funktionalität erreichen können. Eigentlich gibt es eine neuere Version
Es hat jedoch eine großartige Funktion, die Ihr Diktat durch die Funktion neobunchify in ein NeoBunch-Objekt konvertiert. Ich verwende häufig Mako-Vorlagen, und die Weitergabe von Daten als NeoBunch-Objekte macht sie viel lesbarer. Wenn Sie also ein normales Diktum in Ihrem Python-Programm verwenden, die Punktnotation jedoch in einer Mako-Vorlage angezeigt werden soll, können Sie es so verwenden:
from mako.template import Template
from neobunch import neobunchify
mako_template = Template(filename='mako.tmpl', strict_undefined=True)
data = {'tmpl_data': [{'key1': 'value1', 'key2': 'value2'}]}
with open('out.txt', 'w') as out_file:
out_file.write(mako_template.render(**neobunchify(data)))
Und die Mako-Vorlage könnte so aussehen:
% for d in tmpl_data:
Column1 Column2
${d.key1} ${d.key2}
% endfor
Sie können es mit dieser Klasse tun, die ich gerade gemacht habe. Mit dieser Klasse können Sie das Map
-Objekt wie ein anderes Wörterbuch (einschließlich Json-Serialisierung) oder mit der Punktnotation verwenden. Ich hoffe dir helfen:
class Map(dict):
"""
Example:
m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
"""
def __init__(self, *args, **kwargs):
super(Map, self).__init__(*args, **kwargs)
for arg in args:
if isinstance(arg, dict):
for k, v in arg.iteritems():
self[k] = v
if kwargs:
for k, v in kwargs.iteritems():
self[k] = v
def __getattr__(self, attr):
return self.get(attr)
def __setattr__(self, key, value):
self.__setitem__(key, value)
def __setitem__(self, key, value):
super(Map, self).__setitem__(key, value)
self.__dict__.update({key: value})
def __delattr__(self, item):
self.__delitem__(item)
def __delitem__(self, key):
super(Map, self).__delitem__(key)
del self.__dict__[key]
Verwendungsbeispiele:
m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
# Add new key
m.new_key = 'Hello world!'
print m.new_key
print m['new_key']
# Update values
m.new_key = 'Yay!'
# Or
m['new_key'] = 'Yay!'
# Delete key
del m.new_key
# Or
del m['new_key']
Das ist was ich benutze
args = {
'batch_size': 32,
'workers': 4,
'train_dir': 'train',
'val_dir': 'val',
'lr': 1e-3,
'momentum': 0.9,
'weight_decay': 1e-4
}
args = namedtuple('Args', ' '.join(list(args.keys())))(**args)
print (args.lr)
Dies ist keine 'gute' Antwort, aber ich dachte, das wäre geschickt (es behandelt keine verschachtelten Diktate in der aktuellen Form). Wickeln Sie Ihr Diktier einfach in eine Funktion ein:
def make_funcdict(d={}, **kwargs)
def funcdict(d={}, **kwargs):
funcdict.__dict__.update(d)
funcdict.__dict__.update(kwargs)
return funcdict.__dict__
funcdict(d, **kwargs)
return funcdict
Jetzt haben Sie eine etwas andere Syntax. Um auf die Diktierelemente als Attribute zuzugreifen, f.key
. Um auf die Dict-Elemente (und andere Dict-Methoden) auf die übliche Weise zuzugreifen, machen Sie f()['key']
und wir können das Dict bequem aktualisieren, indem Sie f mit Schlüsselwortargumenten und/oder einem Wörterbuch aufrufen
d = {'name':'Henry', 'age':31}
d = make_funcdict(d)
>>> for key in d():
... print key
...
age
name
>>> print d.name
... Henry
>>> print d.age
... 31
>>> d({'Height':'5-11'}, Job='Carpenter')
... {'age': 31, 'name': 'Henry', 'Job': 'Carpenter', 'Height': '5-11'}
Und da ist es. Ich freue mich, wenn jemand Vor- und Nachteile dieser Methode vorschlägt.
Was wären die Vorbehalte und Fallstricke beim Zugriff auf Diktierschlüssel auf diese Weise?
Wie @Henry nahe legt, besteht der Grund, warum Dotted-Access in Diktaten nicht verwendet werden darf, darin, dass Diktennamen auf Python-gültige Variablen beschränkt werden, wodurch alle möglichen Namen eingeschränkt werden.
Im Folgenden finden Sie Beispiele dafür, warum Punktzugriff im Allgemeinen bei einem Diktat d
nicht hilfreich ist:
Gültigkeit
Die folgenden Attribute sind in Python ungültig:
d.1_foo # enumerated names
d./bar # path names
d.21.7, d.12:30 # decimals, time
d."" # empty strings
d.john doe, d.denny's # spaces, misc punctuation
d.3 * x # expressions
Stil
PEP8-Konventionen würden der Attributbenennung eine weiche Einschränkung auferlegen:
A. Reservierte Schlüsselwort (oder eingebaute Funktion) Namen:
d.in
d.False, d.True
d.max, d.min
d.sum
d.id
Wenn der Name eines Funktionsarguments mit einem reservierten Schlüsselwort kollidiert, ist es im Allgemeinen besser, einen einzelnen nachfolgenden Unterstrich anzuhängen.
B. Die Fallregel zu Methoden und Variablennamen :
Variablennamen folgen der gleichen Konvention wie Funktionsnamen.
d.Firstname
d.Country
Verwenden Sie die Funktionsbenennungsregeln: Kleinbuchstaben mit durch Unterstriche getrennten Wörtern, um die Lesbarkeit zu verbessern.
Manchmal werden diese Bedenken in Bibliotheken wie Pandas aufgeworfen, wodurch der punktförmige Zugriff auf DataFrame-Spalten nach Namen ermöglicht wird. Der Standardmechanismus zum Auflösen von Benennungseinschränkungen ist auch Array-Notation - eine Zeichenfolge in Klammern.
Wenn diese Einschränkungen nicht auf Ihren Anwendungsfall zutreffen, gibt es mehrere Optionen für gepunktete Datenstrukturen .