1

I'm making a program in python in which specific instances of an object must be decorated with new functions built at runtime.

I've seen very simple examples of adding functions to objects through MethodType:

import types
def foo():
    print("foo")

class A:
    bar = "bar"

a = A()
a.foo = types.MethodType(foo, a)

But none of the examples I've seen show how a function added in this manner can reference to the new owner's attributes. As far as I know, even though this binds the foo() function to the instance a, foo() must still be a pure function, and cannot contain references to anything local.

In my case, I need functions to change attributes of the object they are added to. Here are two examples of the kind of thing I need to be able to do:

class A:
    foo = "foo"
    def printme():
        print(foo)

def nofoo():
    foo = "bar"

def printBar():
    if foo != "foo"
        self.printme()

I would then need a way to add a copy of a nofoo() or printBar() to an A object in such a way that they can access the object attributes named foo and the function named printme() correctly.

So, is this possible? Is there a way to do this kind of programming in vanilla Python? or at least Is there a programming pattern that achieves this kind of behavior?

P.S.: In my system, I also add attributes dynamically to objects. Your first thought then might be "How can I ever be sure that the object I'm adding the nofoo() function to actually has an attribute named foo?", but I also have a fairly robust tag system that makes sure that I never try to add a nofoo() function to an object that hasn't a foo variable. The reason I mention this is that solutions that look at the class definition aren't very useful to me.

Althis
  • 181
  • 6
  • Your function must take at least one argument, i.e. `self` which will be passed the instance so you can access state associated with the instance. Note, this is how it *always* works.. also, of course `foo` can contain local references... – juanpa.arrivillaga Mar 07 '18 at 01:48
  • Possible duplicate of [Adding a Method to an Existing Object Instance](https://stackoverflow.com/questions/972/adding-a-method-to-an-existing-object-instance) – munk Mar 07 '18 at 01:57
  • Well, technically you could make this a `@staticmethod` (or just not wrap it in a `types.MethodType` before adding it to the instance, which has the same effect), and then it doesn't have to take a `self` argument. But of course then it can't access any attributes of `self`, because that's the whole point of static methods, and of not having `self`, so… – abarnert Mar 07 '18 at 01:57
  • Meanwhile, if you're adding methods to the class, not to the instances, you don't actually need `MethodType`, and in fact almost certaily don't want it. What are you trying to accomplish here? – abarnert Mar 07 '18 at 02:00
  • See https://repl.it/repls/HeftyScarceStrategy for an interactive example. That's how you add methods to classes dynamically; dead simple. https://repl.it/repls/FlamboyantCrookedThing is how you add them to instances; still pretty simple. Whatever problems you're having are either because you're trying to do something trickier than necessary, or because you're misunderstanding something much more fundamental and shouldn't even be trying this stuff yet. – abarnert Mar 07 '18 at 02:02
  • @abarnert I'm highly skeptical of this approach. It sounds like the OP wants prototype based inheritance, but Python has class-based inheritance. So, likely, it is best to implement this another way. – juanpa.arrivillaga Mar 07 '18 at 02:06
  • @juanpa.arrivillaga There are perfectly good reasons for this approach. I'm not at all convinced that the OP has one of those reasons, however. – abarnert Mar 07 '18 at 02:12
  • Just a comment on the possible duplicate question. I originally came from that exact question and it didn't solve my problem. My problem isn't on how to use a MethodType, but specifically on how to access instance attributes once it is used. I honestly think that is rather clear, but if the community continues to not believe so I will edit the question to make it more clear. – Althis Mar 07 '18 at 02:42
  • The answer to the question in your title is "yes". The answer to the question in your question body is that everything would just work, but you've gotten just about every possible basic thing wrong _except for_ the part you're asking about. Given that, it's not surprising that some people don't trust that you understand whether this question is a dup, or even don't trust that your question is meaningful in the first place. – abarnert Mar 07 '18 at 03:01

3 Answers3

1

As said in the comments, your function actually must take at least one parameter: self, the instance the method is being called on. The self parameter can be used as it would be used in a normal instance method. Here is an example:

>>> from types import MethodType
>>> 
>>> class Class:
        def method(self):
            print('method run')


>>> cls = Class()
>>> 
>>> def func(self): # must accept one argument, `self`
        self.method()


>>> cls.func = MethodType(func, cls)
>>> cls.func()
method run
>>>

Without your function accepting self, an exception would be raised:

>>> def func():
        self.method()

>>> cls.func = MethodType(func, cls)
>>> cls.func()
Traceback (most recent call last):
  File "<pyshell#21>", line 1, in <module>
    cls.func()
TypeError: func() takes 0 positional arguments but 1 was given
>>> 
Christian Dean
  • 22,138
  • 7
  • 54
  • 87
0
class A:
     def __init__(self):
         self.foo = "foo"

     def printme(self):
         print(self.foo)

def nofoo(self):
     self.foo = "bar"

a.nofoo = types.MethodType(nofoo, a)
a.nofoo()
a.printme()

prints

bar
nyr1o
  • 966
  • 1
  • 9
  • 23
0

It's not entirely clear what you're trying to do, and I'm worried that whatever it is may be a bad idea. However, I can explain how to do what you're asking, even if it isn't what you want, or should want. I'll point out that it's very uncommon to want to do the second version below, and even rarer to want to do the third version, but Python does allow them both, because "even rarer than very uncommon" still isn't "never". And, in the same spirit…

The short answer is "yes". A dynamically-added method can access the owner object exactly the same way a normal method can.


First, here's a normal, non-dynamic method:

class C:
    def meth(self):
        return self.x

c = C()
c.x = 3
c.meth()

Obviously, with a normal method like this, when you call c.meth(), the c ends up as the value of the self parameter, so self.x is c.x, which is 3.


Now, here's how you dynamically add a method to a class:

class C:
    pass

c = C()
c.x = 3

def meth(self):
    print(self.x)

C.meth = meth
c.meth()

This is actually doing exactly the same thing. (Well, we've left another name for the same function object sitting around in globals, but that's the only difference) If C.meth is the same function it was in the first version, then obviously whatever magic made c.meth() work in the first version will do the exact same thing here.

(This used to be slightly more complicated in Python 2, because of unbound methods, and classic classes too… but fortunately you don't have to worry about that.)


Finally, here's how you dynamically add a method to an instance:

class C:
    pass

c = C()
c.x = 3

def meth(self):
    print(self.x)

c.meth = types.MethodType(meth, c)
c.meth()

Here, you actually have to know the magic that makes c.meth() work in the first two cases. So read the Descriptor HOWTO. After that, it should be obvious.

But if you just want to pretend that Guido is a wizard (Raymond definitely is a wizard) and it's magic… Well, in the first two versions, Guido's magic wand creates a special bound method object whenever you ask for c.meth, but even he isn't magical enough to do that when C.meth doesn't exist. But we can painstakingly create that same bound method object and store it as c.meth. After that, we're going to get the same thing we stored whenever we ask for c.meth, which we explicitly built as the same thing we got in the first two examples, so it'll obviously do the same thing.


But what if we did this:

class C:
    pass

c = C()
c.x = 3

def meth(self):
    print(self.x)

c.meth = meth
c.meth(c)

Here, you're not letting Guido do his descriptor magic to create c.meth, and you're not doing it manually, you're just sticking a regular function there. Which means if you want anything to show up as the self parameter, you have to explicitly pass it as an argument, as in that silly c.meth(c) line at the end. But if you're willing to do that, then even this one works. No matter how self ends up as c, self.x is going to be c.x.

abarnert
  • 354,177
  • 51
  • 601
  • 671