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?
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.
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.
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.
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.
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).
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With