Objects and Classes in Python and Sage -- Misc talks, demos, tutorials, ... using Sage v4.4.1 system:sage

Objects and Classes in Python and Sage

Object oriented programming paradigm

Fundamental ideas:

  1. Any thing of the real (or mathematical) world which needs to be manipulated by the computer is modeled by an object.
  2. Each object is an instance of some class.

object
a portion of memory which contains the information needed to model the real world thing.
class
defines the data structure used to store the object together with its behavior.

Let’s start with some examples: We consider the vector space over \QQ whose basis is indexed by permutations, and a particular element in it:

{{{id=0| F = CombinatorialFreeModule(QQ, Permutations()) el = 3*F([1,3,2])+ F([1,2,3]) el /// B[[1, 2, 3]] + 3*B[[1, 3, 2]] }}}

On can get the class of the element el by:

{{{id=1| type(el) /// }}}

The data are stored in so called attributes:

{{{id=2| el._monomial_coefficients /// {[1, 2, 3]: 1, [1, 3, 2]: 3} }}}

Modifying the attribute modifies the objects:

{{{id=3| el._monomial_coefficients[Permutation([3,2,1])] = 1/2 el /// B[[1, 2, 3]] + 3*B[[1, 3, 2]] + 1/2*B[[3, 2, 1]] }}}

Warning

as a user, you are not supposed to do that by yourself.

As an element of a vector space el has a particular behavior:

{{{id=4| 2*el /// 2*B[[1, 2, 3]] + 6*B[[1, 3, 2]] + B[[3, 2, 1]] }}} {{{id=5| el.support() /// [[1, 2, 3], [1, 3, 2], [3, 2, 1]] }}} {{{id=6| el.coefficient([1, 2, 3]) /// 1 }}}

The behavior is defined through methods (support, coefficient). Note that this is true, even for equality, printing or mathematical operations:

{{{id=7| el.__eq__(F([1,3,2])) /// False }}} {{{id=8| el.__repr__() /// 'B[[1, 2, 3]] + 3*B[[1, 3, 2]] + 1/2*B[[3, 2, 1]]' }}} {{{id=9| el.__mul__(2) /// 2*B[[1, 2, 3]] + 6*B[[1, 3, 2]] + B[[3, 2, 1]] }}}

Some particular actions allows to modify the data structure of el:

{{{id=10| el.rename("bla") el /// bla }}}

Note

The class is stored in a particular attribute called __class__ the normal attribute are stored in a dictionary called __dict__:

{{{id=11| el.__class__ /// }}} {{{id=12| el.__dict__ /// {'_monomial_coefficients': {[3, 2, 1]: 1/2, [1, 2, 3]: 1, [1, 3, 2]: 3}, '__custom_name': 'bla'} }}}

Lots of sage objects are not Python objects but compiled Cython objects. Python sees them as builtin objects and you don’t have access to the data structure. Examples include integers and permutation group elements:

{{{id=13| e = Integer(9) type(e) /// }}} {{{id=14| e.__dict__ /// }}} {{{id=15| e.__dict__.keys() /// ['__module__', '_reduction', '__doc__', '_sage_src_lines_'] }}} {{{id=16| id4 = SymmetricGroup(4).one() type(id4) /// }}} {{{id=17| id4.__dict__ /// }}}

Note

Each objects corresponds to a portion of memory called its identity in python. You can get the identity using id:

{{{id=18| id(el) # random /// 139813642977744 }}} {{{id=19| el1 = el; id(el1) == id(el) /// True }}}

Summary

To define some object, you first have to write a class. The class will defines the methods and the attributes of the object.

method
particular kind of function associated with an object used to get information about the object or to manipulate it.
attribute
variables where the info about the object are stored;

An example: glass of beverage in a restaurant

Let’s write a small class about glasses in a restaurant:

{{{id=20| class Glass(object): def __init__(self, size): assert size > 0 self._size = float(size) self._content = float(0.0) def __repr__(self): if self._content == 0.0: return "An empty glass of size %s"%(self._size) else: return "A glass of size %s cl containing %s cl of water"%( self._size, self._content) def fill(self): self._content = self._size def empty(self): self._content = float(0.0) /// }}}

Let’s create a small glass:

{{{id=21| myGlass = Glass(10); myGlass /// An empty glass of size 10.0 }}} {{{id=22| myGlass.fill(); myGlass /// A glass of size 10.0 cl containing 10.0 cl of water }}} {{{id=23| myGlass.empty(); myGlass /// An empty glass of size 10.0 }}}

Some comments:

  1. The method __init__ is used to initialize the object, it is used by the so called constructor of the class that is executed when calling Glass(10).
  2. The method __repr__ is supposed to return a string which is used to print the object.

Note

  • most of the time, the user should not change directly the attribute of an object. Those attributes are called private. Since there is no mechanism to ensure privacy in python, the usage is to prefix the name by an underscore.
  • as a consequence attribute access is only made through methods.
  • methods which are only for internal use are also prefixed with an underscore.

Exercises

  1. add a method is_empty which returns true if a glass is empty.
  2. define a method drink with a parameter amount which allows to partially drink the water in the glass. Raise an error if one asks to drink more water than there is in the glass or a negative amount of water.
  3. Allows the glass to be filled with something other than water. The method fill should accept a parameter beverage. The beverage is stored in an attribute _beverage. Update the method __repr__ accordingly.
  4. Add an attribute _clean and methods is_clean and wash. At the creation a glass is clean, as soon as it’s filled it becomes dirty, and must be washed to become clean again.
  5. Test everything.
  6. Make sure that everything is tested.
  7. Test everything again.

Inheritance

The problem: object of different classes may have a common behavior.

For example, if one wants to deal now with different dishes (forks, spoons ...) then there is common behavior (becoming dirty and being washed). So the different classes associated to the different kinds of dishes should have the same clean, is_clean and wash methods. But copying and pasting code is bad and evil ! This is done by having a base class which factorizes the common behavior:

{{{id=24| class AbstractDish(object): def __init__(self): self._clean = True def is_clean(self): return self._clean def state(self): return "clean" if self.is_clean() else "dirty" def __repr__(self): return "An unspecified %s dish"%self.state() def _make_dirty(self): self._clean = False def wash(self): self._clean = True /// }}}

Now one can reuse this behavior within a class Spoon:

{{{id=25| class Spoon(AbstractDish): def __repr__(self): return "A %s spoon"%self.state() def eat_with(self): self._make_dirty() /// }}}

Let’s tests it:

{{{id=26| s = Spoon(); s /// A clean spoon }}} {{{id=27| s.is_clean() /// True }}} {{{id=28| s.eat_with(); s /// A dirty spoon }}} {{{id=29| s.is_clean() /// False }}} {{{id=30| s.wash(); s /// A clean spoon }}}

Summary

  1. Any class can reuse the behavior of another class. One says that the subclass inherits from the superclass or that it derives from it.

  2. Any instance of the subclass is also an instance its superclass

    {{{id=31| type(s) /// }}} {{{id=32| isinstance(s, Spoon) /// True }}} {{{id=33| isinstance(s, AbstractDish) /// True }}}
  3. If a subclass redefines a method, then it replaces the former one. One says that the subclass overloads the method. One can nevertheless explicitly call the hidden superclass method.

    {{{id=34| s.__repr__() /// 'A clean spoon' }}} {{{id=35| Spoon.__repr__(s) /// 'A clean spoon' }}} {{{id=36| AbstractDish.__repr__(s) /// 'An unspecified clean dish' }}}
  4. Sometimes one wants to call an overloaded method without knowing in which class it is defined. On use the super operator

    {{{id=37| super(Spoon, s).__repr__() /// 'An unspecified clean dish' }}}

    A very common usage of this construct is to call the __init__ method of the super classes:

    {{{id=38| class Spoon(AbstractDish): def __init__(self): print "Building a spoon" super(Spoon, self).__init__() def __repr__(self): return "A %s spoon"%self.state() def eat_with(self): self.make_dirty() s = Spoon() /// Building a spoon }}} {{{id=39| s /// A clean spoon }}}

Exercises

  1. Modify the class Glasses so that it inherits from Dish.
  2. Write a class Plate whose instance can contain any meals together with a class Fork. Avoid at much as possible code duplication (hint: you can write a factorized class ContainerDish).
  3. Test everything.

Sage specifics about classes

  • Any classes for mathematical objects in Sage should inherits from SageObject.
  • Printing should be done through _repr_ instead of __repr__ to allows for renaming.
  • More generally, Sage specific special methods are usually named _meth_ rather than __meth__. For example, lots of classes implement _hash_ which is used and cached by __hash__.

Advanced uses of classes: Factories

The problem: the actual class of an object sometimes depend on the parameters given to the constructor.

TODO !!!!!!

That all folks !