Ries
Ries

Reputation: 2866

Recursive class definitions in Python

I am experimenting with fluent interfaces in Python.

An example of a fluent sql query generator would look something like this in usage:

sql.select('foo').select('bar').from('sometable').tostring() 

I quickly realized that the ability to define nested classes recursively would probably help.

class sql:
    class select:        
        class select   # <--  HERE
        def __init__(self, dbcolumn, astype=None, asname=None):
            self.dbcolumn = dbcolumn
            self.astype = astype
            self.asname = asname

In the line marked with the comment '# <-- HERE':
I want this nested class reference to refer to the same 'select' class definition of the containing class.

Is this possible somehow? Maybe using some keyword that I am not aware of?

Upvotes: 4

Views: 6266

Answers (3)

Ries
Ries

Reputation: 2866

As the title of the question indicated, I really wanted to know how to recursively nest a class definition. I am sure there are many ways to 'skin the fluent-interface-in-python cat'.

I found a way to recursively nest a class definition using decorators:

# class decorator
def recursiveclass(decorated_class):
    decorated_class.__dict__[decorated_class.__name__] = decorated_class
    return decorated_class     

#fluent interface
class sql:
    @recursiveclass
    class select:        
        def __init__(self, dbcolumn, astype=None, asname=None):
            self.dbcolumn = dbcolumn
            self.astype = astype
            self.asname = asname

Please note that I am not saying this is the best way to implement a fluent interface in Python. I am merely experimenting.

To verify:

dir(sql.select.select.select.select)
['__doc__', '__init__', '__module__', 'select']

Upvotes: 0

Martijn Pieters
Martijn Pieters

Reputation: 1124238

You are confusing classes with methods.

select is a method on an instance of class sql, not a nested class. It could return another instance.

class SelectStatement(object):
    def select(self, tablename):
        return SelectStatement()

class SQL(object):
    def select(self, tablename):
        return SelectStatement()

Take a look at the source code of SQLAlchemy; it does exactly that; generate SQL from a structure python classes, where you can refine a query by calling methods on instances in just such a manner. A series of joins for example, can be chained:

q = session.query(User).join(User.orders).join(Order.items).join(Item.keywords)

Upvotes: 0

ThiefMaster
ThiefMaster

Reputation: 318688

There is no need for "recursive class definitions". All you need to do to allow chaining is to return self in your methods (or a instance of the same class if your objects are not mutable or some methods shouldn't modify the input object).

Example:

>>> class SQL(object):
...     def __init__(self):
...         self.columns = []
...     def select(self, col):
...         self.columns.append(col)
...         return self
...
>>> s = SQL()
>>> s.select('foo').select('bar').columns
['foo', 'bar']

Upvotes: 7

Related Questions