简体   繁体   中英

Dynamically updated instance class method throws error when called by another class method

I think my example will make this easier to follow:

class x():
    def a(self):
        return "hello"
    def b(self):
        return self.a() + " world"

test = x()
print test.b()     # prints "hello world" as expected

test.a = lambda(self): "hola"
print test.b()     # throws error:
                   # Traceback (most recent call last):
                   #   File "<stdin>", line 1, in <module>
                   #   File "<stdin>", line 5, in b
                   # TypeError: <lambda>() takes exactly 1 argument (0 given)

Attempting to update to point x().a to another function, but when x().b calls it, it doesn't appear to pass self as the first argument.

I expected to get "hola world".

You can see the problem if you do

print type(test.a)  # <type 'function'>
print type(test.b)  # <type 'instancemethod'>

If you really want to patch a only on test (not on all instances of x ), you can do:

import types
test.a = types.MethodType((lambda self: "hola"), test, x)

To create an object of type instancemethod .

Functions and methods can be tricky to keep straight. A good rule of thumb is if you need it to be a method, store it on the class.

When do you need it to be a method? When the code is accessing parts of the instance or class.

In your simple example the replacement code does not access the instance, and so you could leave it a function on the instance, like so:

test.a = lambda: "hola"

and here's an example that does access the instance, with the code stored in the class (which is what you will usually want):

x.c = lambda self: self.b().upper()

or stored in the instance:

# using MethodType
import types
test.c = types.MethodType((lambda self: self.b().upper()), test)

# using partial
import functools
test.c = functools.partial((lambda self: self.b().upper()), test)

# just using the instance name, as Ryan did
test.c = lambda: test.some_attribute

While that last method will work most of the time, it does have one pitfall:

oops = test
del oops
oops.c()

Traceback (most recent call last):
  File "test.py", line 42, in <module>
    oops.c()
  File "test.py", line 38, in <lambda>
    test.c = lambda: test.some_attribute
NameError: global name 'test' is not defined

Just something to keep in mind.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM