在Python 3.0中动态地向类添加方法

时间:2011-11-26 06:26:26

标签: python-3.x python

我正在尝试在Python中编写数据库抽象层,它允许您使用链式函数调用来构造SQL语句,例如:

results = db.search("book")
          .author("J. K. Rowling")
          .price("<40.00")
          .title("Harry")
          .execute()

但是当我尝试将所需方法动态添加到db类时,我遇到了问题。

以下是我的代码的重要部分:

import inspect

def myName():
    return inspect.stack()[1][3]

class Search():

    def __init__(self, family):
        self.family = family
        self.options = ['price', 'name', 'author', 'genre']
        #self.options is generated based on family, but this is an example
        for opt in self.options:
            self.__dict__[opt] = self.__Set__
        self.conditions = {}

    def __Set__(self, value):
        self.conditions[myName()] = value
        return self

    def execute(self):
        return self.conditions

但是,当我运行例如:

print(db.search("book").price(">4.00").execute())

输出:

{'__Set__': 'harry'}

我是以错误的方式来做这件事的吗?有没有更好的方法来获取被调用函数的名称或以某种方式制作函数的“硬拷贝”?

3 个答案:

答案 0 :(得分:9)

您可以在创建课程后添加搜索功能(方法)

class Search:  # The class does not include the search methods, at first
    def __init__(self):
        self.conditions = {}

def make_set_condition(option):  # Factory function that generates a "condition setter" for "option"
    def set_cond(self, value):
        self.conditions[option] = value
        return self
    return set_cond

for option in ('price', 'name'):  # The class is extended with additional condition setters
    setattr(Search, option, make_set_condition(option))

Search().name("Nice name").price('$3').conditions  # Example
{'price': '$3', 'name': 'Nice name'}

PS :此类有__init__()方法,没有family参数(条件设置器在运行时动态添加,但会添加到类中,不要分别对每个实例)。如果需要创建具有不同条件设置器的<{1}} 对象,则上述方法的以下变体有效(Search方法具有__init__()参数):

family

参考:http://docs.python.org/howto/descriptor.html#functions-and-methods


如果您确实需要了解其存储属性名称的搜索方法,您可以使用

import types class Search: # The class does not include the search methods, at first def __init__(self, family): self.conditions = {} for option in family: # The class is extended with additional condition setters # The new 'option' attributes must be methods, not regular functions: setattr(self, option, types.MethodType(make_set_condition(option), self)) def make_set_condition(option): # Factory function that generates a "condition setter" for "option" def set_cond(self, value): self.conditions[option] = value return self return set_cond >>> o0 = Search(('price', 'name')) # Example >>> o0.name("Nice name").price('$3').conditions {'price': '$3', 'name': 'Nice name'} >>> dir(o0) # Each Search object has its own condition setters (here: name and price) ['__doc__', '__init__', '__module__', 'conditions', 'name', 'price'] >>> o1 = Search(('director', 'style')) >>> o1.director("Louis L").conditions # New method name {'director': 'Louis L'} >>> dir(o1) # Each Search object has its own condition setters (here: director and style) ['__doc__', '__init__', '__module__', 'conditions', 'director', 'style'] 中进行设置。
make_set_condition()

(就在 set_cond.__name__ = option # Sets the function name 之前)。在执行此操作之前,方法return set_cond具有以下名称:

Search.name

设置其>>> Search.price <function set_cond at 0x107f832f8> 属性后,您会得到一个不同的名称:

__name__

以这种方式设置方法名称使得涉及该方法的错误消息更容易理解。

答案 1 :(得分:4)

首先,您没有向类中添加任何内容,而是将其添加到实例中。

其次,您无需访问 dict 。使用self.__dict__[opt] = self.__Set__可以更好地完成setattr(self, opt, self.__Set__)

第三,不要使用__xxx__作为属性名称。这些保留用于Python内部使用。

第四,正如您所注意到的,Python不容易被愚弄。您调用的方法的内部名称仍为__Set__,即使您使用其他名称访问它。 :-)当您将方法定义为def语句的一部分时,将设置名称。

您可能希望使用元类创建和设置选项方法。您也可能希望实际创建这些方法,而不是尝试为所有方法使用一种方法。如果你真的只想使用一个__getattr__的方式,但它可能有点繁琐,我通常建议反对它。 Lambda或其他动态生成的方法可能更好。

答案 2 :(得分:3)

这里有一些工作代码可以帮助您入门(不是您尝试编写的整个程序,而是显示部件如何组合在一起的东西):

class Assign:

    def __init__(self, searchobj, key):
        self.searchobj = searchobj
        self.key = key

    def __call__(self, value):
        self.searchobj.conditions[self.key] = value
        return self.searchobj

class Book():

    def __init__(self, family):
        self.family = family
        self.options = ['price', 'name', 'author', 'genre']
        self.conditions = {}

    def __getattr__(self, key):
        if key in self.options:
            return Assign(self, key)
        raise RuntimeError('There is no option for: %s' % key)

    def execute(self):
        # XXX do something with the conditions.
        return self.conditions

b = Book('book')
print(b.price(">4.00").author('J. K. Rowling').execute())