Создание свойств экземпляра класса из словаря?

Я импортирую из CSV и получаю данные примерно в формате

{ 'Field1' : 3000, 'Field2' : 6000, 'RandomField' : 5000 }

Имена полей динамические. (Что ж, они динамичны в том смысле, что их может быть больше, чем Field1 и Field2, но я знаю, что Field1 и Field2 всегда будут там.

Я хотел бы передать этот словарь в свой класс allMyFields, чтобы я мог получить доступ к указанным выше данным как к свойствам.

class allMyFields:
    # I think I need to include these to allow hinting in Komodo. I think.
    self.Field1 = None
    self.Field2 = None

    def __init__(self,dictionary):
        for k,v in dictionary.items():
            self.k = v
            #of course, this doesn't work. I've ended up doing this instead
            #self.data[k] = v
            #but it's not the way I want to access the data.

q = { 'Field1' : 3000, 'Field2' : 6000, 'RandomField' : 5000 }
instance = allMyFields(q)
# Ideally I could do this.
print q.Field1

Какие-либо предложения? Что касается того, почему - я хотел бы иметь возможность воспользоваться подсказками по коду, а импорт данных в словарь с именем data, как я делал, не дает мне ничего из этого.

(Поскольку имена переменных не разрешаются до времени выполнения, мне все равно придется бросить кость Komodo - я думаю, что self.Field1 = None должно быть достаточно.)

Итак - как мне делать то, что я хочу? Или я лаю на плохо спроектированное дерево без питона?


person Rizwan Kassim    schedule 28.10.2009    source источник
comment
Вам не нужны атрибуты класса для подсказок Komodo Edit. Он может читать тело __init__ метода, чтобы найти self. переменные. Если говорить более фундаментально, то почему вы не используете для этого простой csv.DictReader и не создаете словари из каждой строки?   -  person S.Lott    schedule 28.10.2009
comment
SLott - я вручную изменяю заголовки с помощью re.sub () и добавляю «поддельный» заголовок. Это не очень веская причина, но переименование ключа после DictReader намного дороже.   -  person Rizwan Kassim    schedule 29.10.2009


Ответы (9)


Вы можете использовать setattr (однако будьте осторожны: не каждая строка является допустимой имя атрибута!):

>>> class AllMyFields:
...     def __init__(self, dictionary):
...         for k, v in dictionary.items():
...             setattr(self, k, v)
... 
>>> o = AllMyFields({'a': 1, 'b': 2})
>>> o.a
1

Изменить: позвольте мне объяснить разницу между приведенным выше кодом и Ответ SilentGhost. Приведенный выше фрагмент кода создает класс, атрибуты экземпляра которого основаны на заданном словаре. Код SilentGhost создает класс, атрибуты класса которого основаны на заданном словаре.

В зависимости от вашей конкретной ситуации любое из этих решений может быть более подходящим. Вы просто создаете один или несколько экземпляров класса? Если ответ один, вы также можете полностью пропустить создание объекта и сконструировать только тип (и, таким образом, перейти к ответу SilentGhost).

person Stephan202    schedule 28.10.2009
comment
Код с бонусной правкой, объясняющий различия между обоими хорошими ответами, был действительно полезен, спасибо! - person aimbire; 31.08.2017
comment
Я был приятно удивлен, обнаружив, что это решение работает с form_dict = flask.form.to_dict () для создания экземпляра db.Model из длинной веб-формы. Удивительный! - person Timothy Lombard; 14.06.2018
comment
Обратите внимание: целые числа и числа с плавающей запятой, введенные в форму, возвращаются как строки в form_dict, но легко исправляются перед использованием классом. - person Timothy Lombard; 14.06.2018

>>> q = { 'Field1' : 3000, 'Field2' : 6000, 'RandomField' : 5000 }
>>> q = type('allMyFields', (object,), q)
>>> q.Field1
3000

документы для type хорошо объясняют, что здесь происходит (см. использование в качестве конструктора).

edit: если вам нужны переменные экземпляра, также работает следующее:

>>> a = q()             # first instance
>>> a.Field1
3000
>>> a.Field1 = 1
>>> a.Field1
1
>>> q().Field1           # second instance
3000
person SilentGhost    schedule 28.10.2009
comment
+1, но я думаю, вам следует объяснить OP, что делает этот код, и / или дать ссылки на документацию. - person Stephan202; 28.10.2009
comment
Думаю, его зовут SilentGhost - person Matt Baker; 28.10.2009
comment
Изящный подход, но тот факт, что они являются переменными класса, менее убедителен, чем переменные экземпляра. - person Rizwan Kassim; 03.11.2009
comment
при необходимости вы можете создать экземпляр q. - person SilentGhost; 03.11.2009

Вы также можете использовать dict.update вместо того, чтобы вручную перебирать items (а если вы зацикливаете, лучше использовать iteritems).

class allMyFields(object):
    # note: you cannot (and don't have to) use self here
    Field1 = None
    Field2 = None

    def __init__(self, dictionary):
        self.__dict__.update(dictionary)

q = { 'Field1' : 3000, 'Field2' : 6000, 'RandomField' : 5000 }
instance = allMyFields(q)

print instance.Field1      # => 3000
print instance.Field2      # => 6000
print instance.RandomField # => 5000
person Cat Plus Plus    schedule 28.10.2009
comment
Любое простое решение, если в ключах есть пробелы? Пример: instance.Something Else не будет работать из-за свободного места. Мне интересно, есть ли для этого элегантное решение. - person Jarad; 12.06.2019
comment
@Jarad вместо dictionary используйте map(lambda kv: (kv[0].replace(' ', '_'), kv[1]), dictionary.items()). Это заменяет все пробелы во всех именах клавиш подчеркиванием. - person iFreilicht; 01.03.2021

Вы можете создать подкласс dict, который позволяет искать ключи по атрибутам:

class AttributeDict(dict):
    def __getattr__(self, name):
        return self[name]

q = AttributeDict({ 'Field1' : 3000, 'Field2' : 6000, 'RandomField' : 5000 })
print q.Field1              
print q.Field2              
print q.RandomField

Если вы попытаетесь найти атрибут, который dict уже имеет (скажем, keys или get), вы получите этот атрибут dict класса (метод). Если запрошенный ключ не существует в классе dict, будет вызван метод __getattr__, который выполнит поиск вашего ключа.

person Matt Anderson    schedule 28.10.2009

Использование именованных кортежей (Python 2.6):

>>> from collections import namedtuple

>>> the_dict = {'Field1': 3, 'Field2': 'b', 'foo': 4.9}
>>> fields = ' '.join(the_dict.keys())
>>> AllMyFields = namedtuple('AllMyFields', fields)
>>> instance = AllMyFields(**the_dict)

>>> print instance.Field1, instance.Field2, instance.foo
3 b 4.9
person Raphaël Saint-Pierre    schedule 28.10.2009

Используйте setattr для изящного способа. Самый быстрый способ - обновить внутренний словарь экземпляра:

>>> class A(object):
...    pass
...
>>> a = A()
>>> a.__dict__.update({"foo": 1, "bar": 2})
>>> a.foo
1
>>> a.bar
2
>>>
person truppo    schedule 28.10.2009

Или вы можете попробовать это

class AllMyFields:
    def __init__(self, field1, field2, random_field):
        self.field1 = field1
        self.field2 = field2
        self.random_field = random_field

    @classmethod
    def get_instance(cls, d: dict):
        return cls(**d)


a = AllMyFields.get_instance({'field1': 3000, 'field2': 6000, 'random_field': 5000})
print(a.field1)
person Mitch    schedule 20.05.2019

усовершенствован из подкласса dict

повторение диктата работает!

class AttributeDict(dict):
    """https://stackoverflow.com/a/1639632/6494418"""

    def __getattr__(self, name):
        return self[name] if not isinstance(self[name], dict) \
            else AttributeDict(self[name])


if __name__ == '__main__':
    d = {"hello": 1, "world": 2, "cat": {"dog": 5}}
    d = AttributeDict(d)
    print(d.cat)
    print(d.cat.dog)
    print(d.cat.items())

    """
    {'dog': 5}
    5
    dict_items([('dog', 5)])
    """
person Colin Wang    schedule 25.07.2019

Если вы готовы добавить новую библиотеку, pydantic - очень эффективное решение. Он использует аннотацию Python для создания объекта и проверки типа. Рассмотрим следующий код:

from pydantic import BaseModel

class Person(BaseModel):
    name: str
    age: str


data = {"name": "ahmed", "age": 36}

p = Person(**data)

pydantic: https://pydantic-docs.helpmanual.io/

person Ahmed Roshdy    schedule 16.07.2020