Как я могу создать объект и добавить к нему атрибуты?

311

Я хочу создать динамический объект (внутри другого объекта) в Python, а затем добавить к нему атрибуты.

Я попытался:

obj = someobject
obj.a = object()
setattr(obj.a, 'somefield', 'somevalue')

но это не сработало.

Любые идеи?

редактировать:

Я устанавливаю атрибуты из forцикла, который перебирает список значений, например

params = ['attr1', 'attr2', 'attr3']
obj = someobject
obj.a = object()

for p in params:
   obj.a.p # where p comes from for loop variable

В приведенном выше примере я хотел бы получить obj.a.attr1, obj.a.attr2, obj.a.attr3.

Я использовал setattrфункцию, потому что я не знал, как это сделать obj.a.NAMEиз forцикла.

Как бы я установить атрибут на основе значения pв примере выше?

Джон
источник
4
Что вы подразумеваете под "не работает"? Я предполагаю, что это вызвало исключение AttributeError, верно?
Джош Райт
1
Да. 'объект' объект не имеет атрибута 'somefield'
Джон
6
Почему вы это делаете? Общий «объект» не имеет реального значения . В чем смысл того, что вы создаете? Почему это не правильный класс или namedtle?
S.Lott
1
Этот пример не является минимальным и запутанным для меня, или я просто не понимаю, почему вы не работаете с некоторыми, a = object()и вам это нужно obj.a = object(). Опять же, я говорю о примере: в вашем реальном коде объект внутри объекта может быть полезен.
Kon псих

Ответы:

215

Вы можете использовать мой древний рецепт Bunch , но если вы не хотите создавать «класс сгустков», в Python уже существует очень простой - все функции могут иметь произвольные атрибуты (включая лямбда-функции). Итак, следующие работы:

obj = someobject
obj.a = lambda: None
setattr(obj.a, 'somefield', 'somevalue')

Будет ли потеря ясности по сравнению с почтенным Bunchрецептом в порядке, это стильное решение, которое я, конечно, оставлю на ваше усмотрение.

Алекс Мартелли
источник
25
@FogleBird, решение о стиле, как я уже говорил. Некоторые специалисты по CS, обученные, например, по лямбда-исчислению Черча, привыкли думать о функциях (лямбда-выражениях) как о фундаментальном типе всех данных (например, целое число 23 можно рассматривать как эквивалентное lambda: 23), поэтому такие эксперты используют lambdas для этой цели. по-видимому, не чувствовал бы ничего, как "взломать". Лично мне не очень нравится lambda в Python - но это вопрос личного вкуса.
Алекс Мартелли
А в некоторых случаях рассмотрение того, lambdaподходит ли шаблон для вашего варианта использования, может привести к осознанию того, что то, о чем вы изначально думали как о данных, на самом деле больше похоже на функцию - или, в любом случае, на функтор.
Кайл Стрэнд,
5
@ naught101, функция - это объект в Python, поэтому ваше возражение непостижимо.
Алекс Мартелли
6
@ naught101, избегание создания нового типа (повторное использование существующего) не усложняет, а упрощает. В настоящее время он может предпочесть, from argparse import Namespaceхотя я бы хотел, чтобы он жил в другом месте *, например, collection) - снова использовать уже существующий тип, просто лучший, и все же избегать создания нового типа. Но этого не было тогда :-).
Алекс Мартелли
1
Смотрите ответ ниже от "JF Sebastian" относительно SimpleNamespace из модуля типов. Если ваша версия python поддерживает это, это лучшее решение (и именно для этого предназначено SimpleNamespace)
Тим Ричардсон,
334

Встроенный objectможет быть создан, но не может иметь никаких атрибутов. (Я бы хотел, чтобы именно для этой цели.) Он не имеет __dict__атрибутов.

Я вообще просто делаю это:

class Object(object):
    pass

a = Object()
a.somefield = somevalue

Когда я могу, я даю Objectклассу более осмысленное имя, в зависимости от того, какие данные я в него помещаю.

Некоторые люди делают разные вещи, когда используют подкласс, dictкоторый позволяет доступ к атрибутам для доступа к ключам. ( d.keyвместо d['key'])

Изменить : Для добавления к вашему вопросу, использование setattrв порядке. Вы просто не можете использовать setattrв object()случаях.

params = ['attr1', 'attr2', 'attr3']
for p in params:
    setattr(obj.a, p, value)
FogleBird
источник
9
его можно создать, но не использовать ни для чего полезного, как только это будет сделано. foo = object()работает, но вы ничего не можете с этим поделать
Даниэль ДиПаоло
Здравствуй. Спасибо за ответ. Я обновил мою проблему выше. см редактировать ты знаешь ответ на это?
Джон
извините, я все еще хочу установить его на объекте. см. обновление выше.
Джон
Мне очень нравится ваш ответ, и я думаю, что буду склоняться в этом направлении в будущем. Я использовал все остальное в этом посте, за исключением этой очень простой, понятной и читаемой методологии. Использование type....или лямбда никогда не было моей любимой, как текстовая рвота в моем коде. Но эта идея отлично подходит для использования объектов для хранения свойств. Делая код более читабельным b / c, когда я вижу лямбду, я замедляю чтение до 25%, в то время как ваш путь имеет смысл! Спасибо.
Марк
отличный ответ, единственное, что я изменил, было использовать Structв качестве имени класса, чтобы сделать его более очевидным. Сэкономил мне кучу печатать ["и "], ура!
Прагман
137

В types.SimpleNamespacePython 3.3+ есть класс :

obj = someobject
obj.a = SimpleNamespace()
for p in params:
    setattr(obj.a, p, value)
# obj.a.attr1

collections.namedtuple, typing.NamedTupleможет быть использован для неизменных объектов. PEP 557. Классы данных предлагают изменчивую альтернативу.

Для более богатой функциональности вы можете попробовать attrsпакет . Смотрите пример использования .

JFS
источник
3
Если вам нужно что-то, что работает с Python 2.7, вы также можете попробовать argparse.Namespaceкласс
RolKau
Согласитесь - мне было бы любопытно, если бы здесь было что-то плохое, но это невероятно удобный вариант Python 3.3+.
ghukill
черт! это не доступно на 2.7?
Roel
attrsПакет @Roel поддерживает Python 2.7
jfs
Мне кажется, это лучшее решение, чем unittest.mock; последний слишком тяжеловесный и более податливый. С фиктивным объектом простое назначение атрибута вызовет его возникновение; SimpleNamespace будет сопротивляться этому.
jdzions
32

Есть несколько способов достичь этой цели. В основном вам нужен объект, который можно расширять.

obj.a = type('Test', (object,), {})  
obj.a.b = 'fun'  

obj.b = lambda:None

class Test:
  pass
obj.c = Test()
evilpie
источник
14
obj.a = type('', (), {})
Иман
27

mockМодуль в основном сделаны для этого.

import mock
obj = mock.Mock()
obj.a = 5
Dunatotatos
источник
3
Disadvantge является то , что это внешняя зависимость
Kangur
6
unittest.Mockявляется частью стандартной библиотеки начиная с Python 3.3 ( docs.python.org/3/library/unittest.mock.html )
illagrenan
2
Зависит от использования вашего кода, я думаю. Если бы это был производственный код, я бы не хотел mockв нем чего-то. Просто странно для меня.
Майк де Клерк
21

Теперь вы можете сделать (не уверен, что это тот же ответ, что и evilpie):

MyObject = type('MyObject', (object,), {})
obj = MyObject()
obj.value = 42
andreabedini
источник
Ответ @ evilpie устанавливает атрибуты непосредственно для MyObject (класса), а не для его экземпляра, как у вас.
Jfs
18

Попробуйте код ниже:

$ python
>>> class Container(object):
...     pass 
...
>>> x = Container()
>>> x.a = 10
>>> x.b = 20
>>> x.banana = 100
>>> x.a, x.b, x.banana
(10, 20, 100)
>>> dir(x)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', 
'__getattribute__', '__hash__', '__init__', '__module__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__',     '__sizeof__', 
'__str__', '__subclasshook__', '__weakref__', 'a', 'b', 'banana']
neldor
источник
1
Можете ли вы объяснить больше, что это делает? хотя код может быть полезен для решения этой проблемы, его объяснение может пойти гораздо дальше, чем просто одна проблема.
DeadChex
1
@DeadChex Ясно, что он создает новый класс (объект), который является пустым классом со свойствами объекта, и хранит атрибуты внутри класса. Это даже лучше, чем устанавливать больше модулей или полагаться на лямбды.
17
2
Не уверен, почему это не имеет больше голосов. Есть ли причина не использовать это для базового класса контейнера? Кажется, отлично работает в Python 2.7, 2.6 и 3.4
user5359531
17

Вы также можете использовать объект класса напрямую; это создает пространство имен:

class a: pass
a.somefield1 = 'somevalue1'
setattr(a, 'somefield2', 'somevalue2')
Эрнесто
источник
9

как говорят документы :

Примечание : objectэто не есть __dict__, так что вы не сможете назначать произвольные атрибуты экземпляра objectкласса.

Вы можете просто использовать экземпляр класса-пустышки.

SilentGhost
источник
2

Эти решения очень полезны при тестировании. Основываясь на ответах всех остальных, я делаю это в Python 2.7.9 (без статического метода я получаю TypeError (метод unbound ...):

In [11]: auth = type('', (), {})
In [12]: auth.func = staticmethod(lambda i: i * 2)
In [13]: auth.func(2)
Out[13]: 4
Robpol86
источник
1

Какие объекты вы используете? Просто попробовал это с примером класса, и он работал нормально:

class MyClass:
  i = 123456
  def f(self):
    return "hello world"

b = MyClass()
b.c = MyClass()
setattr(b.c, 'test', 123)
b.c.test

И я получил 123 как ответ.

Единственная ситуация, в которой я вижу это сбой - это если вы пытаетесь setattrиспользовать встроенный объект.

Обновление: из комментария это повторение: почему вы не можете добавить атрибуты для объекта в Python?

jneves
источник
bc установлен в object (), а не в определенный класс
John
0

Подходя к этому в конце дня, но вот мой пенниворт с объектом, который, как оказалось, содержит некоторые полезные пути в приложении, но вы можете адаптировать его для чего угодно, где вы хотите что-то вроде информации, к которой вы можете получить доступ с помощью getattr и точечной нотации (вот о чем я думаю, этот вопрос на самом деле):

import os

def x_path(path_name):
    return getattr(x_path, path_name)

x_path.root = '/home/x'
for name in ['repository', 'caches', 'projects']:
    setattr(x_path, name, os.path.join(x_path.root, name))

Это круто, потому что сейчас:

In [1]: x_path.projects
Out[1]: '/home/x/projects'

In [2]: x_path('caches')
Out[2]: '/home/x/caches'

Таким образом, здесь используется объект функции, подобный приведенным выше ответам, но функция используется для получения значений (вы все равно можете использовать их, (getattr, x_path, 'repository')а не x_path('repository')если хотите)

Пол Уипп
источник
0

Если мы сможем определить и объединить все атрибуты и значения вместе перед созданием вложенного объекта, то мы могли бы создать новый класс, который принимает аргумент словаря при создании.

# python 2.7

class NestedObject():
    def __init__(self, initial_attrs):
        for key in initial_attrs:
            setattr(self, key, initial_attrs[key])

obj = someobject
attributes = { 'attr1': 'val1', 'attr2': 'val2', 'attr3': 'val3' }
obj.a = NestedObject(attributes)
>>> obj.a.attr1
'val1'
>>> obj.a.attr2
'val2'
>>> obj.a.attr3
'val3'

Мы также можем разрешить ключевые аргументы. Смотрите этот пост .

class NestedObject(object):
    def __init__(self, *initial_attrs, **kwargs):
        for dictionary in initial_attrs:
            for key in dictionary:
                setattr(self, key, dictionary[key])
        for key in kwargs:
            setattr(self, key, kwargs[key])


obj.a = NestedObject(attr1='val1', attr2='val2', attr3= 'val3')
HarlemSquirrel
источник
-2
di = {}
for x in range(20):
    name = '_id%s' % x
    di[name] = type(name, (object), {})
    setattr(di[name], "attr", "value")
lmokto
источник
-2

Иначе, как я вижу, вот так:

import maya.cmds

def getData(objets=None, attrs=None):
    di = {}
    for obj in objets:
        name = str(obj)
        di[name]=[]
        for at in attrs:
            di[name].append(cmds.getAttr(name+'.'+at)[0])
    return di

acns=cmds.ls('L_vest_*_',type='aimConstraint')
attrs=['offset','aimVector','upVector','worldUpVector']

getData(acns,attrs)
Пабло Эммануэль Де Лео
источник
Вы можете добавить в дополнение имя к этому имени [имя] .append ([в, cmds.getAttr (имя + '.' + в) [0]])
Пабло Эммануэль Де Лео
1
Это добавляет очень большую нестандартную зависимость, в то время как простое class a: passдает всю необходимую мощность.
Алексис Пакес