Reputation: 107
Let's say I have the following class:
class Foo(object):
def __init__(self, value):
self.d = value
self.a = value
self.s = value
self.k = value
I want to retrieve the instance variables in the order of declaration.
I tried with vars()
without any success:
list(vars(Foo('value')).keys())
# ['a', 'k', 's', 'd']
What I would like:
list(magic_method(Foo('value')).keys())
# ['d', 'a', 's', 'k']
Edit:
Obviously, there would be a different value for each field.
My goal is to generate XML thanks to the object variables. To be valid, the XML tags has to be in the correct order.
This, combined with __iter__
override would allow me to only have to manage dictionaries of object to generate my XML.
Let's take a library as example. Imagine you have a class Book
, Date
, Person
, Author
and Borrower
:
class Book(object):
def self.__init__()
self._borrower = Borrower()
self._author = Author()
class Date(object)
def __init__(self, date):
self._date = date
class Person(object):
def __init__(self, name):
self._name = name
class Author(object):
def __init__(self):
self._person = Person("Daniel")
class Borrower(object):
def __init__(self):
self._person = Person("Jack")
self._date = Date("2016-06-02")
I would like to create the following XML:
<Book>
<Borrower>
<Person>Jack</Person>
<Date>2016-06-02</Date>
</Borrower>
<Author>
<Person>Daniel</Person>
</Author>
</Book>
I know the classes might look weird (like Date here), but I wanted to make the problem as simple as possible (and there are fields that make perfect sense). In practice, I would query a database and probably pass an record identifier in initializers. The point is that there are some data that respects the same syntax (i.e. Person here).
To summarize, I would like to create such an XML using Python objects. Order matters. That's why I wanted to retrieve variables in order for that purpose: I could then extract the class and generate the XML tag.
Upvotes: 5
Views: 3042
Reputation: 905
If you want ordering of object variables you can use something like that:
from collections import OrderedDict
class FooModel(object):
def __new__(cls, *args, **kwargs):
instance = object.__new__(cls)
instance.__odict__ = OrderedDict()
return instance
def __setattr__(self, key, value):
if key != '__odict__':
self.__odict__[key] = value
object.__setattr__(self, key, value)
def keys(self):
return self.__odict__.keys()
def iteritems(self):
return self.__odict__.iteritems()
class Foo(FooModel):
def __init__(self, value):
self.d = value
self.a = value
self.s = value
self.k = value
Output:
>>> f = Foo('value')
>>> f.x = 5
>>> f.y = 10
>>> f.a = 15
>>> f2 = Foo('value')
>>> print "f.keys()", f.keys()
f.keys() ['d', 'a', 's', 'k', 'x', 'y']
>>> print "f2.keys()", f2.keys()
f2.keys() ['d', 'a', 's', 'k']
print list(f.iteritems())
[('d', 'value'), ('a', 15), ('s', 'value'), ('k', 'value'), ('x', 5), ('y', 10)]
Upvotes: 4
Reputation: 15310
You can implement the __setattr__
method to keep track of these things for you:
#!python3
class OrderedAttrs:
def __init__(self, d, a, s, k):
self._order = []
self.d = d
self.a = a
self.s = s
self.k = k
def __setattr__(self, name, value):
super().__setattr__(name, value)
if not name in self._order:
self._order.append(name)
return value
def ordered_attrs(self, with_order=False):
return [(k,getattr(self, k)) for k in self._order if k != '_order' or with_order]
oa = OrderedAttrs('dee', 'eh', 'ess', 'kay')
oa.foo = 'bar'
oa.baz = 'moo'
print("Default:",oa.ordered_attrs())
print("With _order:", oa.ordered_attrs(with_order=True))
Upvotes: 1
Reputation: 23064
You can give your class a method that is similar to __dict__
, but which returns an OrderedDict instead.
from collections import OrderedDict
class Foo(object):
def __init__(self, d, a, s, k):
self.d = d
self.a = a
self.s = s
self.k = k
def ordered_dict(self):
return OrderedDict(
(key, getattr(self, key)) for key in ('d', 'a', 's', 'k')
)
This works like so:
>>> myfoo = Foo('bar', 1, 7, 'foo')
>>> myfoo.ordered_dict()
OrderedDict([('d', 'bar'),('a', 1), ('s', 7), ('k', 'foo')])
>>> list(myfoo.ordered_dict())
['d', 'a', 's', 'k']
Upvotes: -1
Reputation: 6597
You can't easily do this without using metaclasses (as @Daniel said). But if you want a workaround that produces a similar effect, you can store all of your "instance variables" in a collections.OrderedDict
:
from collections import OrderedDict
class Foo(object):
def __init__(self, value):
self.instvars = OrderedDict()
self.instvars['d'] = value
self.instvars['a'] = value
self.instvars['s'] = value
self.instvars['k'] = value
print(list(Foo('value').instvars.keys()))
# OUT: ['d', 'a', 's', 'k']
Upvotes: 0