Bezdepbonusu7Программирование: статьи и ответы

Вопрос

Динамическое добавление методов в класс в Python 3.0

Автор вопроса: CodahkОтветов: 3

Я пытаюсь написать уровень абстракции базы данных в 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'}

Я иду об этом неправильно? Есть ли лучший способ получить имя вызываемой функции или как-то сделать "печатную копию" функции?

  • Могу я спросить, почему вы пытаетесь заново изобрести SQLAlchemy? Lennart Regebro 26.11.2011
  • На самом деле я довольно часто пытаюсь написать свои собственные библиотеки, которые воспроизводят уже существующие, чтобы я мог больше узнать о языке и лучше понять, как объединяются более продвинутые части :) Codahk 26.11.2011
  • Хорошо, обучающее упражнение, это хорошая причина. Хотя в этом случае я думаю, что чтение исходного кода SQLAlchemy будет хорошим началом. ORM сложны и волшебны. Lennart Regebro 26.11.2011

python

Ответы

Ответ 1 · рейтинг 11 · Eric O Lebigot

Вы можете просто добавить функции поиска (методы) после создания класса:

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 (установщики условий добавляются динамически во время выполнения, но добавляются в класс, а не в каждый экземпляр отдельно). Если необходимо создать 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>

Установка имени метода таким образом облегчает понимание возможных сообщений об ошибках, связанных с этим методом.

Ответ 2 · рейтинг 5 · Lennart Regebro

Во-первых, вы ничего не добавляете к классу, вы добавляете его к экземпляру.

Во-вторых, вам не нужен доступ к dict. self.__dict__[opt] = self.__Set__ лучше делать с setattr(self, opt, self.__Set__).

В-третьих, не используйте __xxx__ в качестве имен атрибутов. Они зарезервированы для внутреннего использования Python.

В-четвертых, как вы заметили, Python нелегко обмануть. Внутреннее имя метода, который вы вызываете, по-прежнему __Set__, хотя вы обращаетесь к нему под другим именем. :-) Имя задается, когда вы определяете метод как часть оператора def.

Вероятно, вы захотите создать и установить методы параметров с помощью метакласса. Вы также можете создать эти методы вместо того, чтобы пытаться использовать один метод для всех из них. Если вы действительно хотите использовать только один __getattr__, это способ, но это может быть немного неудобно, я обычно не рекомендую его. Лямбды или другие динамически генерируемые методы, вероятно, лучше.

Ответ 3 · рейтинг 5 · Raymond Hettinger

Вот некоторый рабочий код для начала (не вся программа, которую вы пытались написать, а то, что показывает, как части могут сочетаться друг с другом):

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

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы