Date Modified Tags Python

My primary Python text editor these days is PyCharm. I have found over time that its GUI based debugging, profiling, and incredible introspection features outweigh the weightiness that comes along with using it. Often I find that stepping through its debugger allows me to inspect whatever object I am concerned with and solve my problem.

Occasionally though, I have found myself wishing for an experience that it a bit more interactive. I was always looking for a way to stop my code at some point and then get into an environment more like the REPL.

In case you're wondering, REPL stands for Read-Eval-Print-Loop and it is also referred to as the interactive interpreter.

I had obviously used the default Python REPL before but was always frustrated by its non-existent tab completion and I also always found it very annoying trying to enter/edit multi-line blocks of code in it. So, I basically avoided the REPL except for short snippets of code.

About a year ago though I started using a new technique for debugging/introspection whenever I feel like I just need to get more interactive than PyCharm will allow me or for those times when I can't use PyCharm for whatever reason.

Let's go through a quick example of what I mean.

Say we have the following piece of code that queries the Hacker News API for some data we want:

# example.py

import requests

base_url = 'https://hacker-news.firebaseio.com'
api_ver = 'v0'
hn_object = 'item'
item_num = 13206438

r = requests.get('{}/{}/{}/{}.json'.format(
    base_url,
    api_ver,
    hn_object,
    item_num))
print(r)

This is a bit contrived, but let's say that for whatever reason, this code is tripping us up and we want to work with it in the REPL so we can try a few things out. Maybe its been a long day and we can't figure out how to print the results of the request and instead are just getting <Response [200]> from the above print statement.

Ptpython

Let's not use the regular Python REPL though. There are more featureful REPLs out there for Python and one of the best I have found is Ptpython.

We can install it with:

$ pip install ptpython

Now, execute it with:

$ ptpython

You will see yourself dropped into a Python REPL that looks somewhat similar to the normal python REPL.

Executing python files in the REPL

Now, let's execute the code we have in the REPL:

If you are using Python 2, use this:

execfile("./example.py") 

Or, if you are using Python 3 like I am:

exec(open("./example.py").read())

Be sure to bask for a moment in the glory of Ptpython's auto-completion capabilities when you start writing the file name!

The code will execute and you will now be able to manipulate the objects in memory:

>>> r
<Response [200]>

Hum, so typing r returns back the HTTP response code. 200 means that the request was successful.

Now, type r. and let's take a look at some of the methods/attributes available to us with this requests object.

If you search through your menu you should see an attribute called text. That looks right! Let's hit it and see what happens.

>>> r.text
'{"by":"kogir","descendants":80,"id":13206438,"kids":[13206637,13209622,13207930,13206568,13206991,13208217,13208369,13206582,13209435,13206819],"score":265,"time":1482080017,"title":"Making SQL Server run on Linux","type":"story","url":"https://blogs.technet.microsoft.com/dataplatforminsider/2016/12/16/sql-server-on-linux-how-introduction/"}'

Ah , perfect! Looks like we are getting some json back! Let's go ahead and play around with this json a bit and write some simple code to get the url of that Hacker News story.

Ptpython docstrings display

First though, let's turn on 'show docstring' in Ptpython:

Nice! That should help us out.

Now let's use the json library to load our json string into a Python dictionary.

>>> response_text = r.text
>>> import json
>>> response_json = json.loads(response_text)

Notice that when we type out the loads() method we get a very helpful docstring from the source code that will help us understand the inner workings of the method.

Now we have our json string loaded into a python dict:

>>> response_json
{'kids': [13206637, 13209622, 13207930, 13206568, 13206991, 13208217, 13208369, 13206582, 13209435, 13206819], 'by': 'kogir', 'score': 265, 'type': 'story', 'time': 1482080017, 'url': 'https://blogs.technet.microsoft.com/dataplatforminsider/2016/12/16/sql-server-on-linux-how-introduction/', 'descendants': 80, 'title': 'Making SQL Server run on Linux', 'id': 13206438}

If this were some very complex json, then it might have been helpful to throw the json string into http://www.jsoneditoronline.org/ so we can expand and collapse the json to get a better idea of how its structured:

This json's structure is fairly simple though, so we can play around in ptpython to get the url:

>>> response_json['url']
'https://blogs.technet.microsoft.com/dataplatforminsider/2016/12/16/sql-server-on-linux-how-introduction/'

Nice! Looks like we got the URL.

Let's also go over some methods and functions that python gives us for more introspection into Python objects.

dir()

dir() is an important function that returns a list of the attributes and methods that belong to the object that you pass into it.

Remember, r is the requests objects we created earlier in the code snippets above.

>>> dir(r)
['__attrs__', '__bool__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__nonzero__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_content', '_content_consumed', 'apparent_encoding', 'close', 'connection', 'content', 'cookies', 'elapsed', 'encoding', 'headers', 'history', 'is_permanent_redirect', 'is_redirect', 'iter_content', 'iter_lines', 'json', 'links', 'ok', 'raise_for_status', 'raw', 'reason', 'request', 'status_code', 'text', 'url']

If you use dir() without passing any objects into it, it will give you all the objects in your current namespace:

>>> dir()
['_', '_1', '__builtins__', '__doc__', '__name__', '__package__', '__warningregistry__', 'api_ver', 'base_url', 'hn_object', 'item_num', 'r', 'requests']

type()

This is a function that its hard to get by without. Type returns the type name for any object you pass into it.

>>> type(r)
<class 'requests.models.Response'>

The inspect module

The inspect module is the King of Introspection in Python. To be honest, I haven't really used it that much, but I probably should start. Just browsing the documentation has shown me a few methods that I really need to remember for the future!

inspect.getdoc

This is an interesting method where you can get the docstring of any object you pass into it:

>>> inspect.getdoc(requests.post)
'Sends a POST request.\n\n:param url: URL for the new :class:`Request` object.\n:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.\n:param json: (optional) json data to send in the body of the :class:`Request`.\n:param \\*\\*kwargs: Optional arguments that ``request`` takes.\n:return: :class:`Response <Response>` object\n:rtype: requests.Response'

inspect.getsourcefile

This could definitely be handy for locating the source files for pure-python modules. Note that it will fail though if the object is a built-in object.

>>> inspect.getsourcefile(requests)
'd:\\hal9000\\hn_example\\venvpy3\\lib\\site-packages\\requests\\__init__.py' 

There are many other methods avaible in the inspect module so I suggest you browse it a bit to get an idea of what's available.

Hopefully, this post gave you some ideas on how helpful ptpython and executing files in the REPL can be. If you are looking for more of an actual command line debugger that you can step through line by line, then remember there is always PDB.

There are also some really great PDB replacement libraries though and I think next post I may go over one of the best I have found, pudb. It's a full-screen curses-based CLI GUI debugger for Python!


Comments

comments powered by Disqus