polymorphism in python

R

rashkatsa

Hi all !

I have written a little module called 'polymorph' that allows to call
different methods of a class that have the same name but that have not
the same number of arguments. *args are supported. You could find the
module attached to this message.

As in python, you could not define two method with same name (the second
one override the first one), i have choosen to design it through private
prefix '__' (defined method names and called method name are different).
You just have to put the number of args of your method (without the
'self' one) and use the prefix keyword 'p' (polymorph) or 'pe' (elliptic
method where last argument is '*args').

now an example:

#=======================================================================
import polymorph

""" You could add polymorphism for your class.
You just have to inherit polymorph.Polymorph class
and prefix your methods that have the same name
but differ from the number of args with the
following prefix :

__<x>p__ where x is the number of args of your method
__<x>pe__ where x is the number of args of your method if you
use elliptic argument at the end (*args)

see below for a little demo class.

Priority : check if __<x>p__... exists before __<x>pe__... for the
same <x>.
"""

class Demo(polymorph.Polymorph):
def __init__(self):
PolymorphicClass.__init__(self)
def __1p__foo(self, first):
return (first,)
def __2p__foo(self,first,second):
return (first,second)
def __0p__bar(self):
return ()
def __1pe__bar(self,*args):
return [args]
def __2p__bar(self,first,second):
return (second,first)

aninstance = Demo()

#print "foo with no parameter : ",aninstance.foo()
print "foo with 1 parameter : ",aninstance.foo(3)
print "foo with 2 parameters : ",aninstance.foo(4,6)
#print "foo with 3 parameters : ",aninstance.foo(4,6,8)
print "bar with 0 parameter : ",aninstance.bar()
print "bar with 1 parameter : ",aninstance.bar(4)
print "bar with 2 parameters : ",aninstance.bar(9,1)
print "bar with 10 parameters : ",aninstance.bar(1,2,3,4,5,6,7,8,9,0)
#=======================================================================

returns :

foo with 1 parameter : (3,)
foo with 2 parameters : (4, 6)
bar with 0 parameter : ()
bar with 1 parameter : [(4,)]
bar with 2 parameters : (1, 9)
bar with 10 parameters : [(1, 2, 3, 4, 5, 6, 7, 8, 9, 0)]

I think that this could help to design classes with lesser 'switch'
behavior in all-in-one method with *args argument.

Do you find it useful ?

regards,

rashkatsa



import types,string

""" You could add polymorphism for your class.
You just have to inherit polymorph.Polymorph class
and prefix your methods that have the same name
but differ from the number of args with the
following prefix :

__<x>p__ where x is the number of args of your method
__<x>pe__ where x is the number of args of your method if you use elliptic argument at the end (*args)

for example :
def __1p__spam(self, first)
def __3pe__egg(self,first,second,*args)

Priority : check if __<x>p__... exists before __<x>pe__... for the same <x>.

author: rashkatsa
send any comments to (e-mail address removed)
"""

# metaclass
class MetaPolymorph:
def __init__(self, name, bases, namespace):
"""Create a new class."""
self.__name__ = name
self.__bases__ = bases
self.__namespace__ = namespace
def __call__(self):
"""Create a new instance."""
return PolymorphInstance(self)

# instance
class PolymorphInstance:
def __init__(self, metaclass):
self.__metaclass__ = metaclass
# simple polymorphic method
self.polymorphicMethods={}
for pmn in [x for x in self.__metaclass__.__namespace__.keys() if string.find(x,'p__') != -1 and string.rfind(x[:string.find(x,'p__')],'__')!=-1 and type(self.__metaclass__.__namespace__[x]) is types.FunctionType]:
pm = pmn[string.find(pmn,'p__')+3:]
nbarg=str(pmn[string.rfind(pmn[:string.find(pmn,'p__')],'__')+2:string.find(pmn,'p__')])
if pm in self.polymorphicMethods:
self.polymorphicMethods[pm][nbarg]=self.__metaclass__.__namespace__[pmn]
else:
self.polymorphicMethods[pm]={nbarg:self.__metaclass__.__namespace__[pmn]}
# elliptic polymorphic method
self.ePolymorphicMethods={}
for pmn in [x for x in self.__metaclass__.__namespace__.keys() if string.find(x,'pe__') != -1 and string.rfind(x[:string.find(x,'pe__')],'__')!=-1 and type(self.__metaclass__.__namespace__[x]) is types.FunctionType]:
pm = pmn[string.find(pmn,'pe__')+4:]
nbarg=str(pmn[string.rfind(pmn[:string.find(pmn,'pe__')],'__')+2:string.find(pmn,'pe__')])
if pm in self.ePolymorphicMethods:
self.ePolymorphicMethods[pm][nbarg]=self.__metaclass__.__namespace__[pmn]
else:
self.ePolymorphicMethods[pm]={nbarg:self.__metaclass__.__namespace__[pmn]}
def __getattr__(self, name):
polymorphMethodName=None
try:
value = self.__metaclass__.__namespace__[name]
except KeyError:
if name in self.polymorphicMethods.keys()+self.ePolymorphicMethods.keys():
polymorphMethodName=name
else:
raise AttributeError, name
if not polymorphMethodName:
if type(value) is not types.FunctionType:
return value
return BoundMethod(self,value)
else:
if name in self.polymorphicMethods.keys():
pMethods=self.polymorphicMethods[name]
else:
pMethods=None
if name in self.ePolymorphicMethods.keys():
ellipticPMethods=self.ePolymorphicMethods[name]
else:
ellipticPMethods=None
return BoundPolymorphMethods(self,polymorphMethodName,pMethods,ellipticPMethods)

# bound function with instance == BoundMethod
class BoundMethod:
def __init__(self, instance, function):
self.instance = instance
self.function = function
def __call__(self, *args):
return apply(self.function, (self.instance,) + args)

# bound polymorph functions with instance == BoundPolymorphMethods
class BoundPolymorphMethods:
def __init__(self, instance, functionName, functions, ellipticFunctions):
self.instance = instance
self.functionName=functionName
self.functions = functions
self.ellipticFunctions=ellipticFunctions
def __call__(self, *args):
self.func=None
argl=len(args)
try:
self.func = self.functions[str(argl)]
except KeyError:
# try to find an elliptic function
if self.ellipticFunctions:
# sort the list to find the min-max elliptic function that could be called
sortedKeys=[int(x) for x in self.ellipticFunctions.keys()]
sortedKeys.sort()
sortedKeys.reverse()
keyFound=None
# (x-1) because *args could be empty : ()
for x in sortedKeys:
if argl >= (x-1):
keyFound=str(x)
break
if keyFound:
self.func=self.ellipticFunctions[keyFound]
if not self.func:
if argl < 1:
errorMsg='Error: Unknown polymorphic method '+self.functionName+' with no argument'
elif argl == 1:
errorMsg='Error: Unknown polymorphic method '+self.functionName+' with 1 argument'
else:
errorMsg='Error: Unknown polymorphic method '+self.functionName+' with '+str(len(args))+' arguments'
raise errorMsg
return apply(self.func, (self.instance,) + args)

# create Polymorph class with the metaclass defined above
Polymorph = MetaPolymorph('Polymorph', (), {})
 
M

martin z

A very interesting invention... does it work with parameter defaults? Oh,
and IIRC, the word you're looking for is "Function overloading" not
"polymorphism". Polymorphism refers to the ability to have completely
different objects that can be used the same way, which Python always has.
 
J

Jay O'Connor

martin said:
A very interesting invention... does it work with parameter defaults? Oh,
and IIRC, the word you're looking for is "Function overloading" not
"polymorphism". Polymorphism refers to the ability to have completely
different objects that can be used the same way, which Python always has.

"Function overloading" is still one form of polymorphism. Polymorphism
as a concept can be approached in different ways, including function
overloading, generic programming and inheritance. Languages like
Python are Smalltalk are very good at implementing polymorphism based
on sending a message to an object and as long as the object understands
the message, it doesn't matter what the object is (part of their dynamic
binding); but they are not as good at overloading based on argument
types (how do you have two methods that have the same name but one takes
a single integer argument and one takes a single string argument?
answer, you have one function with an 'if' decision, or you have two
functions with two different names, breaking the polymorphism, or for
more complex objects you get doule-dispatch involved) . Languages like
C++ and Java restrict polymorphism of objects to those within the
hierarchy, but do allow for polymophism by function overloading; C++ and
Ada also have polymopshim through generic programming (generics in Ada,
templates in C++)
 
A

anton muhin

Jay said:
martin z wrote:
"Function overloading" is still one form of polymorphism.
I'm not completly agree. Indeed, dispatching on arguments' types can be
regarded as polymorphism. However, OP needed to dispatch on *number* of
parameters that seems like classic overloading IMHO

regards,
anton.
 
J

Jay O'Connor

anton said:
I'm not completly agree. Indeed, dispatching on arguments' types can
be regarded as polymorphism. However, OP needed to dispatch on
*number* of parameters that seems like classic overloading IMHO


I didn't really address the OP phrasing because I'm not really convinced
that overloading on numbers of parameters is really polymophism either,
I was mostly just addressing the follow up; just pointing out that
function overloading, in general, is one way of implementing polymorphism.
 
R

Roy Smith

Jay O'Connor said:
Languages like Python [and] Smalltalk [...] are not as good at
overloading based on argument types (how do you have two methods
that have the same name but one takes a single integer argument
and one takes a single string argument?

But how often is it actually useful to do such a thing? I can't
remember the last time I wanted to do something like that in Python.
Did lots of it in C++, but that's more working around the language than
anything else. Can you think of a good example of why you would want to
do something like that in Python?

If your intent is that you could call foo(4) or foo('4'), you could just
write:

def foo (x):
theRealX = int (x)

or you could maybe even do:

def foo (x):
if type(x) == StringType:
do string stuff
else:
do integer stuff

but if you're trying to do either of the above, I suspect you're trying
to write C++ in Python (which is as bad, if not worse, as trying to
write Fortran in Python).
 
K

KefX

f your intent is that you could call foo(4) or foo('4'), you could just
write:

def foo (x):
theRealX = int (x)

or you could maybe even do:

def foo (x):
if type(x) == StringType:
do string stuff
else:
do integer stuff

but if you're trying to do either of the above, I suspect you're trying
to write C++ in Python (which is as bad, if not worse, as trying to
write Fortran in Python).

The second example should use the expression:
isinstance(x, str)

and not this:
type(x) == StringType

But as you said, it's best to avoid doing it at all ;)

- Kef
 
J

Jay O'Connor

Roy said:
Languages like Python [and] Smalltalk [...] are not as good at
overloading based on argument types (how do you have two methods
that have the same name but one takes a single integer argument
and one takes a single string argument?

But how often is it actually useful to do such a thing? I can't
remember the last time I wanted to do something like that in Python.

I occasionally run into it when I want to display something where you
can polymorphically ask to display something but how it's displayed
depends on whether it's a number or not. However, it's a failry
contrived example as you can do operator overloading on more than just
strings and integers. Even on objects within a hiearchy. I'm not really
a big fan off it, just that it can be done; and that it's legitamite
polymorphism.
or you could maybe even do:

def foo (x):
if type(x) == StringType:
do string stuff
else:
do integer stuff

Generally, if you are programming to the interface and not to the class
(which is what you are usually doing in OO programming, or should be if
your language allows it) than testing for the actual type of an object
is usually a no-no
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top