繁体   English   中英

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

[英]Dynamically add methods to a class in Python 3.0

我正在尝试用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'}

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

您可以在创建类之后添加搜索功能(方法):

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 :这个类有一个没有family参数的__init__()方法(条件设置器在运行时动态添加,但是被添加到类中,而不是单独添加到每个实例)。 如果需要创建具有不同条件设置器的 Search 对象 ,则上述方法的以下变体可用( __init__()方法具有family参数):

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']

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


如果你真的需要知道它们存储的属性名称的搜索方法,你可以简单地在make_set_condition()设置它。

       set_cond.__name__ = option  # Sets the function name

(就在return set_cond之前)。 在执行此操作之前,方法Search.name具有以下名称:

>>> Search.price
<function set_cond at 0x107f832f8>

设置其__name__属性后,您将获得一个不同的名称:

>>> Search.price
<function price at 0x107f83490>

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

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

其次,您不需要访问dict self.__dict__[opt] = self.__Set__最好用setattr(self, opt, self.__Set__)

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

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

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

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

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())

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM