Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to display an objects instance variables in IPython like Matlab does?

I'm trying to move away from Matlab to Python. While the magic ? in IPython is nice, one very nice feature of Matlab is that you can see on the command line (by omitting the ;) the instance variables (called properties in Matlab) of the object in question. Is this possible in python (I guess via IPython)?

Ideally a class like this:

class MyClass(object):
    _x = 5

    @property
    def x(self):
        return self._x + 100

    @x.setter
    def x(self, value):
        self._x = value + 1

    def myFunction(self, y):
        return self.x ** 2 + y

Would display something like:

mc = Myclass()
mc
<package.MyClass> <superclass1> <superclass2>

Attributes:
_x: 5
 x: 105

Method Attributes:
myFunction(self, y)

Is that possible via overriding the print method (if such a thing exits) of the class? Or via a magic method in ipython?

like image 523
user1507844 Avatar asked Mar 18 '13 22:03

user1507844


People also ask

Can instance variables be objects?

An object that is created using a class is said to be an instance of that class. We will sometimes say that the object belongs to the class. The variables that the object contains are called instance variables.

How do you use an instance variable in Python?

We can access the instance variable using the object and dot ( . ) operator. In Python, to work with an instance variable and method, we use the self keyword. We use the self keyword as the first parameter to a method.

How do you identify an object in IPython?

The command whos and linemagic %whos are available in IPython, but are not part of standard Python. Both of these will list current variables, along with some information about them.

Can you see variables in Jupyter notebook?

Within a Python Notebook, it's possible to view, inspect, sort, and filter the variables within your current Jupyter session. By selecting the Variables icon in the main toolbar after running code and cells, you'll see a list of the current variables, which will automatically update as variables are used in code.


2 Answers

The short answer is that there is no way to get a list of all attributes of an object in Python, because the attributes could be generated dynamically. For an extreme example, consider this class:

>>> class Spam(object):
...     def __getattr__(self, attr):
...         if attr.startswith('x'):
...             return attr[1:]
>>> spam = Spam()
>>> spam.xeggs
'eggs'

Even if the interpreter could someone figure out a list of all attributes, that list would be infinite.

For simple classes, spam.__dict__ is often good enough. It doesn't handle dynamic attributes, __slots__-based attributes, class attributes, C extension classes, attributes inherited from most of the above, and all kinds of other things. But it's at least something—and sometimes, it's the something you want. To a first approximation, it's exactly the stuff you explicitly assigned in __init__ or later, and nothing else.

For a best effort aimed at "everything" aimed at human readability, use dir(spam).

For a best effort aimed at "everything" for programmatic use, use inspect.getmembers(spam). (Although in fact the implementation is just a wrapper around dir in CPython 2.x, it could do more—and in fact does in CPython 3.2+.)

These will both handle a wide range of things that __dict__ cannot, and may skip things that are in the __dict__ but that you don't want to see. But they're still inherently incomplete.

Whatever you use, to get the values as well as the keys is easy. If you're using __dict__ or getmembers, it's trivial; the __dict__ is, normally, either a dict, or something that acts close enough to a dict for your purposes, and getmembers explicitly returns key-value pairs. If you're using dir, you can get a dict very easily:

{key: getattr(spam, key) for key in dir(spam)}

One last thing: "object" is a bit of an ambiguous term. It can mean "any instance of a class derived from object", "any instance of a class", "any instance of a new-style class", or "any value of any type at all" (modules, classes, functions, etc.). You can use dir and getmembers on just about anything; the exact details of what that means are described in the docs.

One even-last-er thing: You may notice that getmembers returns things like ('__str__', <method-wrapper '__str__' of Spam object at 0x1066be790>), which you probably aren't interested in. Since the results are just name-value pairs, if you just want to remove __dunder__ methods, _private variables, etc., that's easy. But often, you want to filter on the "kind of member". The getmembers function takes a filter parameter, but the docs don't do a great job explaining how to use it (and, on top of that, expect that you understand how descriptors work). Basically, if you want a filter, it's usually callable, lambda x: not callable(x), or a lambda made up of a combination of inspect.isfoo functions.

So, this is common enough you may want to write it up as a function:

def get_public_variables(obj):
    return [(name, value) for name, value 
            in inspect.getmembers(obj, lambda x: not callable(x))
            if not name.startswith('_')]

You can turn that into a custom IPython %magic function, or just make a %macro out of it, or just leave it as a regular function and call it explicitly.


In a comment, you asked whether you can just package this up into a __repr__ function instead of trying to create a %magic function or whatever.

If you've already got all of your classes inheriting from a single root class, this is a great idea. You can write a single __repr__ that works for all of your classes (or, if it works for 99% of them, you can override that __repr__ in the other 1%), and then every time you evaluate any of your objects in the interpreter or print them out, you'll get what you want.

However, a few things to keep in mind:

Python has both __str__ (what you get if you print an object) and __repr__ (what you get if you just evaluate an object at the interactive prompt) for a reason. Usually, the former is a nice human-readable representation, while the latter is something that's either eval-able (or typable-into-the-interactive-prompt), or the concise angle-bracket form that gives you just enough to distinguish the type and identity of an object.

That's just a convention rather than a rule, so you can feel free to break it. However, if you are going to break it, you may still want to make use of the str/repr distinction—e.g., make repr give you a complete dump of all the internals, while str shows just the useful public values.

More seriously, you have to consider how repr values are composed. For example, if you print or repr a list, you get, effectively, '[' + ', '.join(map(repr, item))) + ']'. This is going to look pretty odd with a multi-line repr. And it'll be even worse if you use any kind of pretty-printer that tries to indent nested collections, like the one that's built into IPython. The result probably won't be unreadable, it'll just defeat the benefits that the pretty-printer is meant to provide.

As for the specific stuff you want to display: That's all pretty easy. Something like this:

def __repr__(self):
    lines = []

    classes = inspect.getmro(type(self))
    lines.append(' '.join(repr(cls) for cls in classes))

    lines.append('')
    lines.append('Attributes:')
    attributes = inspect.getmembers(self, callable)
    longest = max(len(name) for name, value in attributes)
    fmt = '{:>%s}: {}' % (longest, )
    for name, value in attributes:
        if not name.startswith('__'):
            lines.append(fmt.format(name, value))

    lines.append('')
    lines.append('Methods:')
    methods = inspect.getmembers(self, negate(callable))
    for name, value in methods:
        if not name.startswith('__'):
            lines.append(name)

    return '\n'.join(lines)

Right-justifying the attribute names is the hardest part here. (And I probably got it wrong, since this is untested code…) Everything else is either easy, or fun (playing with different filters to getmembers to see what they do).

like image 53
abarnert Avatar answered Sep 29 '22 04:09

abarnert


I was able to achieve what I wanted with IPython (at least roughly) by implementing _repr_pretty_:

def get_public_variables(obj):
    from inspect import getmembers
    return [(name, value) for name, value in
            getmembers(obj, lambda x: not callable(x)) if
            not name.startswith('__')]


class MySuperClass(object):
    def _repr_pretty_(self, p, cycle):
        for (name, value) in get_public_variables(self):
            f = '{:>12}{} {:<} \n'
            line = f.format(str(name), ':', str(value))
            # p.text(str(name) + ': ' + str(value) + '\n')
            p.text(line)

class MyClass(MySuperClass):
    _x = 5

    @property
    def x(self):
        return self._x + 100

gives me out of:

mc = MyClass()
mc
Out[15]: 
          _x: 5 
           x: 105 

clearly there is some fine tuning to be done in terms of whitespace, etc. But this was roughly what I was trying to accomplish

like image 44
user1507844 Avatar answered Sep 29 '22 04:09

user1507844