Dynamically adding and removing methods

Steven D'Aprano steve at REMOVETHIScyber.com.au
Wed Sep 28 14:53:55 CEST 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