It's time to write some code! Our objective: create a ``car" class that keeps track of its passengers.
>>> from peak.api import binding >>> class Car: passengers = binding.Make(dict, attrName='passengers') >>> aCar=Car() >>> print aCar.passengers {}
Let's go through this sample line by line. In the first line we import the peak.binding API. Then, we create a simple class, with one attribute, passengers. We define the attribute using the binding.Make function, which creates an attribute binding from a type or function, and an optional attribute name.
Instances of class Car will each get their own passengers attribute: a dictionary. Now, if you're new to Python, you might wonder why we don't place a dictionary directly in the class, like this:
class Car: passengers = {}
The problem is that attributes defined in a Python class body are shared, so every car will end up with the same passenger dictionary. Experienced Python programmers solve this problem by placing code in their __init__ method to initialize the structure, like so:
class Car: def __init__(self): self.passengers = {}
This works alright for simple situations, but gets more complex when you use inheritance to create subclasses of Car. It becomes necessary to call the superclass __init__ methods, and the order of initialization for different attributes can get tricky.
If you develop in Java or C++ or some other language that has instance variable initializers, you'll be happy to know that PEAK lets you have them in Python too - only better. In most static languages, variable initializers run at object creation time. So, either you create all of an object's collaborators at creation time, or you write accessor functions to hide whether the fields or attributes are initialized. This can be quite tedious in complex programs.
But PEAK's attribute bindings are lazy. They do not compute their value until they are used. If we take a new instance of our Car class, and print its dictionary before and after referencing the passengers attribute:
>>> anotherCar=Car() >>> print anotherCar.__dict__ {} >>> print anotherCar.passengers {} >>> print anotherCar.__dict__ {'passengers': {}}
We find that the object doesn't really have the attribute until we try to access it, but once we do access it, it springs into being as though it had always been there, and it acts like a normal attribute thereafter.
If you're familiar with the Eiffel programming language, you'll notice that PEAK attribute bindings are quite similar to Eiffel's once functions, except that they're for instances rather than for classes. A ``once function" is computed at most once, the first time its value is referenced. In PEAK, attribute bindings compute their value once, and then cache the result, until or unless the attribute is deleted.