向函数返回的类添加方法

Posted

技术标签:

【中文标题】向函数返回的类添加方法【英文标题】:Add method to class returned by function 【发布时间】:2012-10-11 20:16:48 【问题描述】:

免责声明:这个问题与家庭作业有关。我并不期待一个解决方案,但我希望能更好地理解所要求的内容以及这个家庭作业的(可能的)效用。

这个问题是在关于使用 Python 的 mysqldb 模块以及如何更好地表示结果集(可能还有它的行)而不是元组的元组(MySQLdb 的默认设置)的课程之后提出的。

问题是:修改classFactory.py源码,让build_row函数返回的DataRow类多了一个方法:

retrieve(self, curs, condition=None)

self 是(像往常一样)正在调用其方法的实例,curs 是现有数据库连接上的数据库游标,条件(如果存在)是条件字符串,所有接收的行都必须为真. 检索方法应该是一个生成器,产生结果集的连续行,直到它完全耗尽。每一行都应该是一个 DataRow 类型的新对象。

def build_row(table, cols):
    """Build a class that creates instances of specific rows"""
    class DataRow:
        """Generic data row class, specialized by surrounding function"""
        def __init__(self, data):
            """Uses data and column names to inject attributes"""
            assert len(data)==len(self.cols)
            for colname, dat in zip(self.cols, data):
                setattr(self, colname, dat)
        def __repr__(self):
            return "0_record(1)".format(self.table, ", ".join(["0!r".format(getattr(self, c)) for c in self.cols]))
    DataRow.table = table
    DataRow.cols = cols.split()
    return DataRow
    在函数定义中创建类的目的是什么?这通常由专业开发人员完成吗?如果有,有什么好处? 为什么数据库游标是retrieve 方法的(显式)参数?数据库游标不应该在这个脚本的范围之外被引用吗(当建立数据库连接时)? 是否正确地说(当前)build_row 函数将接受两个参数 - tablecols - 并返回一个可能看起来像(例如)“exampleTable_record(fieldA, fieldB,字段C)"? 成功添加retrieve 方法后,我应该期待build_row 现在返回什么?

正如我之前提到的,如果我知道为什么要求我添加 retrieve 方法,那么我可能会更好地了解如何解决这个问题。

【问题讨论】:

函数中是否缺少缩进级别? 这种类型的事情在Python中相当普遍。在这种情况下,我会担心名为“table”或“cols”或“retrieve”的列会覆盖属性,所以我认为这不是应该做什么的好例子。然而,专业开发人员确实经常编写可利用的漏洞代码:) 【参考方案1】:
    这被称为工厂设计模式......虽然在这种情况下它只是为了使类的实例化被强制到包含该类的方法......这迫使您设置表和列名属性在实例化时...如果在您尝试调用检索时未正确实例化,可能会导致错误... 我为了查询数据库需要一个游标对象 是的,但它返回并且 DataRow 类不是字符串...这具有字符串表示形式 具有数据的数据行 ...

这是一个讨论工厂设计模式的链接 Why do we need Abstract factory design pattern?

一个示例检索函数

@classmethod #if this is a classmethod
def retrieve(self, curs, condition=None):
        if condition:
           curs.execute("SELECT * FROM 0 WHERE 1".format(self.table,condition)
        else:
           curs.execute("SELECT * FROM 0".format(self.table)
        for row in curs.fetchall():
            yield DataRow(row)

然后你会做类似的事情

my_users = build_row("users",["username","password","avatar"])
my_users = my_users()  #if you need to operate on an instance.... but class method would be better
#users is the name of a table already in our database, whose columns are username,password, and avatar
db=sqlite3.connect("some_database.sql")
for user in my_users.retrieve(db.cursor(),"username='bob'"):
    print user #something like users_record('username':'bob','password':'123%gw','avatar':None)
    print user.username #bob
    print user.avatar #None
    print user.password #whatever
    print user.blah #Error!!!!

【讨论】:

(1) 好的,这是一种常见的做法;使用工厂设计模式有什么优势? (2) 为什么需要在这个函数中调用数据库游标? (4) 能否提供一个示例(完整的虚拟数据),说明retrieve 方法成功添加到类后会返回什么? 您正在使用retrieve,就好像它是一个类方法一样。那是合适的,但问题和答案都暗示了一个实例方法。 fixed ... sorta ...但它可能应该是一个类方法【参考方案2】:

2) 为什么数据库游标是检索方法的(显式)参数?数据库游标不应该在这个脚本的范围之外被引用吗(当建立数据库连接时)?

封装。依赖全局魔法变量会产生应尽可能避免的依赖关系。考虑您的应用程序可能同时有多个处于活动状态的游标,或者甚至可能通过单独的连接与两个数据库通信。

传入参数还可以让你的代码更容易测试:你可以给它传递一个模拟类或类似的东西,而不必修改全局变量。

【讨论】:

【参考方案3】:

您所拥有的是一个可以专业使用的示例。函数build_row 是一个类工厂,它返回一个适合处理特定表和列的数据的类。

虽然我个人不会在这种特定情况下使用工厂模式,但它确实有效。

所以为了解释这段代码在做什么,它基本上创建了一个使用函数调用中的表和列的类。如果您注意到代码中有self.colsself.table,但它们实际上并不是DataRow 的一部分,那么它们会在之后附加到它,这显示了以这种方式创建类的灵活性。

build_row 函数实际上返回的是一个类!这个类应该用于从数据库中获取数据......这是您必须为您的作业构建的部分。您需要在您的选择语句中使用table(表名),您还需要cols ...本质上是您的函数(我不会为您编写)应该执行的操作:

SELECT <PUT THE `cols` HERE> FROM `table`;

并返回结果。这应该足以让您继续前进。

【讨论】:

以上是关于向函数返回的类添加方法的主要内容,如果未能解决你的问题,请参考以下文章

如何向 SKAction 序列添加返回 void 的函数?

是否可以在视图django中添加方法,用于在httpresponse中返回方法返回字符串的类视图中[关闭]

jQuery 属性操作方法

为啥我的函数向返回未定义的对象数组添加键和增量值

从数组向 UITable 添加行

向接受任何可迭代并返回压缩值的函数添加类型提示