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

Cod*_*ahk 9 python python-3.x

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

results = db.search("book")
          .author("J. K. Rowling")
          .price("<40.00")
          .title("Harry")
          .execute()
Run Code Online (Sandbox Code Playgroud)

但是当我尝试将所需方法动态添加到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
Run Code Online (Sandbox Code Playgroud)

但是,当我运行例如:

print(db.search("book").price(">4.00").execute())
Run Code Online (Sandbox Code Playgroud)

输出:

{'__Set__': 'harry'}
Run Code Online (Sandbox Code Playgroud)

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

Eri*_*got 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'}
Run Code Online (Sandbox Code Playgroud)

PS:这个类有一个__init__()没有family参数的方法(条件设置器在运行时动态添加,但是被添加到类中,而不是单独添加到每个实例).如果需要创建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']
Run Code Online (Sandbox Code Playgroud)

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


如果您确实需要了解存储它们的属性名称的搜索方法,您只需将其设置make_set_condition()

       set_cond.__name__ = option  # Sets the function name
Run Code Online (Sandbox Code Playgroud)

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

>>> Search.price
<function set_cond at 0x107f832f8>
Run Code Online (Sandbox Code Playgroud)

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

>>> Search.price
<function price at 0x107f83490>
Run Code Online (Sandbox Code Playgroud)

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


Ray*_*ger 5

下面是一些可以帮助您入门的工作代码(不是您尝试编写的整个程序,而是显示各个部分如何组合在一起的代码):

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())
Run Code Online (Sandbox Code Playgroud)


Len*_*bro 5

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

其次,您不需要访问dict。该self.__dict__[opt] = self.__Set__是做得更好setattr(self, opt, self.__Set__)

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

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

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