|
3
|
By storing an object's data in a dictionary, you can easily create a Filter class which tests a given object against a set of nested inclusion rules. This works particularly well for database filtering.
Implementation disclaimer: Obviously, some parts of both scripts are incomplete. I have a custom date class, for example, which I felt you could infer on your own. I also do more complex casting than quoted_field(). And my Objects and Filters (and many other parts) are more complex and integrated into my implementations. Since those bits are extraneous to the core concept, I left them as exercises for the implementer. Discussion by example: I'm going to discuss the "Pure object filter" example only, since the ADO example is merely a similar implementation of the same idea. The basic idea is that you have two items in your possession:
anObject = MyObject() anObject.data = {'ID': 30124, 'Name': 'GvR', 'Number of Contributions': 82739}
aFilter = MyFilter() aFilter.data = [{'Name': 'GvR'}, {'Number of Contributions': ('__ge__', 1000)}] This simple Filter will allow us to take our example Object, or a series of such Objects, and test them. In plain language, we might be evaluating a list of contributors, and want to produce a subset of that list: people with 1000 or more contributions. Just in case GvR is having a slow period, we include him in the list regardless of his number of contributions. I could probably take several pages to describe all the possibilities, but I'll simply walk through this example Filter, and show you how parse_branch() does its thing. We begin by passing our Filter's .data attribute to parse_branch: aResult = parse_branch('', aFilter.data, anObject) At the topmost level, we are asking parse_branch to take anObject and analyze it against our Filter and its ruleset. It does this by recursively unpacking sequences in our Filter in particular ways. The basic rules are:
We have all four in our example (all right, our dictionary is something of a degenerate case, but it's there nonetheless). Let's quickly walk through them. The first container is a list. This gets separated into its two items, and each is evaluated in order. When each has been evaluated (which may involve a recursive call to parse_branch), their True/False values will be or'ed together. The first item in that outermost list is a dictionary. If it had multiple items, they would be and'ed together; our example only has one item. Therefore, it merely checks to see if anObject.data['Name'] equals 'GvR'. If it does, then our first list item is True, and the second item will not be evaluated, and anObject "passes" the Filter. If the 'Name' is not 'GvR', the second item is evaluated. The second item is also a dictionary; however, its 'value' half isn't a simple string: it's a 2-tuple. This signals our parser to evaluate the expression: anObject.data['Number of Contributions'].__ge__(1000). Just in case our .data value is an object that doesn't support __ge__ (in Python 2.2, this includes many basic types like int), we provide a lambda function to perform the operation using the built-in behavior for that type; mostly __cmp__. That's all there is to it! Our example might be written in straight Python code as: anObject.data['Name'] == 'GvR' or anObject.data['Number of Contributions'] >= 1000 But of course, this isn't nearly as much fun as abstracting the process. ;) The same example, run through the ADO version, does not test for truth value on its own. Instead, it produces valid SQL for ADO database connections. Our example Filter would produce something like: "([Name] = 'GvR' or [Number of Contributions] >= 1000)" which could then easily be inserted into a WHERE clause. I currently use this very technique in an object server design. Analysis: The point of the entire exercise is this: the same Filter can be evaluated using different strategies in different contexts. In the first example, we compared an Object directly to the filter. In the second example, we applied the same filter to a database query. Abstracting the logic in this way allows us to write Filter logic once, and use it in a variety of situations. It also allows us the usual benefits of abstraction: dynamic instantiation, code isolation, consistent expression, easier state management (you could pickle or otherwise store the Filters), and maintainability. Python, with its built-in sequences, gives us a simple, powerful way to express complex logic without introducing (too much) unfamiliar syntax.
Tags: algorithms
|
Add a comment
Sign in to comment

Download
Copy to clipboard
