Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically add methods to a class in Python 3.0

I'm trying to write a Database Abstraction Layer in Python which lets you construct SQL statments using chained function calls such as:

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

but I am running into problems when I try to dynamically add the required methods to the db class.

Here is the important parts of my code:

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

However, when I run the example such as:

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

outputs:

{'__Set__': 'harry'}

Am I going about this the wrong way? Is there a better way to get the name of the function being called or to somehow make a 'hard copy' of the function?

like image 757
Codahk Avatar asked Nov 26 '11 06:11

Codahk


People also ask

How do you create a class object dynamically in Python?

Python Code can be dynamically imported and classes can be dynamically created at run-time. Classes can be dynamically created using the type() function in Python. The type() function is used to return the type of the object. The above syntax returns the type of object.

What is __ add __ in Python?

The __add__() method in Python specifies what happens when you call + on two objects. When you call obj1 + obj2, you are essentially calling obj1. __add__(obj2).


1 Answers

You can simply add the search functions (methods) after the class is created:

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: This class has an __init__() method that does not have the family parameter (the condition setters are dynamically added at runtime, but are added to the class, not to each instance separately). If Search objects with different condition setters need to be created, then the following variation on the above method works (the __init__() method has a family parameter):

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

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


If you really need search methods that know about the name of the attribute they are stored in, you can simply set it in make_set_condition() with

       set_cond.__name__ = option  # Sets the function name

(just before the return set_cond). Before doing this, method Search.name has the following name:

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

after setting its __name__ attribute, you get a different name:

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

Setting the method name this way makes possible error messages involving the method easier to understand.

like image 108
Eric O Lebigot Avatar answered Sep 28 '22 17:09

Eric O Lebigot