Debugging workflow

A friend of mine, who used Java in his day job, sent me a code snippet that he needed help with. He was new to Python and was still getting used to the differences between Java and Python. He mostly worked with databases, so he was exploring the different ORMs that were available. He tried SQLAlchemy, the Django ORM, and eventually, found that he liked a still very new ORM called Pony. At that time, the documentation for Pony was still in its infancy, so he sent me the code so that I could help him out. It looked similar to this:

__author__ = "John Doe"

import string
import random

from pony.orm import db_session, commit, Database, Required

db = Database()


class Person(db.Entity):
    name = Required(str)
    age = Required(int)


vowels = 'aeiou'
consonants = str(letter for letter in string.ascii_lowercase if letter not in vowels)


def gen():
    return random.choice(consonants) + random.choice(vowels)


names = [(gen() for _ in range(random.randint(3, 4))) for i in range(100)]

db.bind('sqlite', 'data.sqlite', create_db=True)
db.generate_mapping(create_tables=True)

with db_session:
    for name in names:
        Person(name=name, age=random.randint(5, 21))

commit()

I ran this fine in debug mode, and got an exception in the console, saying that it was a MappingError, so I looked at the file and found out that the script did not bind to a database before generating mappings, so that was easy enough to fix— it needed to bind before generating mappings. But then came another problem, and that was TypeError:

Debugging workflow

So, it's saying that we have TypeError, and that it was expecting Unicode, but for what? This is where frames come in handy. Frames, located on the left-hand side of the debug menu, are like layers in an application. Frames showcase this callback sequence (or a function call stack), letting you jump between the files where the problem was caused.

Debugging workflow

In the preceding screenshot, you can see that the topmost item in the frame list is where the exception was raised; the list item shows you the function call, as well as the file in which the function exists. You can also see that the library files are highlighted in a dark yellow color (indicated by the red arrow), whereas your files are clear (indicated by the orange arrow). By using this panel, you can go back and forth in the sequence and see what went wrong. In this case, let's go back to our own file and see what the problem is:

Debugging workflow

So, it seems that the problem here arises when we try to initialize a Person object. Now, let's go back to the original exception, which said that it was a TypeError and it expected a Unicode object. Let's use our frames to dive into where the object was initiated:

Debugging workflow

We can see here that name is a generator, which feels wrong to me, because it should be a string or perhaps even Unicode. Let's go back to our original file and see what this is all about and also see what name is all about:

Debugging workflow

So, if we hover over name, we can see that it's a generator object, but that doesn't sound right. I mean, a person's name isn't supposed to be a generator. Name comes from names, and let's take a look at what names is made up of:

Debugging workflow

You can use the Evaluate Expression… button to check what an expression is; this button even has autocompletion. Right now, you can use it to evaluate simple expressions such as checking out the gen function. You can even select an expression in Python, right-click on the expression and then choose Evaluate Expression...:

Debugging workflow

You could also choose to evaluate this inside the console:

Debugging workflow

It turns out that these objects are all generators, and we need to fix that by getting rid of the braces around the expression to be added:

[gen() for _ in range(random.randint(3, 4)) for i in range(100)]

But, if we were to evaluate this, it would look rather weird:

Debugging workflow

First, we're trying to generate names; names are usually not two letters long. Second, when was the last time you saw a name with a greater-than sign in it? Something fishy is going on here. So, the gen function is giving us all of this data, and hence, there has to be something wrong with the gen function. Let's take a look at the gen function then. It turns out that gen concatenates a consonant and a vowel to form a pronounceable syllable.

Taking a look at gen, we find that it makes function calls to random.choice, so I doubt there's a problem in the standard library. It uses two variables, vowels and consonants, and that means there's a problem in both or either of these two. The vowels variable is pretty simple, so there isn't much room for error. However, we're probably messing something up when we're generating consonants. Let's set a line breakpoint just before the declaration of the gen function and take a look at what the consonants variable is made up of.

Debugging workflow

Ha! It looks like we've got a stringified (is that even a word?) generator. We need to use the string's .join function to get the job done. Let's do that now and change the line to this:

consonants = "".join(letter for letter in string.ascii_lowercase if letter not in vowels)

Let's see if that works now and take a look at what the names look similar to:

Debugging workflow

It seems like we're having a little bit of trouble here. I've heard of a nickname called zu, but I think the names are too small, so let's make them bigger:

names = ["".join(gen() for _ in range(random.randint(3, 4))) for i in range(100)]
Debugging workflow

After making the change and rerunning the program, we get mobuwa and yemuyo as names. This is pretty neat! I might just use these names as example names; they are way better than John Doe.

After that final change, the script ran smoothly and the right data was inserted into the database. I was able to send this back to my friend, and told him where he went wrong—mostly with the string concatenation using generators.

Before we depart this section, I'd like to tell you that Evaluate Expression… and the Console work inside frames. This means that if you're trying to evaluate an expression in a different frame that you have currently selected in the debugger, the debugger will give you an error.

Finally, PyCharm also supports watches and the usual step-into/step-out procedures that are conventional in debugging.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.120.109