簡體   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