Learning Python now coming from Perl

Nick Craig-Wood nick at craig-wood.com
Tue Dec 9 13:30:52 CET 2008


Roy Smith <roy at panix.com> wrote:
>  In article <slrngjps0o.51b.nick at irishsea.home.craig-wood.com>,
>   Nick Craig-Wood <nick at craig-wood.com> wrote:
>   
> > My favourite mistake when I made the transition was calling methods
> > without parentheses.  In perl it is common to call methods without
> > parentheses - in python this does absolutely nothing!  pychecker does
> > warn about it though.
> > 
> >   perl   -> $object->method
> >   python -> object.method()
> 
>  On the other hand, leaving out the parens returns the function itself, 
>  which you can then call later.  I've often used this to create data-driven 
>  logic.

I didn't say it wasn't useful, just that if you came from Perl like I
did, it is an easy mistake to make ;-)

>  For example, I'm currently working on some code that marshals objects of 
>  various types to a wire protocol.  I've got something like:
> 
>  encoders = {
>     SM_INT: write_int,
>     SM_SHORT: write_short,
>     SM_FLOAT: write_float,
>     # and so on
>  }
> 
>  class AnyVal:
>     def __init__(self, type, value):
>        self.type = type
>        self.value = value
> 
>  def write_anyval(any):
>     encoders[any.type](any.value)
> 
>  The fact that functions are objects which can be assigned and stored in 
>  containers makes this easy to do.

OO lore says whenever you see a type field in an instance you've gone
wrong - types should be represented by what sort of object you've got,
not by a type field.

Eg http://www.soberit.hut.fi/mmantyla/BadCodeSmellsTaxonomy.htm

"""The situation where switch statements or type codes are needed
should be handled by creating subclasses. """

Here is my first iteration (untested)

class AnyVal:
   def __init__(self, value):
      self.value = value
   def write(self):
      raise NotImplementedError()

class IntVal(AnyVal):
   def write(self):
      # write_int code

class ShortVal(AnyVal):
   def write(self):
      # write_short code

class FloatVal(AnyVal):
   def write(self):
      # write_float code

Then to write an AnyVal you just call any.write()

The initialisation of the AnyVals then becomes

from AnyVal(int_expression, SM_INT)
to IntVal(int_expression)

However, if the types of the expressions aren't known until run time,
then use a dict of class types

AnyValRegistry = {
   SM_INT: IntVal,
   SM_SHORT: ShortVal,
   SM_FLOAT: FloatVal,
   # and so on
}

And initialise AnyVal objects thus

  any = AnyValRegistry[type](value)

This smells of code duplication though and a real potential for a
mismatch between the AnyValRegistry and the actual classes.

I'd probably generalise this by putting the type code in the class and
use a __metaclass__ to autogenerate the AnyValRegistry dict which
would then become an attribute of AnyClass

Eg (slightly tested)

SM_INT=1
SM_SHORT=2
SM_FLOAT=3

class AnyVal(object):
   TYPE = None
   registry = {}
   class __metaclass__(type):
       def __init__(cls, name, bases, dict):
           cls.registry[cls.TYPE] = cls
   def __init__(self, value):
       self.value = value
   @classmethod
   def new(cls, type_code, value):
       """Factory function to generate the correct subclass of AnyVal by type code"""
       return cls.registry[type_code](value)
   def write(self):
       raise NotImplementedError()

class IntVal(AnyVal):
   TYPE = SM_INT
   def write(self):
      # write_int code
      print "int", self.value

class ShortVal(AnyVal):
   TYPE = SM_SHORT
   def write(self):
      # write_short code
      print "short", self.value

class FloatVal(AnyVal):
   TYPE = SM_FLOAT
   def write(self):
      # write_float code
      print "float", self.value

You then make new objects with any = AnyVal.new(type_code, value) and
write them with any.write()

Anyone can add a subclass of AnyVal and have it added to the
AnyVal.registry which is neat.

>>> any = AnyVal.new(SM_SHORT, 1)
>>> any
<__main__.ShortVal object at 0xb7e3776c>
>>> any.write()
short 1

>>> any = AnyVal.new(SM_FLOAT, 1.8)
>>> any
<__main__.FloatVal object at 0xb7e37a6c>
>>> any.write()
float 1.8

You could also override __new__ so you could write AnyVal(type_code,
value) to create the object of a new type.  I personally don't think
its is worth it - a factory function is nice and obvious and show
exactly what is going on.

-- 
Nick Craig-Wood <nick at craig-wood.com> -- http://www.craig-wood.com/nick



More information about the Python-list mailing list