29
inspect.getmembers(object[, predicate])

Return all the members of an object in a list of (name, value) pairs sorted by name.

I want to use this method, but I don't want the members to be sorted. I want them returned in the same order they were defined. Is there an alternative to this method?


My use case is creating a form like so:

class RegisterForm(Form):
    username = Field(model_field='username', filters=validators.minlength(3))
    password1 = Field(model_field='password', widget=widgets.PasswordInput)
    password2 = Field(widget=widgets.PasswordInput)
    first_name = Field(model_field='first_name')
    last_name = Field(model_field='last_name')
    address = SubForm(form=AddressForm, model_field='address')

I want the fields to be rendered in the same order they are defined.

Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
mpen
  • 272,448
  • 266
  • 850
  • 1,236

6 Answers6

16

You can dig around to find the line number for methods, not sure about other members:

import inspect

class A:
    def one(self):
        pass

    def two(self):
        pass

    def three(self):
        pass

    def four(self):
        pass

def linenumber_of_member(m):
    try:
        return m[1].__func__.__code__.co_firstlineno
    except AttributeError:
        return -1

a = A()
l = inspect.getmembers(a)
print(l)
l.sort(key=linenumber_of_member)
print(l)

prints:

[('__doc__', None), ('__module__', '__main__'), ('four', <bound method A.four of <__main__.A instance at 0x0179F738>>), ('one', <bound method A.one of <__main__.A instance at 0x0179F738>>), ('three', <bound method A.three of <__main__.A instance at 0x0179F738>>), ('two', <bound method A.two of <__main__.A instance at 0x0179F738>>)]
[('__doc__', None), ('__module__', '__main__'), ('one', <bound method A.one of <__main__.A instance at 0x0179F738>>), ('two', <bound method A.two of <__main__.A instance at 0x0179F738>>), ('three', <bound method A.three of <__main__.A instance at 0x0179F738>>), ('four', <bound method A.four of <__main__.A instance at 0x0179F738>>)]
Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
  • This doesn't account for functions being imported from somewhere else, it will look at their line numbers, which will be unrelated to the current file. For example if you had `import os` then in your class you did `five = os.makedirs`, its `.__code__.co_firstlineno` would be [its line number in `os`](https://github.com/python/cpython/blob/c1a3f9a9cbf5cf96ced5a2b16572cba7be964377/Lib/os.py#L200) (current 200 for me on Python 3.9), not in your module. You'll have to [filter those members out](https://stackoverflow.com/questions/65251833/get-all-defined-functions-in-python-module). – Boris Verkhovskiy Dec 12 '20 at 08:31
5

The attributes (methods and other members) of an object is usually looked up through an object's special __dict__ attribute which is a standard Python dictionary. It doesn't guarantee any specific ordering.

If an attribute is not found in the object's __dict__ the class's is searched instead (where methods usually reside) and so on until the whole inheritance chain has been traversed.

Here is some custom inspection done in the interactive prompt to illustrate this (Python 3.1):

>>> class Klass():
...     def test(self):
...             pass
...
>>> k = Klass()
>>> k.__dict__
{}
>>> k.__class__.__dict__.items()
[('test', <function test at 0x00000000024113C8>), ('__dict__', <attribute '__dic
t__' of 'Klass' objects>), ('__module__', '__main__'), ('__weakref__', <attribut
e '__weakref__' of 'Klass' objects>), ('__doc__', None)]

Would I have put a constructor (__init__) in Klass and set an attribute through self it would've shown up in k.__dict__.

You can circumvent this by using a custom metaclass. The documentation contains an example which does exactly what you want.

See the bottom of this page for the OrderedClass example.

Don't know what version of Python you have so I assumed latest.

mpen
  • 272,448
  • 266
  • 850
  • 1,236
Skurmedel
  • 21,515
  • 5
  • 53
  • 66
  • Python 2.6. So I have to use a metaclass to keep the members in order? – mpen Jul 02 '10 at 22:24
  • @Mark: I should've added, you still can't use inspect or dir, since the `type` object will use a straight up dictionary in the end. But they circumvent that through a custom attribute on subclasses called `members` which is ordered. – Skurmedel Jul 03 '10 at 00:23
  • 1
    @Skurmedel: Couldn't we explicitly replace `__dict__` with an ordered dict No matter though, I'm fine having an ordered member variable. – mpen Jul 03 '10 at 00:30
  • 1
    @Mark: Sadly no, I think `type` is expecting a `dict` and it seems that if I pass an OrderedDict it will end up a dict anyway, perhaps through `dict(x)`. I need to read up some more, but maybe `type` is implemented in C and needs the default dictionary type. – Skurmedel Jul 10 '10 at 00:31
  • 2
    @Mark: Python 2.6.5 source for `type`: http://svn.python.org/view/python/tags/r265/Objects/typeobject.c?view=markup I've been reading the (frankly huge) function `type_new` and I think it's copying the passed in dictionary to a new `dict`, which would explain why it ends up as a `dict` no matter what in my tests. – Skurmedel Jul 10 '10 at 00:50
  • Well that's dumb :( So hard to do something that ought to be so simple! – mpen Jul 10 '10 at 03:12
5

I don't think Python 2.6 has a __prepare__ method, so I can't swap out the default dict for an ordered one. I can, however, replace it using a metaclass and the __new__ method. Instead of inspecting line numbers, I think its easier and more efficient to just use a creation counter.

class MetaForm(type):
    def __new__(cls, name, bases, attrs):
        attrs['fields'] = OrderedDict(
            sorted(
                [(name, attrs.pop(name)) for name, field in attrs.items() if isinstance(field, Field)],
                key=lambda t: t[1].counter
            )
        )
        return type.__new__(cls, name, bases, attrs)

class Form(object):
    __metaclass__ = MetaForm

class Field(object):
    counter = 0
    def __init__(self):
        self.counter = Field.counter
        Field.counter += 1
mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • This doesn't work if you have different classes of objects and want to maintain order between all of them... I guess I could make them all inherit from the same `OrderedObject` though. – mpen Jul 09 '10 at 21:59
  • Starting with CPython 3.6 and all other Python implementations starting with 3.7, the builtin `dict` is ordered by insertion so you don't need to use `OrderedDict` for this – Boris Verkhovskiy Dec 12 '20 at 08:37
  • 1
    This is the most resilient solution IMO, and it also works for fields scattered in multiple classes in an inheritance hierarchy. – Codoscope Sep 26 '22 at 15:43
3

You can use the builtin function vars() as an alternative for inspect.getmembers that returns members in the order they were defined:

>>> class RegisterForm:
...     username = ""
...     password1 = ""
...     password2 = ""
...     first_name = ""
...     last_name = ""
...     address = ""
...
>>> list(vars(RegisterForm).keys())
['__module__', 'username', 'password1', 'password2', 'first_name', 'last_name', 'address', '__doc__']

This works starting with CPython 3.6 and all other Python implementations starting with Python 3.7, because dicts are now ordered by insertion, which means the underlying __dict__ property of a class (which is what vars() returns) is ordered.

Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
2
members = []
for name, obj in inspect.getmembers(module):
    source, start_line = inspect.getsourcelines(obj)
    members.append([name, obj, start_line])

def _line_order(value):
    return value[2]

members.sort(key = _line_order)
Andrew
  • 4,696
  • 1
  • 19
  • 17
1

In reference to Ned Batchelder's answer above, in Python 3 line numbers of a method m can be gotten with m.__func__.__code__.co_firstlineno

inspect — Inspect live objects

M--
  • 25,431
  • 8
  • 61
  • 93
Alec Peters
  • 221
  • 3
  • 10