Dynamically adding and removing methods
Steven D'Aprano
steve at REMOVETHIScyber.com.au
Wed Sep 28 08:53:55 EDT 2005
On Tue, 27 Sep 2005 16:42:21 +0000, Ron Adam wrote:
>>> >>> def beacon(self, x):
>>>... print "beacon + %s" % x
>>>...
>>
>>
>> Did you mean bacon? *wink*
>
> Of course... remembering arbitrary word letter sequences is probably my
> worst skill. ;-) That, and I think for some reason the name Francis
> Beacon was in my mind at the time.
I think you mean Francis Bacon *wink*
Thanks to everybody for all the feedback, it was useful, but I was finding
that I couldn't keep it all straight in my head (as seen by the mistaken
conclusions I drew earlier). So I wrote a short script to systematically
test the various combinations of functions and methods. I'm satisfied with
the results. For the sake of completeness, here are my results:
In the script below, ham and spam are defined first as regular functions
outside of a class definition. ham is defined with a placeholder for
"self", spam is not. They are then assigned (in separate tests) to both an
instance and a class.
eggs is defined as a class method, and then assigned to an object outside
a class. Think of it as the control variable.
"Failure" means the call raises an exception, "Success" means it does not.
Here are the results:
####################
Comparing module function with instance method:
=====================================================
Test: func= | ham | spam | eggs |
----------------------+---------+---------+---------+
func(arg) | Failure | Success | Failure |
inst.func(arg) | Failure | Success | Success |
inst.func(self, arg) | Success | Failure | Failure |
Comparing module function with class method:
=====================================================
Test: func= | ham | spam | eggs |
----------------------+---------+---------+---------+
func(arg) | Failure | Success | Failure |
inst.func(arg) | Success | Failure | Success |
inst.func(self, arg) | Failure | Failure | Failure |
Comparing object types:
====================================
Where: | ham | spam | eggs |
--------------+------+------+------+
module level | f | f | m |
instance | f | f | m |
class | m | m | m |
Key: f = function; m = method; ? = other
####################
This clearly shows that assigning a function to a class attribute invokes
whatever machinery Python uses to turn that function into a true method,
but assigning it to an instance does not.
Here is the script for those interested:
####################
"""Testing of the dynamic addition of functions to classes and instances."""
TABLE = """
%s
=====================================================
Test: func= | ham | spam | eggs |
----------------------+---------+---------+---------+
func(arg) | %s | %s | %s |
inst.func(arg) | %s | %s | %s |
inst.func(self, arg) | %s | %s | %s |
"""
TABLE2 = """
%s
====================================
Where: | ham | spam | eggs |
--------------+------+------+------+
module level | %s | %s | %s |
instance | %s | %s | %s |
class | %s | %s | %s |
Key: f = function; m = method; ? = other
"""
# Functions and methods to be tested:
def ham(self, x):
"""Function with placeholder self argument."""
return "ham " * x
def spam(x):
"""Function without placeholder self argument."""
return "spam " * x
class Klass:
def eggs(self, x):
"""Method defined in the class."""
return "%s eggs" % x
eggs = Klass.eggs
# Testing functions:
def get_type(obj):
s = type(obj).__name__
if s == "function":
return "f"
elif s == "instancemethod":
return "m"
else:
return "?"
def single_test(func, args):
"""Calls func(*args) and returns None if it succeeds or an exception if it fails."""
try:
func(*args)
return "Success"
except Exception, obj:
return "Failure"
def multiple_test(instance, label):
"""Runs multiple tests and returns a table of results."""
L = [label]
for f in (ham, spam, eggs, instance.ham, instance.spam, instance.eggs):
L.append(single_test(f, [1]))
for f in (instance.ham, instance.spam, instance.eggs):
L.append(single_test(f, [instance, 1]))
return TABLE % tuple(L)
def type_test(inst1, inst2):
L = ["Comparing object types:"]
for obj in (ham, spam, eggs, inst1.ham, inst1.spam, inst1.eggs, \
inst2.ham, inst2.spam, inst2.eggs):
L.append(get_type(obj))
return TABLE2 % tuple(L)
def main():
inst1 = Klass()
inst1.ham = ham
inst1.spam = spam
print multiple_test(inst1, "Comparing module function with instance method:")
inst2 = Klass()
Klass.ham = ham
Klass.spam = spam
print multiple_test(inst2, "Comparing module function with class method:")
print type_test(inst1, inst2)
if __name__ == "__main__":
main()
####################
I hope this is as interesting and useful for others as it was for me.
--
Steven.
More information about the Python-list
mailing list