tail -f findings.out

A better way to search for methods of Python objects

Python’s introspection abilities are quite extensive and useful. They are also well-documented, so I won’t go into the basics here. Check out this article if you need a good overview. N.B.: discussion and code below applies to both methods and attributes. I will simply refer to “methods” for simplicity.

Beyond simply listing the methods of an object, however, I often find that I want to search through them for something in particular. And eyeballing the output of dir(obj) is only efficient in the simplest of cases. hasattr(obj, “method”) won’t get you far either, as you need to match the “method” name exactly. What if you just have a good guess about the name of a method based on what you need to do? What if you want to know everything you can do with directories in the os module or ISO related methods in datetime.date? I haven’t found anything to help with this sort of problem yet. Approaches like writing a loop to do the search every time or perusing the pertinent API docs are too circuitous for such quick questions. So let’s make a tool to do this more easily!

We start with knowing some object and some string containing all or part of the method(s) we’re interested in:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def mf(obj, term):
    """
    Searches through the methods and attributes defined for obj,
    looks for those containing the term passed.
    Returns all matches or a warning if none found.
    """

    meths = dir(obj)
    match_meths = []
    for meth in meths:
        if meth.rfind(term) != -1:
            match_meths.append(meth)
    if match_meths:
        print match_meths
    else:
        print "No matches!"

This simply iterates over the methods and attributes defined for the object passed and looks for the term passed within the name of each. rfind returns the highest index of the substring passed in the string it’s called on and returns -1 if no match is found. Once the matches are collected they are printed out. An example:

1
2
3
4
>>>from method_finder import mf
>>>import os
>>>mf(os, "dir")
['chdir', 'curdir', 'fchdir', 'listdir', 'makedirs', 'mkdir', 'pardir', 'removedirs', 'rmdir']

For convenience let’s make it available on the Python interactive prompt at every load without any extra effort. This is easy enough using a .pythonstartup file. This file can be used to load various useful items like tab completion for interactive Python sessions. If you haven’t used it before, you’ll need to add the following to your .bashrc:

1
export PYTHONSTARTUP="$HOME/.pythonstartup"

Then in your home directory create a .pythonstartup file containing something like the following:

1
2
3
4
5
6
7
8
9
10
11
import os
# Adds mf(obj, "str") allowing search for methods matching 'str' on obj
# as well as obinfo(obj) returning lots of info on obj
util_loc = "/home/shuckins/code/code_homerepo/python-programming/utilities"
if os.path.isdir(util_loc):
    import sys
    sys.path.append(util_loc)
    from utils import mf, obinfo
    sys.path.remove(util_loc)
    del sys
del os

You’ll need to change the path specified to wherever you placed utils.py containing the mf() function of course. You can download mine here if you’d like. The del statements are to clean up any trace of this operation once you get to the interactive interpreter. This way you get the added functions without mucking up your namespace.

This also loads in an obinfo(obj) function that I included in the same utils.py file. This is based on the interrogate() function written by Patrick O’Brien in the introspection article mentioned above. My version just adds a check for objects without docstrings and prints more of the docstring:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def obinfo(obj):
    """
    Print useful information about object.

    From http://www.ibm.com/developerworks/library/l-pyint.html
    """

    if hasattr(obj, '__name__'):
        print "NAME:    ", obj.__name__
    if hasattr(obj, '__class__'):
        print "CLASS:   ", obj.__class__.__name__
    print "ID:      ", id(obj)
    print "TYPE:    ", type(obj)
    print "VALUE:   ", repr(obj)
    print "CALLABLE:",
    if callable(obj):
        print "Yes"
    else:
        print "No"
    if hasattr(obj, '__doc__'):
        doc = getattr(obj, '__doc__')
        doc = doc.strip()
        topfive = doc.split('\n')[0:4]
        print "DOC:     ", "\n".join(topfive)
    else:
        print "No docstring. Yell at the author."

It’s fairly useful:

1
2
3
4
5
6
7
8
>>> obinfo(["some", "list"].count)
NAME:     count
CLASS:    builtin_function_or_method
ID:       139922087930696
TYPE:     <type 'builtin_function_or_method'>
VALUE:    <built-in method count of list object at 0x7f422658ef80>
CALLABLE: Yes
DOC:      L.count(value) -> integer -- return number of occurrences of value
Tags: , ,
November 8, 2009 - 5:08 PM Comments (4)

Catching warnings from the MySQLdb module

The MySQLdb Python module implements the Python DB API for MySQL. I’ve written about its use before. MySQL issues warning messages in a number of circumstances and PEP 249 (which specifies the Python DB API) describes a Warning error message to be included.

One issue I ran into recently was how to catch warnings thrown by this module when running queries. Oftentimes tutorials or forum discussions that cover warnings in the context of MySQLdb describe how to filter them (they can clog up script output). But in a recent case I wanted to grab and check the warning, logging a dependent result. I had hoped this clean implementation would work in a method used for all calls to the MySQL DB:

1
2
3
4
5
6
7
8
9
10
11
try:
    cursor.execute(query)
except MySQLdb.Error, e:
    raise e
except MySQLdb.Warning, e:
    raise e
finally:
    data = cursor.fetchall()
    rows_returned = cursor.rowcount
    cursor.close()
    db.close()

But the warnings just went right through. Instead I needed the warnings module’s assistance:

1
2
3
4
5
6
7
8
9
10
11
12
import warnings
with warnings.catch_warnings():
    warnings.simplefilter('error', MySQLdb.Warning)
    try:
        cursor.execute(query)
    except MySQLdb.Error, e:
        raise e
    finally:
        data = cursor.fetchall()
        rows_returned = cursor.rowcount
        cursor.close()
        db.close()

This catches the warnings and raises them as errors, although their class is still correct, allowing a clean implementation to call the above code (wrapped into a method called do_query):

1
2
3
4
5
try:
    self.do_query(make_cool_table)
    logger.info("Created cool_table table.")
except MySQLdb.Warning:
    logger.info("cool_table already exists.")
Tags: , ,
October 25, 2009 - 10:53 PM No Comments

Checking options with optparse callbacks

optparse is a flexible and powerful module for processing command line options in Python programs. I’ve used it for a while, but I didn’t have much occasion to get into callbacks until recently. A callback here is a user-defined function optparse calls when its associated option is encountered. They can help you keep your script’s logic clear within a forest of options by providing arbitrary checks when particular options are passed.

When I first began using them, I ran into a critical point of confusion that wasn’t clearly illuminated in the assorted guides I found for optparse. In response, here is a short overview of how to use callbacks to perform checks and validations against passed options.

First, a very basic script using optparse for option parsing:

1
2
3
4
5
6
7
import optparse
parser = optparse.OptionParser()
parser.add_option("-f", "--input-file", dest="inputfile",
                  help="The file with input")
(options, arguments) = parser.parse_args()

print options

Which outputs:

1
2
python test.py -f test.txt
{'inputfile': 'test.txt'}

For a more detailed coverage of the basics, review Doug Hellman’s PyMOTW article on optparse. It would be best to verify that this input file exists so that we might inform the user and exit early. Adding that functionality to our example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import optparse
import os

def check_file(option, opt, value, parser):
    if not os.path.exists(value):
        raise optparse.OptionValueError("%s doesn't seem to exist." % value)
    parser.values.inputfile = value

parser = optparse.OptionParser()
parser.add_option("-f", "--input-file", dest="inputfile",
                  action="callback", callback=check_file,
                  type="string", help="The file with input")
(options, arguments) = parser.parse_args()

print options

We’ve added a check_file() function to perform our check and also added the callback specification to the associated option. The arguments a callback function is passed are described in the module documentation. optparse.OptionValueError provides an elegant way to send an error message about the problem and quit the program in one step.

So what’s the problem? When I first wrote a callback, I didn’t catch the line in the tutorial examples that set the value of the option. I still don’t understand why this is necessary, but without the “parser.values.inputfile = value” line above, you’d find the inputfile variable to have None for its value. Strange, but true. I could understand the callback function having to return the option value if all was well, but why would the option’s value get unset in the first place?

Additionally, it turns out you have to set the option’s type even if the desired value is supposed to be default. In the above example, without ‘type=”string”‘, the callback function would have thrown an error, complaining that we were claiming to use a string and weren’t.

So watch out for these details, and clean up your scripts with callbacks.

Tags: , ,
August 16, 2009 - 12:44 AM No Comments

What are all the wxPython Events?

On several occasions recently I found did not know what event to specify to trigger something in my wxPython apps. I had a hard time finding anything close to a comprehensive list of all events available, so I was limited to posting questions on the wxPython users mailing list. And, while this is a wonderful and active list, it would be nice to have a guide even closer at hand.

Cody Precord on the list provided me with a handy way to get this:

1
2
3
4
5
import wx

for x in dir(wx):
  if x.startswith('EVT_'):
      print x

Run that, and out comes a list of all the EVT types. Of course, in some cases you may not know what the event is just from the name, but they are mostly self-explanatory. See for yourself:

1
2
3
4
5
6
7
EVT_ACTIVATE
EVT_ACTIVATE_APP
EVT_BUTTON
EVT_CALCULATE_LAYOUT
EVT_CHAR
EVT_CHAR_HOOK
EVT_CHECKBOX
Tags: , ,
October 1, 2007 - 11:44 PM Comments (3)

Twitter links powered by Tweet This v1.6.1, a WordPress plugin for Twitter.