Python 4

Here we can let our imagination run free – no constraints like in the Python 3 proposal.  We don't have to worry about migratability of old Python code, or if something breaks current Python conventions.  All that matters is that the language is elegant, and does useful things, as opposed to things that are theoretically interesting.

There has been some discussion on comp.lang.python regarding "classless" languages.  I believe that classes ( prototypes ) are useful in most programs, so rather than eliminate them, it might be better to just make their use optional.  We will use the term "prototypes" to mean the new streamlined version of classes.

Prototypes are handy for normal programs in which there are many instances for each prototype.  In that case a "two tier" program structure helps to keep things organized.  If you don't want to use prototypes, you can create an entire program by "cloning" one instance from another.

The first step to prototypes is containers.  These are useful, even if you don't get to prototypes.  Containers may be created by just making an assignment.

x.y.z = 'xyz'  # x and y are now containers

window1.plot2.xaxis.label.font.size = 12

Containers may hold any variables, including functions and other containers.

c:

    a = 'abc'

    f(x): x**2

    simulator.spectre.analyses:

        transient:

            start = '10u'

            stop  = '20u'

        noise:

            frequency = '100KHz'

   

>>> c.*

['a', 'f()', 'simulator']

>>> window1.plot2.xaxis.label.*

['font:', 'value', 'xloc', 'yloc']

>>> window1.plot2.xaxis.label.font.**

name:   Arial

size:   12

style:  Bold

Containers may inherit variables from other containers, prototypes and instances.  Conflicts are resolved using Python's MRO (Method Resolution Order).

d = __inherit__( x, window1, c )

Note 1: This is a little-used function.  Inheritance is primarily used with prototypes.  We will use a special syntax for that.

Note 2:  Inherited variables are not copied to the new container.  The new container has only references to inherited variables.  If you change an ancestor, the variables will change in all descendent containers.

Prototypes may be constructed using <- syntax. The difference between a prototype and a container is that the prototype inherits some basic variables from the built-in primitive __proto__, just as if __proto__ were added at the end of an __inherit__ list.

d <- x, window1, c

#    same as d = __inherit__( x, window1, c, __proto__ )

What are the basic variables provided by __proto__?

>>> dir(__proto__)

['__class__', '__delattr__', '__doc__', '__getattribute__',

'__hash__', '__init__', '__new__', '__reduce__',

'__reduce_ex__', '__repr__', '__setattr__', '__str__']

Normally, prototypes and instances inherit from just one prototype.

Animal <- __proto__:  # Inherit from the primitive __proto__.

    home = "Earth"

    numAnimals = 0

 

Cat <- Animal:        # Inherit from Animal.

    numCats = 0

    genus = "feline"

    __init__ ( n = 'unknown', s = 'Meow' ):

        .name  = n  # Set instance variables.

        .sound = s

        Cat.numCats += 1

    talk ():

        print "My name is %s ... %s" % ( .name, .sound )

        print "I am one of %s cats." % Cat.numCats

Instances are the same as prototypes, but they have been "initialized" by calling the __init__ function, if it exists.

cat1 <- Cat():   # Create an instance of Cat.

    home = "Tucson"

cat2 <- cat1( "Garfield", "Purr" )

Read this as -- Instance cat1 inherits from prototype Cat and is initialized with default arguments.  cat2 is cloned from cat1 and is initialized with the arguments given.  The optional colon introduces an indented block where more variables may be added or modified after creating an instance or prototype.  By convention, prototypes are capitalized and instances are lower case (or camelCase).  Arguments are optional, but the parentheses must be there to ensure that we get an instance, not another prototype.

Cloning differs from inheritance in that each new instance gets its own copy of all inherited variables.

>>> cat1.sound = 'Moo'

>>> cat2.sound

'Purr' # Had cat1 been a prototype, cat2 would now say "Moo".

Calling a function from an instance assigns that instance to a global variable __self__ then calls the function.  Any variables with leading dots are attributes of __self__.

>>> cat2.talk()

My name is Garfield ... Purr

I am one of 3 cats.

More examples of creating instances and calling functions:

a = Animal()

m = Mammal(); print "m:",; m.talk()

f = Feline(); print "f:",; f.talk()

c = Cat();    print "c:",; c.talk()

c.show()

 

>>> bf = cat1.talk # a bound function

>>> bf

<bound function Cat.talk of <__main__.Cat object at 0x00A8A570>>

>>> bf()

My name is ... Garfield

I am a feline from Earth

Mammal sound:  Purr

 

>>> uf = Cat.talk  # an unbound function

>>> __self__ = cat1; uf()  # __self__ must be set prior to call. [1]

My name is ... Garfield

I am a feline from Earth

Mammal sound:  Purr

Note 1:  Unbound functions are rarely called from the command line.  These are typically used in the body of another function, where __self__ has already been set.  So the awkward syntax above is not a big problem.