Jak mogę utworzyć obiekt i dodać do niego atrybuty?

310

Chcę utworzyć obiekt dynamiczny (wewnątrz innego obiektu) w Pythonie, a następnie dodać do niego atrybuty.

Próbowałem:

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

ale to nie zadziałało.

Jakieś pomysły?

edytować:

Ustawiam atrybuty z forpętli, która przechodzi przez listę wartości, np

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

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

W powyższym przykładzie chciałbym uzyskać obj.a.attr1, obj.a.attr2, obj.a.attr3.

Użyłem tej setattrfunkcji, ponieważ nie wiedziałem, jak to zrobić obj.a.NAMEz forpętli.

Jak ustawić atrybut na podstawie wartości z ppowyższego przykładu?

Jan
źródło
4
Co rozumiesz przez „nie działał”? Zakładam, że zgłosił wyjątek AttributeError, prawda?
Josh Wright
1
Tak. Obiekt „object” nie ma atrybutu „somefield”
Jan
6
Dlaczego to robisz? Ogólny „obiekt” nie ma rzeczywistego znaczenia . Jakie jest znaczenie tego, co tworzysz? Dlaczego nie jest to odpowiednia klasa ani nazwa-nazwa?
S.Lott
1
Ten przykład nie jest dla mnie minimalny i mylący, bo po prostu nie rozumiem, dlaczego z niektórymi nie pracujesz a = object() i potrzebujesz obj.a = object(). Ponownie mówię o przykładzie, w twoim rzeczywistym kodzie może się przydać obiekt wewnątrz obiektu.
kon psych

Odpowiedzi:

215

Możesz użyć mojej starożytnej receptury Bunch , ale jeśli nie chcesz tworzyć „klasy pęczka”, w Pythonie istnieje już bardzo prosta - wszystkie funkcje mogą mieć dowolne atrybuty (w tym funkcje lambda). Tak więc następujące prace:

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

To, czy utrata jasności w porównaniu z czcigodnym Bunchprzepisem jest w porządku, jest decyzją stylową, którą oczywiście podejmę.

Alex Martelli
źródło
25
@FogleBird, decyzja o stylu, jak wspomniałem. Niektórzy eksperci CS wyszkoleni np. W rachunku lambda Kościoła są przyzwyczajeni do myślenia o funkcjach (lambda) jako podstawowym typie wszystkich danych (na przykład liczbę całkowitą 23 można uznać za równoważną lambda: 23), więc do takich ekspertów używających lambdas do tego celu prawdopodobnie nie czułby się niczym „hack”. Osobiście nie bardzo lubię lambda w Pythonie - ale to kwestia osobistego gustu.
Alex Martelli
A w niektórych przypadkach rozważenie, czy lambdawzorzec pasuje do twojego przypadku użycia, może doprowadzić do uświadomienia sobie, że coś, co pierwotnie uważałeś za dane, jest w rzeczywistości bardziej jak funkcja - lub, w każdym razie, funktorem.
Kyle Strand
5
@ naught101, funkcja jest obiektem w Pythonie, więc twój sprzeciw jest niezgłębiony.
Alex Martelli,
6
@ naught101, unikanie tworzenia nowego typu (ponowne użycie istniejącego) nie komplikuje, upraszcza. W dzisiejszych czasach może faktycznie wolę, from argparse import Namespacechoć chciałbym, żeby mieszkał gdzie indziej * np. collection) - ponownie wykorzystując już istniejący typ, po prostu lepszy i wciąż unikając tworzenia nowego typu. Ale wtedy go tam nie było :-).
Alex Martelli
1
Zobacz odpowiedź poniżej z „JF Sebastian” dotyczącą SimpleNamespace z modułu typów. Jeśli twoja wersja Pythona obsługuje to, jest to najlepsze rozwiązanie (i dokładnie do czego przeznaczona jest SimpleNamespace)
Tim Richardson
333

Wbudowane objectmogą być tworzone instancje, ale nie mogą mieć ustawionych żadnych atrybutów. (Chciałbym, żeby mógł, właśnie w tym celu.) Nie ma__dict__ posiadać atrybutów.

Zasadniczo po prostu to robię:

class Object(object):
    pass

a = Object()
a.somefield = somevalue

Kiedy mogę, daję Object nadaję klasie bardziej znaczącą nazwę, w zależności od tego, jakie dane w niej umieszczam.

Niektóre osoby robią coś innego, wykorzystując ich podklasę, dictktóra umożliwia dostęp do atrybutów w celu uzyskania kluczy. ( d.keyzamiast d['key'])

Edycja : Do dodania do pytania użycie setattrjest w porządku. Po prostu nie możesz używać setattrw object()instancjach.

params = ['attr1', 'attr2', 'attr3']
for p in params:
    setattr(obj.a, p, value)
FogleBird
źródło
9
to może być instancja, po prostu nie służy do niczego przydatne, gdy to zostało zrobione. foo = object()działa, ale po prostu niewiele można z tym zrobić
Daniel DiPaolo
Cześć. Dziękuję za odpowiedź. Zaktualizowałem swój problem powyżej. zobacz edycję. znasz odpowiedź na to pytanie?
Jan
przepraszam, nadal chcę ustawić go na obiekcie. patrz aktualizacja powyżej.
Jan
Naprawdę podoba mi się twoja odpowiedź i myślę, że skręcę w tym kierunku. W tym poście wykorzystałem wszystko inne oprócz tej bardzo prostej, zrozumiałej i czytelnej metodologii. Używanie type....lub lambda nigdy nie było moją ulubioną, podobnie jak wymioty tekstowe w moim kodzie. Ale ten pomysł jest świetny do używania obiektów do przechowywania właściwości. Pozostawia kod bardziej czytelny b / c, gdy widzę lambda Zwalniam czytanie do 25%, podczas gdy twoja droga ma sens! Dzięki.
Marc
świetna odpowiedź, jedyną rzeczą, którą zmieniłem, było użycie Structnazwy klasy, aby uczynić ją bardziej oczywistą. Zaoszczędził mi mnóstwo pisania ["i "], Na zdrowie!
pragman
137

W types.SimpleNamespacePython 3.3+ istnieje klasa :

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

collections.namedtuple, typing.NamedTuplemoże być stosowany do obiektów niezmiennych. PEP 557 - Klasy danych sugerują zmienną alternatywę.

Aby uzyskać bogatszą funkcjonalność, możesz wypróbować attrspakiet . Zobacz przykładowe użycie .

jfs
źródło
3
Jeśli potrzebujesz czegoś, co działa z Pythonem 2.7, możesz także wypróbować argparse.Namespaceklasę
RolKau
Zgadzam się - byłbym ciekawy, czy istnieje tutaj wada, ale jest to niezwykle przydatna afordancja pytona 3.3+.
ghukill
cholera! to nie jest dostępne w wersji 2.7?
Roel,
attrsPakiet @Roel obsługuje Python 2.7
jfs
Wydaje mi się to lepszym rozwiązaniem niż unittest.mock; ten ostatni jest nieco zbyt ciężki i bardziej plastyczny. W przypadku fałszywego obiektu po prostu przypisanie do atrybutu spowoduje jego powstanie; SimpleNamespace się temu oprze.
jdzions
32

Istnieje kilka sposobów na osiągnięcie tego celu. Zasadniczo potrzebujesz obiektu, który można rozszerzyć.

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

obj.b = lambda:None

class Test:
  pass
obj.c = Test()
evilpie
źródło
14
obj.a = type('', (), {})
iman
26

mockModuł jest w zasadzie wykonane za to.

import mock
obj = mock.Mock()
obj.a = 5
Dunatotatos
źródło
3
Wadą jest to, że jest to zależność zewnętrzna
Kangur,
5
unittest.Mockjest częścią standardowej biblioteki od Python 3.3 ( docs.python.org/3/library/unittest.mock.html )
illagrenan
2
Myślę, że to zależy od użycia twojego kodu. Jeśli jest to kod produkcyjny, nie chciałbym, żeby był mockw nim trochę . Po prostu czuję się dziwnie.
Mike de Klerk
21

Teraz możesz zrobić (nie jestem pewien, czy to ta sama odpowiedź, co zło):

MyObject = type('MyObject', (object,), {})
obj = MyObject()
obj.value = 42
andreabedini
źródło
Odpowiedź @ evilpie ustawia atrybuty bezpośrednio na MyObject (klasa), a nie na instancję taką jak twoja.
jfs
18

Wypróbuj poniższy kod:

$ 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
źródło
1
Czy możesz wyjaśnić więcej na temat tego, co to robi? chociaż kod może być przydatny do rozwiązania tego problemu, wyjaśnienie go może znacznie wykraczać poza jeden problem.
DeadChex,
1
@DeadChex Wyraźnie tworzy nową klasę (obiekt), która jest pustą klasą z właściwościami obiektu, i przechowuje atrybuty wewnątrz klasy. Jest to nawet lepsze niż instalowanie większej liczby modułów lub poleganie na lambdach.
m3nda
2
Nie jestem pewien, dlaczego nie ma więcej pozytywnych opinii. Czy istnieje powód, aby nie używać tego dla podstawowej klasy kontenera? Wygląda na to, że działa dobrze w Pythonie 2.7, 2.6 i 3.4
użytkownik5359531
17

Możesz także użyć bezpośrednio obiektu klasy; tworzy przestrzeń nazw:

class a: pass
a.somefield1 = 'somevalue1'
setattr(a, 'somefield2', 'somevalue2')
Ernesto
źródło
9

jak mówią doktorzy :

Uwaga : objectnie nie mają __dict__, więc można nie przypisze arbitralnych atrybutów do instancji objectklasy.

Możesz po prostu użyć instancji klasy fikcyjnej.

SilentGhost
źródło
2

Te rozwiązania są bardzo pomocne podczas testowania. Opierając się na odpowiedziach innych osób, robię to w Pythonie 2.7.9 (bez metody static otrzymuję TypeError (niezwiązana metoda ...):

In [11]: auth = type('', (), {})
In [12]: auth.func = staticmethod(lambda i: i * 2)
In [13]: auth.func(2)
Out[13]: 4
Robpol86
źródło
1

Z jakich obiektów korzystasz? Właśnie wypróbowałem to na przykładowej klasie i działało dobrze:

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

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

I dostałem 123jako odpowiedź.

Jedyną sytuacją, w której widzę ten błąd, jest próba setattrużycia wbudowanego obiektu.

Aktualizacja: Z komentarza jest to powtórzenie: Dlaczego nie możesz dodawać atrybutów do obiektu w pythonie?

jneves
źródło
bc jest ustawiony na object (), a nie na zdefiniowaną klasę
John
0

Przybywam do późna w ciągu dnia, ale tutaj jest moja grosza z obiektem, który akurat zawiera pewne przydatne ścieżki w aplikacji, ale możesz go dostosować do dowolnego miejsca, w którym chcesz uzyskać swoisty słownik informacji, do których możesz uzyskać dostęp za pomocą notacji getattr i kropki (o czym myślę, że tak naprawdę chodzi o to pytanie):

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

To jest fajne, ponieważ teraz:

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

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

Używa to obiektu funkcyjnego jak powyższe odpowiedzi, ale używa funkcji do uzyskania wartości (nadal możesz używać, (getattr, x_path, 'repository')a nie x_path('repository')wolisz).

Paul Whipp
źródło
0

Jeśli potrafimy ustalić i zsumować wszystkie atrybuty i wartości razem przed utworzeniem zagnieżdżonego obiektu, moglibyśmy stworzyć nową klasę, która pobiera argument słownika podczas tworzenia.

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

Możemy również zezwolić na argumenty słów kluczowych. Zobacz ten post .

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')
Harlem Wiewiórka
źródło
-2
di = {}
for x in range(20):
    name = '_id%s' % x
    di[name] = type(name, (object), {})
    setattr(di[name], "attr", "value")
lmokto
źródło
-2

Innym sposobem widzę, w ten sposób:

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)
Pablo Emmanuel De Leo
źródło
możesz dodać w dołączać nazwę attr this di [name] .append ([at, cmds.getAttr (name + '.' + at) [0]])
Pablo Emmanuel De Leo
1
Dodaje to bardzo dużą niestandardową zależność, a prosty class a: passdaje całą wymaganą moc.
Alexis Paques,