Unification of Methods and Functions

D

David MacQuigg

David MacQuigg wrote:

Yes, but as I pointed out elsewhere, it may help to write about this in some
other context than your other proposals (the fact that this is buried in
Appendix 1 under a title of "Translating Python Classes to Prototypes" doesn't
lend itself to much reader traffic).

I've put some simple examples comparing Python 2 to the proposed
Unified Function Syntax at http://ece.arizona.edu/~edatools/Python
These are all pretty simple, and show just the changes necessary to
unify functions and methods. More examples are welcome. Send me
anything you think will be difficult to translate to the new syntax.

I've also moved the Appendix 1: Translating Python Classes from
Prototypes.doc to PrototypeSyntax.doc. These are "edge cases".
Unlike the simple examples above, the purpose is to show that all the
existing classes in Python can be migrated to the proposed syntax.

-- Dave
 
G

Greg Ewing

Neil said:
I am a ex-Java/.NET programmer so I admit I'm biased but the problem
is not language specific, it's design specific. I do understand that in
Python this is only syntatic sugar but using a module namespace to
acheive these needs removes what is a link in the design.

I don't see it that way. To me, using a module namespace is
an alternative way of expressing the same link. A naming
convention for functions (make_dodo, first_dodo,
extinctify_dodos) would be a third way of expressing the
link.
 
A

Antoon Pardon

3) Higher order functions. These are functions that
whose arguments and/or return values can be again
functions.

The typical example is a sorting function that takes
as an argument a function that decides which of two
elements is the smaller. By providing different
functions (one that looks at names or one that
looks at Id number) the same function can be used
to sort people alphabetical or Numerical by Id.

Now to go a step further. Suppose we have a lot
of functions Fi: Each of these functions is
defined as follows Fi(x) = i + x. Now we
can have an other function S. S is defined as
follows: S(a) = Fa. Now we can use S to add
two numbers. Suppose we want to add 2 and 5.
First we call S(2), this will result in F2.
Then we call F2(5) which will result in 7.
Or more directly we could call S(2)(5).

Now python allows to define higher order functions.
S would be defined as follows in python:

def S(i):

def Fi(x):

return i + x

return Fi

Now any normal function can be turned into a higher
order function with a related functionality. Supose
we have a function with the following signature in
python

def func(a1, a2, a3):


We can then write the higher order function as
follows:

def Hfunc(a1):

def Ifunc(a2, a3):

return func(a1, a2, a3)

return Ifunc


Now transforming a function to a higher order
equivallent is called currying and currying is
again a higher order function. In python we
write it as follows

def curry(f):

def Hf(a1):

def If(*ta):

la = (a1,) + ta
return apply(f, la)

return If

return Hf


So from now on if we have a function we can turn it
into its curried version as follows:

Hfunc = curry(func)


So now we have all we need for object oriented programming.
To illustrate I'll make a stack pseudo class. We start
with the assumption that we have the following class.

class Bare:
pass


First approach


def Stack():

st = Bare()
st.lst = []
return st


def Empty(st):

return len(st.lst) == 0


def Pop(st):

result = st.lst[-1]
del st.lst[-1]
return result


def Push(st, el):

st.lst.append(el)



stack = Stack()
Push(stack, 4)
Push(stack, 'hallo')
El = Pop(stack)


This is a strickt procedural approach. It has the disadvantages that the
procedures Empty, Pop and Push aren't very tied to the stack and that
we therefore prohibit other structures the use of the same name


We can eliminate these disadvantages as follows:


Second approach


def Stack():

def Empty(st):

return len(st.lst) == 0


def Pop(st):

result = st.lst[-1]
del st.lst[-1]
return result


def Push(st, el):

st.lst.append(el)


st = Bare()
st.lst = []
st.Empty = Empty
st.Pop = Pop
st.Push = Push
return st


stack = Stack()
stack.Push(stack, 4)
stack.Push(stack, 'hallo')
El = stack.Pop(stack)


This looks already somewhat object oriented. The problem here
is that you are essentially obligated to allways provide the
first argument twice. To eliminate this cumbersome repetition
we work with applied curried functions as methods.


Third approach

def Stack():

def Empty(st):

return len(st.lst) == 0


def Pop(st):

result = st.lst[-1]
del st.lst[-1]
return result


def Push(st, el):

st.lst.append(el)


st = Bare()
st.lst = []
st.Empty = curry(Empty)(st)
st.Pop = curry(Pop)(st)
st.Push = curry(Push)(st)
return st


stack = Stack()
stack.Push(4)
stack.Push('hallo')
El = stack.Pop()


And we now have standard python method calls. We will just
make some cosmetic changes here to have it resemble python
class conventions more.


Fourth approach

def Stack():

def __init__(self):

self.lst = []

def Empty(self):

return len(self.lst) == 0


def Pop(self):

result = self.lst[-1]
del self.lst[-1]
return result


def Push(self, el):

self.lst.append(el)


self = Bare()
__init__(self)
self.Empty = curry(Empty)(self)
self.Pop = curry(Pop)(self)
self.Push = curry(Push)(self)
return self


stack = Stack()
stack.Push(4)
stack.Push('hallo')
El = stack.Pop()


And this is more or less equivallent to:


class Stack():

def __init__(self):

self.lst = []

def Empty(self):

return len(self.lst) == 0


def Pop(self):

result = self.lst[-1]
del self.lst[-1]
return result


def Push(self, el):

self.lst.append(el)


stack = Stack()
stack.Push(4)
stack.Push('hallo')
El = stack.Pop()
 
D

David MacQuigg

I don't see it that way. To me, using a module namespace is
an alternative way of expressing the same link. A naming
convention for functions (make_dodo, first_dodo,
extinctify_dodos) would be a third way of expressing the
link.

To summarize our discussion on static methods, we have four ways to
accomplish the purpose of making a method callable without an
instance.

1) Add the staticmethod wrapper.
2) Create a dummy instance, and use it to call the method.
3) Move the method outside of the class, and if that disturbs the
modular structure of the program, make the class and its external
function a new module with its own namespace.
4) Move the method outside of the class, but make sure it is still
"associated" with the class by giving it a special name.

Is there any reason we *should not* use solution #1?

-- Dave
 
D

Duncan Booth

To summarize our discussion on static methods, we have four ways to
accomplish the purpose of making a method callable without an
instance.

1) Add the staticmethod wrapper.
2) Create a dummy instance, and use it to call the method.
3) Move the method outside of the class, and if that disturbs the
modular structure of the program, make the class and its external
function a new module with its own namespace.
4) Move the method outside of the class, but make sure it is still
"associated" with the class by giving it a special name.

Is there any reason we *should not* use solution #1?

You missed 5) Use a classmethod wrapper.

The commonest reason I know of for wanting to a method callable without an
instance is to provide the class with factory functions, and in that case
you definitely don't want to use solution #1 as solution #5 gives you
support for subclassing at no extra charge.
 
D

David MacQuigg

You missed 5) Use a classmethod wrapper.

The commonest reason I know of for wanting to a method callable without an
instance is to provide the class with factory functions, and in that case
you definitely don't want to use solution #1 as solution #5 gives you
support for subclassing at no extra charge.

I haven't added any classmethod examples to my OOP chapter, because
until now I've thought of them as very specialized. I'm searching for
a good textbook example, but all I can find is trivially replacable
with an instance method or a static method. If you have an instance
already, the class can be resolved via self.__class__. If you don't
have an instance, the desired class can be passed as an argument to a
static method.

I sounds like you may have a good use case for classmethods. Could
you give us an example, and a brief explanation of what it does that
can't be done as easily with other method forms? Your help will be
greatly appreciated.

-- Dave
 
D

Duncan Booth

I haven't added any classmethod examples to my OOP chapter, because
until now I've thought of them as very specialized. I'm searching for
a good textbook example, but all I can find is trivially replacable
with an instance method or a static method. If you have an instance
already, the class can be resolved via self.__class__. If you don't
have an instance, the desired class can be passed as an argument to a
static method.

I find that slightly surprising. You say that if you have a static method
you can pass the class as a parameter, but since you already specify the
class somewhere to access the static method surely you are duplicating
information unneccessarily? You could equally say that static methods
aren't needed because you can always use a class method and just ignore the
class parameter if you don't need it.
I sounds like you may have a good use case for classmethods. Could
you give us an example, and a brief explanation of what it does that
can't be done as easily with other method forms? Your help will be
greatly appreciated.

Ok, I'll try and give you a couple of examples, feel free to tear them
apart. The most obvious one is to write factory methods:

-------- begin cut ---------------
class Shape(object):
def __init__(self):
super(Shape, self).__init__()
self.moveTo(0, 0)
self.resize(10, 10)

def __repr__(self):
return "<%s instance at %s x=%s y=%s width=%s height=%s>" % (
self.__class__.__name__, id(self),
self.x, self.y, self.width, self.height)

def moveTo(self, x, y):
self.x, self.y = x, y

def resize(self, width, height):
self.width, self.height = width, height

# Factory methods
def fromCenterAndSize(cls, cx, cy, width, height):
self = cls()
self.moveTo(cx, cy)
self.resize(width, height)
return self
fromCenterAndSize = classmethod(fromCenterAndSize)

def fromTLBR(cls, top, left, bottom, right):
self = cls()
self.moveTo((left+right)/2., (top+bottom)/2.)
self.resize(right-left, top-bottom)
return self
fromTLBR = classmethod(fromTLBR)

class Rectangle(Shape): pass
class Ellipse(Shape): pass

print Rectangle.fromCenterAndSize(10, 10, 3, 4)
print Ellipse.fromTLBR(20, 0, 0, 30)
squares = [ Rectangle.fromCenterAndSize(i, j, 1, 1)
for i in range(2) for j in range(2) ]
print squares
-------- end cut ------------
Running this code gives something like:

<Rectangle instance at 9322032 x=10 y=10 width=3 height=4>
<Ellipse instance at 9322032 x=15.0 y=10.0 width=30 height=20>
[<Rectangle instance at 9321072 x=0 y=0 width=1 height=1>, <Rectangle
instance at 9320016 x=0 y=1 width=1 height=1>, <Rectangle instance at
9321200 x=1 y=0 width=1 height=1>, <Rectangle instance at 9321168 x=1 y=1
width=1 height=1>]

The important point is that the factory methods create objects of the
correct type.

The shape class has two factory methods here. Either one of them could
actually be moved into the initialiser, but since they both take the same
number and type of parameters any attempt to move them both into the
initialiser would be confusing at best. the factory methods give me two
clear ways to create a Shape, and it is obvious from the call which one I
am using.

Shape is clearly intended to be a base class, so I created a couple of
derived classes. Each derived class inherits the factory methods, or can
override them if it needs. Instance methods won't do here, as you want a
single call to create and initialise the objects. Static methods won't do
as unless you duplicated the class in the call you can't create an object
of the appropriate type.

My second example carries on from the first. Sometimes you want to count or
even find all existing objects of a particular class. You can do this
easily enough for a single class using weak references and a static method
to retrieve the count or the objects, but if you want to do it for several
classes, and want to avoid duplicating the code, class methods make the job
fairly easy.


--------- begin cut -------------
from weakref import WeakValueDictionary

class TrackLifetimeMixin(object):
def __init__(self):
cls = self.__class__
if '_TrackLifetimeMixin__instances' not in cls.__dict__:
cls.__instances = WeakValueDictionary()
cls.__instancecount = 0
cls.__instances[id(self)] = self
cls.__instancecount += 1

def __getInstances(cls):
return cls.__dict__.get('_TrackLifetimeMixin__instances' , {})
__getInstances = classmethod(__getInstances)

def getLiveInstances(cls):
instances = cls.__getInstances().values()
for k in cls.__subclasses__():
instances.extend(k.getLiveInstances())
return instances
getLiveInstances = classmethod(getLiveInstances)

def getLiveInstanceCount(cls):
count = len(cls.__getInstances())
for k in cls.__subclasses__():
count += k.getLiveInstanceCount()
return count
getLiveInstanceCount = classmethod(getLiveInstanceCount)

def getTotalInstanceCount(cls):
count = cls.__dict__.get('_TrackLifetimeMixin__instancecount' , 0)
for k in cls.__subclasses__():
count += k.getTotalInstanceCount()
return count
getTotalInstanceCount = classmethod(getTotalInstanceCount)


class Shape(TrackLifetimeMixin, object):
def __init__(self):
super(Shape, self).__init__()
self.moveTo(0, 0)
self.resize(10, 10)

def __repr__(self):
return "<%s instance at %s x=%s y=%s width=%s height=%s>" % (
self.__class__.__name__, id(self),
self.x, self.y, self.width, self.height)

def moveTo(self, x, y):
self.x, self.y = x, y

def resize(self, width, height):
self.width, self.height = width, height

# Factory methods
def fromCenterAndSize(cls, cx, cy, width, height):
self = cls()
self.moveTo(cx, cy)
self.resize(width, height)
return self
fromCenterAndSize = classmethod(fromCenterAndSize)

def fromTLBR(cls, top, left, bottom, right):
self = cls()
self.moveTo((left+right)/2., (top+bottom)/2.)
self.resize(right-left, top-bottom)
return self
fromTLBR = classmethod(fromTLBR)

class Rectangle(Shape): pass
class Ellipse(Shape): pass

print Rectangle.fromCenterAndSize(10, 10, 3, 4)
print Ellipse.fromTLBR(20, 0, 0, 30)
squares = [ Rectangle.fromCenterAndSize(i, j, 1, 1) for i in range(2) for j
in range(2) ]

print Shape.getLiveInstances()
for cls in Shape, Rectangle, Ellipse:
print cls.__name__, "instances:", cls.getLiveInstanceCount(), \
"now, ", cls.getTotalInstanceCount(), "total"
--------- end cut -------------

The middle part of this file is unchanged. I've added a new mixin class at
the top, but the class Shape is unchanged except that it now includes the
mixin class in its bases. The last 4 lines are also new and print a few
statistics about the classes Shape, Rectangle, Ellipse:

<Rectangle instance at 9376016 x=10 y=10 width=3 height=4>
<Ellipse instance at 9376016 x=15.0 y=10.0 width=30 height=20>
[<Rectangle instance at 9376240 x=0 y=0 width=1 height=1>, <Rectangle
instance at 9376272 x=0 y=1 width=1 height=1>, <Rectangle instance at
9376336 x=1 y=1 width=1 height=1>, <Rectangle instance at 9376304 x=1 y=0
width=1 height=1>]
Shape instances: 4 now, 6 total
Rectangle instances: 4 now, 5 total
Ellipse instances: 0 now, 1 total
 
D

David MacQuigg

I find that slightly surprising. You say that if you have a static method
you can pass the class as a parameter, but since you already specify the
class somewhere to access the static method surely you are duplicating
information unneccessarily? You could equally say that static methods
aren't needed because you can always use a class method and just ignore the
class parameter if you don't need it.

The information is duplicate only if the class we want to pass in is
the always the same as the class in which the method is defined, in
which case, we would just hard-code the class name in the method.

class Mammal(Animal):
def cmeth(cls, *args):
print cls, args
cm = classmethod(cmeth)
def smeth(cls, *args):
print cls, args
sm = staticmethod(smeth)

Using a classmethod:<class '__main__.Feline'> ()

Using a staticmethod:<class '__main__.Feline'> ()

You are right, we could just ignore the cls argument in the
classmethod, and avoid the need to learn staticmethods, but between
the two, I would rather use staticmethod, as it is simpler in most
cases.

What would be nice is if we had a variable like __class__ that would
work in any method form. Then we would not need a special first
argument for classmethods, or any special method form at all for this
purpose.
Ok, I'll try and give you a couple of examples, feel free to tear them
apart. The most obvious one is to write factory methods:

I like these examples, and I think they will fit nicely into the
teaching sequence -- Spam and Foo, Animals_1, Animals_2, then some
real programs. I would change the classmethods to staticmethods,
however, and avoid the need to teach classmethods. This would make
your calls look like:

print Shape.fromCenterAndSize(Rectangle, 10, 10, 3, 4)
-- or if you prefer a short alias --
print CS(Rectangle, 10, 10, 3, 4)

-- Dave

-------- begin cut ---------------
class Shape(object):
def __init__(self):
super(Shape, self).__init__()
self.moveTo(0, 0)
self.resize(10, 10)

def __repr__(self):
return "<%s instance at %s x=%s y=%s width=%s height=%s>" % (
self.__class__.__name__, id(self),
self.x, self.y, self.width, self.height)

def moveTo(self, x, y):
self.x, self.y = x, y

def resize(self, width, height):
self.width, self.height = width, height

# Factory methods
def fromCenterAndSize(cls, cx, cy, width, height):
self = cls()
self.moveTo(cx, cy)
self.resize(width, height)
return self
fromCenterAndSize = classmethod(fromCenterAndSize)

def fromTLBR(cls, top, left, bottom, right):
self = cls()
self.moveTo((left+right)/2., (top+bottom)/2.)
self.resize(right-left, top-bottom)
return self
fromTLBR = classmethod(fromTLBR)

class Rectangle(Shape): pass
class Ellipse(Shape): pass

print Rectangle.fromCenterAndSize(10, 10, 3, 4)
print Ellipse.fromTLBR(20, 0, 0, 30)
squares = [ Rectangle.fromCenterAndSize(i, j, 1, 1)
for i in range(2) for j in range(2) ]
print squares
-------- end cut ------------
Running this code gives something like:

<Rectangle instance at 9322032 x=10 y=10 width=3 height=4>
<Ellipse instance at 9322032 x=15.0 y=10.0 width=30 height=20>
[<Rectangle instance at 9321072 x=0 y=0 width=1 height=1>, <Rectangle
instance at 9320016 x=0 y=1 width=1 height=1>, <Rectangle instance at
9321200 x=1 y=0 width=1 height=1>, <Rectangle instance at 9321168 x=1 y=1
width=1 height=1>]

The important point is that the factory methods create objects of the
correct type.

The shape class has two factory methods here. Either one of them could
actually be moved into the initialiser, but since they both take the same
number and type of parameters any attempt to move them both into the
initialiser would be confusing at best. the factory methods give me two
clear ways to create a Shape, and it is obvious from the call which one I
am using.

Shape is clearly intended to be a base class, so I created a couple of
derived classes. Each derived class inherits the factory methods, or can
override them if it needs. Instance methods won't do here, as you want a
single call to create and initialise the objects. Static methods won't do
as unless you duplicated the class in the call you can't create an object
of the appropriate type.

My second example carries on from the first. Sometimes you want to count or
even find all existing objects of a particular class. You can do this
easily enough for a single class using weak references and a static method
to retrieve the count or the objects, but if you want to do it for several
classes, and want to avoid duplicating the code, class methods make the job
fairly easy.


--------- begin cut -------------
from weakref import WeakValueDictionary

class TrackLifetimeMixin(object):
def __init__(self):
cls = self.__class__
if '_TrackLifetimeMixin__instances' not in cls.__dict__:
cls.__instances = WeakValueDictionary()
cls.__instancecount = 0
cls.__instances[id(self)] = self
cls.__instancecount += 1

def __getInstances(cls):
return cls.__dict__.get('_TrackLifetimeMixin__instances' , {})
__getInstances = classmethod(__getInstances)

def getLiveInstances(cls):
instances = cls.__getInstances().values()
for k in cls.__subclasses__():
instances.extend(k.getLiveInstances())
return instances
getLiveInstances = classmethod(getLiveInstances)

def getLiveInstanceCount(cls):
count = len(cls.__getInstances())
for k in cls.__subclasses__():
count += k.getLiveInstanceCount()
return count
getLiveInstanceCount = classmethod(getLiveInstanceCount)

def getTotalInstanceCount(cls):
count = cls.__dict__.get('_TrackLifetimeMixin__instancecount' , 0)
for k in cls.__subclasses__():
count += k.getTotalInstanceCount()
return count
getTotalInstanceCount = classmethod(getTotalInstanceCount)


class Shape(TrackLifetimeMixin, object):
def __init__(self):
super(Shape, self).__init__()
self.moveTo(0, 0)
self.resize(10, 10)

def __repr__(self):
return "<%s instance at %s x=%s y=%s width=%s height=%s>" % (
self.__class__.__name__, id(self),
self.x, self.y, self.width, self.height)

def moveTo(self, x, y):
self.x, self.y = x, y

def resize(self, width, height):
self.width, self.height = width, height

# Factory methods
def fromCenterAndSize(cls, cx, cy, width, height):
self = cls()
self.moveTo(cx, cy)
self.resize(width, height)
return self
fromCenterAndSize = classmethod(fromCenterAndSize)

def fromTLBR(cls, top, left, bottom, right):
self = cls()
self.moveTo((left+right)/2., (top+bottom)/2.)
self.resize(right-left, top-bottom)
return self
fromTLBR = classmethod(fromTLBR)

class Rectangle(Shape): pass
class Ellipse(Shape): pass

print Rectangle.fromCenterAndSize(10, 10, 3, 4)
print Ellipse.fromTLBR(20, 0, 0, 30)
squares = [ Rectangle.fromCenterAndSize(i, j, 1, 1) for i in range(2) for j
in range(2) ]

print Shape.getLiveInstances()
for cls in Shape, Rectangle, Ellipse:
print cls.__name__, "instances:", cls.getLiveInstanceCount(), \
"now, ", cls.getTotalInstanceCount(), "total"
--------- end cut -------------

The middle part of this file is unchanged. I've added a new mixin class at
the top, but the class Shape is unchanged except that it now includes the
mixin class in its bases. The last 4 lines are also new and print a few
statistics about the classes Shape, Rectangle, Ellipse:

<Rectangle instance at 9376016 x=10 y=10 width=3 height=4>
<Ellipse instance at 9376016 x=15.0 y=10.0 width=30 height=20>
[<Rectangle instance at 9376240 x=0 y=0 width=1 height=1>, <Rectangle
instance at 9376272 x=0 y=1 width=1 height=1>, <Rectangle instance at
9376336 x=1 y=1 width=1 height=1>, <Rectangle instance at 9376304 x=1 y=0
width=1 height=1>]
Shape instances: 4 now, 6 total
Rectangle instances: 4 now, 5 total
Ellipse instances: 0 now, 1 total
 
D

Duncan Booth

I like these examples, and I think they will fit nicely into the
teaching sequence -- Spam and Foo, Animals_1, Animals_2, then some
real programs. I would change the classmethods to staticmethods,
however, and avoid the need to teach classmethods. This would make
your calls look like:

print Shape.fromCenterAndSize(Rectangle, 10, 10, 3, 4)
-- or if you prefer a short alias --
print CS(Rectangle, 10, 10, 3, 4)

So how do you handle the case where you need to override one of the factory
methods in a subclass?

e.g. Rather contortedly:

class OffsetRectangle(Rectangle):
'''A rectangle where the 'center' is actually half way up
the left edge'''
def fromCenterAndSize(cls, cx, cy, width, height):
self = cls()
self.moveTo(cx+width/2.0, cy)
self.resize(width, height)
return self
fromCenterAndSize = classmethod(fromCenterAndSize)

print OffsetRectangle.fromCenterAndSize(5, 10, 10, 10)
 
D

David MacQuigg

So how do you handle the case where you need to override one of the factory
methods in a subclass?

e.g. Rather contortedly:

I'm not sure what this modification is intended to do, but assuming it
must work with multiple subclasses of OffsetRectangle, I would write
the example as follows:

class OffsetRectangle(Rectangle):
'''A rectangle where the 'center' is actually half way up
the left edge'''
def fromCenterAndSize(cls, cx, cy, width, height):
self = cls()
self.moveTo(cx+width/2.0, cy)
self.resize(width, height)
return self
## fromCenterAndSize = classmethod(fromCenterAndSize)
frmCenterAndSize = staticmethod(fromCenterAndSize)

class Rect1(OffsetRectangle): pass
class Rect2(OffsetRectangle): pass
class Rect3(OffsetRectangle): pass

for cls in Rect1, Rect2, Rect3, Rectangle:
print OffsetRectangle.fromCenterAndSize(cls, 5, 10, 10, 10)

On the other hand, if you don't want to use the specific method from
OffsetRectangle, but you want the method to be chosen from the same
class as the class being instantiated, the last line would be:

print cls.fromCenterAndSize(cls, 5, 10, 10, 10)

I may be missing your intent. If so, maybe you could show a more
complete example.

-- Dave
 
D

Duncan Booth

I may be missing your intent. If so, maybe you could show a more
complete example.

Ok, let me try to clarify again.

I am suggesting that we write factory methods using classmethod to give
code like:

Rectangle.fromCenterAndSize(10, 10, 3, 4)
Ellipse.fromCenterAndSize(10, 10, 3, 4)
OffsetRectangle.fromCenterAndSize(10, 10, 3, 4)
TextLabel.fromCenterAndSize(10, 10, 3, 4)

and as I understand it, you would prefer to use static methods and
write:

Shape.fromCenterAndSize(Rectangle, 10, 10, 3, 4)
Shape.fromCenterAndSize(Ellipse, 10, 10, 3, 4)
Shape.fromCenterAndSize(OffsetRectangle, 10, 10, 3, 4)
Shape.fromCenterAndSize(TextLabel, 10, 10, 3, 4)

In the hope that I'm right so far, I'll try to list some of the ways
that I believe my code is a better way to implement this sort of scheme.

First off, my classes all implement an interface which I'll call 'shape
factory'. Interfaces in Python aren't declared explicitly, but that
doesn't stop me claiming that I have one. Any class which implements
shape factory can be instanciated in a consistent manner.

Secondly, the implementation is hidden. I might tell you here that
Rectangle and Ellipse have a common base class, OffsetRectangle
subclasses Rectangle, and TextLabel doesn't share the same base class as
the others (perhaps it doesn't have any other methods in common), but
this would just be irrelevant information and any other relationship
between the classes would work just so long as each continued to
implement the shape factory interface.

I'm sure we both know the difference between a base class and an
interface, but, just to check we are communicating, I'm going to spell
out what I think is the important distinction:

An interface lets multiple classes share functionality or services that
they offer. A base class lets multiple classes share common
implementations of code. A base class need not supply any public
interface; indeed a base class might expose a public interface which
derived classes do not expose.

I think this is important, as you rarely want to write code where the
common implementation is the important thing; usually its the common set
of services that are important. Even in languages such as C++ and Java
it is better to write code that accepts objects which implement an
interface rather than code which requires objects to have a specific
base class.

The final point I want to make about my code, and I admit it is less
important, is that the factory methods can be passed around just like
any other method. I could have a dict mapping object name to factory
method and create any of the objects in my system simply by calling the
saved method.

Back to your way of doing things:

Shape.fromCenterAndSize(Rectangle, 10, 10, 3, 4)
Shape.fromCenterAndSize(Ellipse, 10, 10, 3, 4)
Shape.fromCenterAndSize(OffsetRectangle, 10, 10, 3, 4)
Shape.fromCenterAndSize(TextLabel, 10, 10, 3, 4)

I may have some problems communicating my intent here. How do I know
which classes it is valid to pass to Shape.fromCenterAndSize? My classes
no longer have any common interface.

Oops. Did I mention that TextLabel is an unrelated class and doesn't
have the same methods as Shape? Shape.fromCenterAndSize calls the resize
method, but my TextLabel class has a setFont method instead, so that
code is going to throw an AttributeError. Fortunately, that is easily
fixed:

Shape.fromCenterAndSize(Rectangle, 10, 10, 3, 4)
Shape.fromCenterAndSize(Ellipse, 10, 10, 3, 4)
Shape.fromCenterAndSize(OffsetRectangle, 10, 10, 3, 4)
TextLabel.fromCenterAndSize(TextLabel, 10, 10, 3, 4)

Sadly, it looks as though the implementation, which I presume we would like
to hide, is beginning to be significant.

The code may now execute with throwing an exception, but I just spotted
a worse problem. The OffsetRectangle object has its own implementation
of fromCenterAndSize, so while you can construct it through the Shape
class you get the wrong values stored. You can fix that in the same way
as before, but there is still a deadly trap for the unwary: anyone who
accidentally calls the factory method in Shape will silently get the
wrong results:

Shape.fromCenterAndSize(Rectangle, 10, 10, 3, 4)
Shape.fromCenterAndSize(Ellipse, 10, 10, 3, 4)
OffsetRectangle.fromCenterAndSize(OffsetRectangle, 10, 10, 3, 4)
TextLabel.fromCenterAndSize(TextLabel, 10, 10, 3, 4)

Now my problem is the inconsistency of the code, the underlying
implementation has become all important. Why should I be calling
the method on Shape for two of the classes, and on the classes
themselves for the other two? How am I going to remember which is which?
Fortunately there is nothing stopping us from using the derived class
for the call:

Rectangle.fromCenterAndSize(Rectangle, 10, 10, 3, 4)
Ellipse.fromCenterAndSize(Ellipse, 10, 10, 3, 4)
OffsetRectangle.fromCenterAndSize(OffsetRectangle, 10, 10, 3, 4)
TextLabel.fromCenterAndSize(TextLabel, 10, 10, 3, 4)

Now we have a consistent interface again. The only problem with this is
that we have duplication in the call. That is easily fixed though by
switching to class methods and you get back to my code.

One more point. You said in an earlier post:
I would change the classmethods to staticmethods, however, and avoid
the need to teach classmethods.

If you are going to teach your students how to create objects in Python you
will need to explain the __new__ method. __new__ is automatically a class
method, so there is no way you can avoid teaching class methods. You can
however avoid teaching static methods.
 
D

David MacQuigg


Shape.fromCenterAndSize(Rectangle, 10, 10, 3, 4)
Shape.fromCenterAndSize(Ellipse, 10, 10, 3, 4)
Shape.fromCenterAndSize(OffsetRectangle, 10, 10, 3, 4)
Shape.fromCenterAndSize(TextLabel, 10, 10, 3, 4)

Rectangle.fromCenterAndSize(Rectangle, 10, 10, 3, 4)
Ellipse.fromCenterAndSize(Ellipse, 10, 10, 3, 4)
OffsetRectangle.fromCenterAndSize(OffsetRectangle, 10, 10, 3, 4)
TextLabel.fromCenterAndSize(TextLabel, 10, 10, 3, 4)

Now we have a consistent interface again. The only problem with this is
that we have duplication in the call. That is easily fixed though by
switching to class methods and you get back to my code.

I can still replace this more easily with:

for cls in Rectangle,Ellipse,OffsetRectangle,Textlabel:
cls.fromCenterAndSize(cls, 10, 10, 3, 4)

However, if you change this example so all the arguments are
different, then you have made your point. Class methods can avoid
much duplication of class names.
One more point. You said in an earlier post:


If you are going to teach your students how to create objects in Python you
will need to explain the __new__ method. __new__ is automatically a class
method, so there is no way you can avoid teaching class methods. You can
however avoid teaching static methods.

Actually, __new__ is a staticmethod. According to GvR in
http://python.org/2.2.3/descrintro.html#__new__
'''
Factoid: __new__ is a static method, not a class method. I initially
thought it would have to be a class method, and that's why I added the
classmethod primitive. Unfortunately, with class methods, upcalls
don't work right in this case, so I had to make it a static method
with an explicit class as its first argument. Ironically, there are
now no known uses for class methods in the Python distribution (other
than in the test suite). However, class methods are still useful in
other places, for example, to program inheritable alternate
constructors.
'''

Static methods are needed whenever you want to call a method without
an instance. True, we could do this with a class method and just
ignore the class, but the strange first argument will need further
explanation, and will seem odd to students seeing lots of static
methods and very few class methods in typical programs.

I will probably leave class methods as they are in Learning Python,
2nd ed - in an "advanced" section toward the end of the OOP chapters.

I haven't yet decided what to do with __new__ and __metaclass__. I've
received a good example of using these to generate classes for the
Animals examples, but the Python documentation is poor, and I think I
can accomplish the purpose of automatically generating classes with
correct attributes, using "factory functions" as your example
illustrates.

-- Dave
 
D

David MacQuigg

Sorry for the late reply on this question. I just now saw this post
in reviewing the thread.

I may be way off here, but I don't see how a global __self__ could work:

<not python>

class B:
def __init__(name,data):
.data = data*5
.name = '***%s***'%name

class A:
def __init__(name,data):
.data = data # __self__ is a?
.obj = B() # __self__ is now the B instance?
.name = name # now what?

a = A()

</not python>

The __self__ variable is set automatically when a function is called
from an instance, and returned to its prior value ( usually None )
when the call returns. So in the example above, we have an implicit
call to __init__ from the new instance a. This makes the first line
of the __init__ function equivalent to a.data = data.

The second line is equivalent to a.obj = B(). When the new instance
of B is constructed, there is a call to its __init__ function from
that new instance. At that moment, __self__ is set to the new
instance (a.obj), and that instance gets two new attributes, data and
name.

On returning from the call to B.__init__, __self__ returns to its
prior value 'a', and we set the final attribute a.name When you are
done, there will be one instance a, with attributes data, name, and
obj. The obj attribute is an instance of B, with attributes data and
name.

It works just like in current Python, except that the current instance
is "passed" by pointing to it with a special variable, rather than
inserting it into the argument sequence. The key difference is that
when you use the first argument for passing an instance, that instance
is *required* in every call, even if it is not used. (Hence the need
for special syntax when we want to avoid this requirement.) If you
use a global variable to pass the current instance, any function that
doesn't require it simply ignores it.

-- Dave
 
D

Duncan Booth

Actually, __new__ is a staticmethod.

Thanks, I'd missed that.
I haven't yet decided what to do with __new__ and __metaclass__. I've
received a good example of using these to generate classes for the
Animals examples, but the Python documentation is poor, and I think I
can accomplish the purpose of automatically generating classes with
correct attributes, using "factory functions" as your example
illustrates.
The main time I've felt a need to use __new__ is to implement flyweight
classes. If the object to be returned is based entirely on the arguments to
the constructor, and the object doesn't contain modifiable state, then you
can cache the objects you have already created and simply return the
appropriate one from the cache instead of creating a new object.

The same argument would apply to singletons except I've never yet found a
good use for a singleton :)
 

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,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top