3

I'm trying to create methods whose names are based on a class' instance's specific attribute. My code is similar to this:

class myClass:
    info_to_be_accessed = {
        'infoA': 14,
        'infoB': 21,
        'infoC': False,
        'infoD': 'spam'
    }

    def create_methods(self):
        for info in self.info_to_be_accessed:
            setattr(self, info, lambda self: self.info_to_be_accessed.get(info, None))
        return self

c = myClass().create_methods()
print(c.infoA())

I was expecting c.infoA() to be an instance method, but it turns out as a static method that does not get the self parameter when called. Output raises a TypeError:

Traceback (most recent call last):
  File "test.py", line 15, in <module>
    print(c.infoA())
TypeError: <lambda>() missing 1 required positional argument: 'self'

Expected output:

14

EDIT: Answering @chepner. I'm honestly not sure this is the best way to deal with it, but my basic problem is that I am dealing with data that is outside my database / my own code. I'm retrieving information from a database I have no control of, so it can be analysed by my code. Basically, my real-code class has a pandas dataframe with information about the result of a complex SQL query and the ability to plot a chart about it, but sometimes I want to filter the dataframe in order to plot it, and sometimes I want to plot the whole thing. I'm not really analyzing a dictionary, but instead creating methods to filter this dataframe. Thing is, the (external) object I'm analyzing with this query has fixed values a certain attribute can be, but if this external database changes in the future, I don't want to create extra code just to cover the new value that is added there. Using methods instead of parameters is just more convenient to me when analyzing this data, but this is not a code that goes to production, merely something I use at the CLI. This is why it's convenient to me to have separate methods for each value that can be passed.

10
  • 2
    Why are you doing this? What does this solve that simply defining the methods at class creation time doesn't? The methods provided by a class should be the same for all instances. Commented Dec 20, 2018 at 19:29
  • 1
    Is the idea that an instance might provide its own version of info_to_be_accessed before calling create_methods? It sounds like you just want a dictionary, not a set of methods. (Or a single method get that performs dictionary lookups for you.) Commented Dec 20, 2018 at 19:35
  • I have edited original post in order to answer your question. Commented Dec 20, 2018 at 19:47
  • It's really not clear from that description what you want. However, it sounds like the only reason myClass exists is to replace dictionary lookups with method calls. That seems like a huge waste of time and effort. Why go through the hassle of something like d = {'foo': 'bar'}; myClass(d).foo() when you can just use d['foo']? Commented Dec 20, 2018 at 19:53
  • Because I'm not working with dictionaries in real life, it was just a simpler example to make it easier to understand. Commented Dec 20, 2018 at 19:54

1 Answer 1

2

(Somewhat) replying to wim's comment:

I have not encountered a good use case for this myself, but you can set arbitrary functions as methods of arbitrary instances. Is it good style? Probably there's a better way in most cases. But you can and I'll show how.


You are just setting an attribute which happens to be a function on your instance.

Functions are (non-data) descriptors and you can use their __get__ method to fix the first parameter.

Demo:

>>> class myClass: 
...:     def __repr__(self): # nicer output for demo 
...:         return '<myClass object>' 
...:     def create_method_on_instance(self, name, function): 
...:         setattr(self, name, function.__get__(self))                                                               
>>>                                                                                                                    
>>> m1 = myClass()                                                                                                     
>>> m2 = myClass()                                                                                                     
>>> m1.create_method_on_instance('foo', lambda self, x, y: print(self, x + y))                                           
>>> m2.create_method_on_instance('foo', lambda self, x, y: print(self, x - y))                                           
>>>                                                                                                             
>>> m1.foo(3, 4)                                                                                                       
<myClass object> 7
>>> m2.foo(3, 4)                                                                                                 
<myClass object> -1

I can't reiterate everything from the Descriptor HowTo Functions and Methods section here, but it breaks down like this:

When you access a function attribute on an instance via the dot-notation, the function's __get__ method won't be called.

However, the function's __get__ method is responsible for binding the first argument (usually called self) of the function to the instance in question (thus creating a method).

You can still call __get__ manually if you want to create methods from arbitrary functions on arbitrary instances. This can get a little silly, since you could even bind the first function argument to some instance and then set the method as an attribute of another object ;)

>>> class Unrelated: 
...:     pass 
...:
>>> u = Unrelated()                                                                                                                   
>>> u.foo = (lambda self, x, y: print(self, x+y)).__get__(m1)                                                          
>>> u.foo(3, 4)                                                                                                        
<myClass object> 7
Sign up to request clarification or add additional context in comments.

2 Comments

-1 This is a really dumb idea and I don't think it's even what the OP was asking about anyway. Methods should exist on the class object, not on the instance.
I'm marking this as the answer since timgeb gets the point of my question: it's more about a curiosity than actual practical aplications or best possible solution. I love metaprogramming and got honestly curious about this, and this answer is really interesting and shows in-depth explanation of how descriptors work (which I didn't know before). Thank you very much for your help, sir.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.