Como posso criar um objeto e adicionar atributos a ele?

311

Quero criar um objeto dinâmico (dentro de outro objeto) em Python e depois adicionar atributos a ele.

Eu tentei:

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

mas isso não funcionou.

Alguma ideia?

editar:

Estou definindo os atributos de um forloop que percorre uma lista de valores, por exemplo

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

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

No exemplo acima, gostaria de obter obj.a.attr1, obj.a.attr2, obj.a.attr3.

Eu usei a setattrfunção porque não sabia como fazer a obj.a.NAMEpartir de um forloop.

Como eu definiria o atributo com base no valor do pexemplo acima?

John
fonte
4
O que você quer dizer com "não funcionou"? Suponho que tenha gerado uma exceção AttributeError, certo?
Josh Wright
1
sim. 'objeto' objeto tem 'SomeField' nenhum atributo
John
6
Por que você está fazendo isso? Um "objeto" genérico não tem significado real . Qual é o significado da coisa que você está criando? Por que não é uma classe apropriada ou um nome nomeado?
S.Lott 13/05
1
O exemplo não é mínimo e confuso para mim ou simplesmente não vejo por que você não trabalha com alguns a = object()e precisa obj.a = object(). Mais uma vez, estou falando do exemplo: no seu código real, um objeto dentro de um objeto pode ser útil.
Kon psych

Respostas:

215

Você pode usar minha receita antiga do Bunch , mas se não quiser criar uma "classe de agrupamento", já existe uma muito simples no Python - todas as funções podem ter atributos arbitrários (incluindo funções lambda). Portanto, o seguinte funciona:

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

Se a perda de clareza em comparação com a venerável Bunchreceita está correta, é uma decisão de estilo que, é claro, deixarei para você.

Alex Martelli
fonte
25
@FogleBird, uma decisão de estilo, como mencionei. Alguns especialistas em CS treinados, por exemplo, no cálculo lambda da Igreja, estão acostumados a pensar nas funções (lambdas) como o tipo fundamental de todos os dados (o número inteiro 23, por exemplo, pode ser visto como equivalente a lambda: 23), de modo que esses especialistas usam lambdas para esse propósito. presumivelmente não sentiria nada como "um hack". Pessoalmente, não gosto muito de lambda Python - mas isso é uma questão de gosto pessoal.
Alex Martelli
E, em alguns casos, considerar se o lambdapadrão se encaixa ou não no seu caso de uso pode levar você a perceber que algo que você pensava originalmente como dados é na verdade mais parecido com uma função - ou, em qualquer caso, com um functor.
Kyle Strand
5
@ naught101, uma função é um objeto, em Python, então sua objeção é insondável.
Alex Martelli 26/01
6
@ naught101, evitar a criação de um novo tipo (reutilizar um existente) não complica, simplifica. Hoje em dia, na verdade, pode preferir, from argparse import Namespaceembora eu desejasse que ele morasse em outro lugar *, por exemplo collection) - novamente reutilizando um tipo existente, apenas um melhor, e ainda evitando a criação de novos tipos. Mas, não estava lá então :-).
Alex Martelli
1
Veja a resposta abaixo de "JF Sebastian" sobre SimpleNamespace no módulo de tipos. Se a sua versão do python suporta, esta é a melhor solução (e exatamente o que SimpleNamespace é projetado para)
Tim Richardson
334

O interno objectpode ser instanciado, mas não pode ter nenhum atributo definido nele. (Eu gostaria que pudesse, para esse propósito exato.) Ele não precisa __dict__conter os atributos.

Eu geralmente apenas faço isso:

class Object(object):
    pass

a = Object()
a.somefield = somevalue

Quando posso, dou à Objectclasse um nome mais significativo, dependendo do tipo de dados que estou colocando nela.

Algumas pessoas fazem algo diferente, onde usam uma subclasse dictque permite que o acesso aos atributos chegue às chaves. (em d.keyvez de d['key'])

Editar : para adicionar à sua pergunta, usar setattré bom. Você simplesmente não pode usar setattrem object()instâncias.

params = ['attr1', 'attr2', 'attr3']
for p in params:
    setattr(obj.a, p, value)
FogleBird
fonte
9
Ele pode ser instanciado, mas não usado para nada de útil depois de feito. foo = object()obras, mas você simplesmente não pode fazer muita coisa com ele
Daniel DiPaolo
Oi. Obrigado pela resposta. Atualizei meu problema acima. veja a edição. Voce sabe a resposta para isto?
John
desculpe, ainda quero defini-lo no objeto. veja atualização acima.
John
Eu realmente gosto da sua resposta e acho que vou inclinar essa direção no futuro. Eu usei sobre tudo o mais neste post, exceto por essa metodologia muito simples, compreensível e legível. Usar type....ou lambda nunca foi meu favorito, como vômito de texto no meu código. Mas essa idéia é ótima para usar objetos para manter propriedades. Deixa o código mais legível b / c quando vejo lambda, diminuo a velocidade da leitura para 25% enquanto seu caminho faz total sentido! Obrigado.
Marc
ótima resposta, a única coisa que mudei foi usar Structcomo o nome da classe para torná-la mais óbvia. Me salvou uma tonelada de digitação ["e "], Saúde!
pragman
137

types.SimpleNamespaceclasse no Python 3.3+ :

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

collections.namedtuple, typing.NamedTuplepoderia ser usado para objetos imutáveis. PEP 557 - Classes de dados sugere uma alternativa mutável.

Para uma funcionalidade mais rica, você pode tentar o attrspacote . Veja um exemplo de uso .

jfs
fonte
3
Se você precisa de algo que funciona com o Python 2.7, você também pode tentar a argparse.Namespaceclasse
RolKau
Concordo - eu ficaria curioso se houver uma desvantagem aqui, mas esse é um custo incrivelmente útil do python 3.3+.
ghukill
Droga! isso não está disponível no 2.7?
Roel
O attrspacote @Roel suporta Python 2.7
jfs
Essa me parece uma solução melhor do que o unittest.mock; o último é um pouco pesado e um pouco mais maleável. Com um objeto simulado, a simples atribuição de um atributo fará com que ele entre em existência; O SimpleNamespace resistirá a isso.
jdzions
32

Existem algumas maneiras de atingir esse objetivo. Basicamente, você precisa de um objeto que seja extensível.

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

obj.b = lambda:None

class Test:
  pass
obj.c = Test()
evilpie
fonte
14
obj.a = type('', (), {})
Iman
27

O mockmódulo é basicamente feito para isso.

import mock
obj = mock.Mock()
obj.a = 5
Dunatotatos
fonte
3
Disadvantge é que é uma dependência externa
Kangur
6
unittest.Mockfaz parte da biblioteca padrão desde o Python 3.3 ( docs.python.org/3/library/unittest.mock.html )
illagrenan
2
Depende do uso do seu código, eu acho. Se é um código de produção, eu não gostaria mockdisso. Apenas me parece estranho.
Mike de Klerk
21

Agora você pode fazer (não tenho certeza se é a mesma resposta que evilpie):

MyObject = type('MyObject', (object,), {})
obj = MyObject()
obj.value = 42
andreabedini
fonte
A resposta de @ evilpie define atributos diretamente no MyObject (a classe), não em sua instância como a sua.
JFS
18

Experimente o código abaixo:

$ 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
fonte
1
Você pode explicar mais sobre o que isso faz? Embora o código possa ser útil para solucionar esse problema, explicá-lo pode ir muito além de apenas um problema.
23915 DeadChex
1
@DeadChex Claramente, ele cria uma nova classe (objeto) que é uma classe vazia com propriedades de objeto e armazena os atributos dentro da classe. Isso é ainda melhor do que instalar mais módulos ou confiar em lambdas.
M3nda
2
Não sei por que isso não tem mais votos positivos. Existe um motivo para não usar isso para uma classe básica de contêiner? Parece funcionar bem no Python 2.7, 2.6 e 3.4
user5359531 15/06
17

Você também pode usar um objeto de classe diretamente; ele cria um espaço para nome:

class a: pass
a.somefield1 = 'somevalue1'
setattr(a, 'somefield2', 'somevalue2')
Ernesto
fonte
9

como dizem os médicos :

Nota : objectse não tiver um __dict__, então você não pode atributos arbitrários atribuir a uma instância da objectclasse.

Você poderia apenas usar instância de classe fictícia.

SilentGhost
fonte
2

Essas soluções são muito úteis durante o teste. Com base nas respostas de todos os outros, faço isso no Python 2.7.9 (sem staticmethod, recebo um TypeError (método independente ...):

In [11]: auth = type('', (), {})
In [12]: auth.func = staticmethod(lambda i: i * 2)
In [13]: auth.func(2)
Out[13]: 4
Robpol86
fonte
1

Quais objetos você está usando? Apenas tentei isso com uma classe de amostra e funcionou bem:

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

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

E eu tenho 123como resposta.

A única situação em que vejo essa falha é se você estiver tentando um setattrobjeto interno.

Atualização: A partir do comentário, é uma repetição de: Por que você não pode adicionar atributos ao objeto em python?

jneves
fonte
bc está definido como objeto () não é uma classe definida
John
0

Chegando a esse final do dia, mas aqui está o meu pennyworth com um objeto que contém alguns caminhos úteis em um aplicativo, mas você pode adaptá-lo para qualquer coisa em que você queira um pouco de informação que possa acessar com a notação getattr e dot (sobre o que eu acho que essa pergunta realmente é):

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

Isso é legal porque agora:

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

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

Portanto, isso usa o objeto de função como as respostas acima, mas usa a função para obter os valores (você ainda pode usar (getattr, x_path, 'repository')e não x_path('repository')se preferir).

Paul Whipp
fonte
0

Se pudermos determinar e agregar todos os atributos e valores antes de criar o objeto aninhado, poderemos criar uma nova classe que use um argumento de dicionário na criação.

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

Também podemos permitir argumentos de palavras-chave. Veja este 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')
HarlemSquirrel
fonte
-2
di = {}
for x in range(20):
    name = '_id%s' % x
    di[name] = type(name, (object), {})
    setattr(di[name], "attr", "value")
lmokto
fonte
-2

De outra maneira, eu vejo, desta maneira:

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
fonte
você pode adicionar no apêndice o nome attr this di [name] .append ([at, cmds.getAttr (name + '.' + at) [0]])
Pablo Emmanuel De Leo
1
Isso está adicionando uma dependência não-padrão muito grande, enquanto uma simples class a: passfornece toda a energia necessária.
Alexis Paques