Python中的递归类定义

4

我正在尝试使用Python的流畅接口。

一个流畅的SQL查询生成器的示例在使用时看起来像这样:

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

我很快意识到,递归定义嵌套类的能力可能会有所帮助。
class sql:
    class select:        
        class select   # <--  HERE
        def __init__(self, dbcolumn, astype=None, asname=None):
            self.dbcolumn = dbcolumn
            self.astype = astype
            self.asname = asname

在标有'# <-- HERE'注释的行中:
我希望这个嵌套类引用能够引用包含类的相同'select'类定义。
这种可能性是否存在?也许使用一些我不知道的关键字可以实现吗?

1
但是selectsql上的一个方法;它可以返回一个类,但它本身不是一个类。 - Martijn Pieters
6
那不是实现流畅接口的方法。 - Ignacio Vazquez-Abrams
@ChrisMorgan:不,你只需要避免嵌套类定义来实现流畅接口。 - Martijn Pieters
@Ries: 但这样做会让你失去父类的上下文。除了让 API 的用户感到困惑外,没有必要以那种方式嵌套类。它应该是一个方法;你的 sql.select 示例告诉我你希望它像方法一样工作。 - Martijn Pieters
@ChrisMorgan:正如SQLAlchemy所展示的那样,除了jQuery之外,Python中也有很好的链接用例可供实现。我不会说这是不符合Python风格的。 - Martijn Pieters
显示剩余4条评论
3个回答

7
不需要“递归类定义”。为了允许链接,你只需要在你的方法中返回self(如果你的对象是不可变的或某些方法不应该修改输入对象,则返回同一类的实例)。
例如:
>>> 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']

我对这个例子的(误解?)担忧是,添加一个SQL.insert函数会使得像这样的操作成为可能: s.select('foo').insert() 这在SQL流畅接口中是没有意义的。 - Ries
1
当调用select时,只需设置一个标志位,并在对具有设置了select标志的对象进行insert操作时引发异常。 - ThiefMaster
那听起来像是一个不必要的黑客行为。 - Ries
如果Python具有更好的代码补全兼容性,建议的标志也不会很友好。但这只是一个理论上的缺点。 - Ries

0
你把方法搞混了。 selectclass sql的一个实例上的方法,而不是嵌套类。它可以返回另一个实例。
class SelectStatement(object):
    def select(self, tablename):
        return SelectStatement()

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

看一下 SQLAlchemy的源代码;它恰好可以从Python类的结构生成SQL,您可以通过对实例调用方法来细化查询。例如,一系列连接可以链接起来:

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

这里没有混淆。也许我使用小写的类名让你感到困惑了? :) - Ries
@Ries:不,我非常确定我没有被搞混了。 - Martijn Pieters
无论如何,感谢您提供 SQLAlchemy 的链接。 - Ries
@Ries:您想根据另一个实例的上下文返回类实例。您可以通过方法来实现这一点;如果您只是实例化了一个嵌套类,它将不会继承该上下文。 - Martijn Pieters
请参见 https://gist.github.com/3175416 了解我如何传递上下文。但是,我同意它使用了实例化相同类的方法。但是我仍然喜欢嵌套类结构。 - Ries
@Ries:那么为什么要使用父级sql类呢?为什么不只使用一个模块呢?在那里使用sql类没有任何实际理由。 - Martijn Pieters

0
正如问题标题所示,我真的想知道如何递归嵌套类定义。我相信有很多方法可以“解决Python中流畅接口的问题”。
我发现使用装饰器可以递归嵌套类定义:
# 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

请注意,我并不是在说这是在Python中实现流畅接口的最佳方法。我只是在进行实验。
验证方式如下:
dir(sql.select.select.select.select)
['__doc__', '__init__', '__module__', 'select']

1
上面的示例确实展示了如何递归嵌套类,但我开始怀疑这样做的有用性,因为子类无法访问其父类的属性。也许这对于定义流畅接口更有意义:https://gist.github.com/3175416 - Ries

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接