Programmation python courante
================================

.. _namespaces:

les espaces de nommage
-----------------------

Packages et modules::

    package/
        __init__.py
        module1.py
        subpackage/
            __init__.py
            module2.py

A utilser pour organiser votre projet
Permet de minimiser les risques de conflits de nome
Permet de diminuer les entrées dans le :envvar:`PYTHONPATH`

::

    import package.module1
    from package.subpackage import module2
    from package.subpackage.module2 import name

- le fichier `__init__.py`
- `reload(module)` au prompt

- dangereux : l'import "*", utiliser l'attribut spécial `__all__` pour l'import
  sélectif

::

    from os import *

lance un module en tant que script :

::

    if __name__ == "__main__":
        main()

Organisation modulaire

- construire des composants élémentaires
- combiner ces composants
- utiliser une structure pyramidale : les composants sont les éléments de
  composants plus complexes.


- découplage de l'ensemble en composants indépendants (gros programmes réalisables)
- donner de la structure (rendre les gros programmes compréhensibles)
- spécifier les liens entre les composants (rendre les programmes maintenables)
- identifier les sous-composants indépendants (rendre les programmes réutilisables)
- forcer l'abstraction (augmenter la sureté du programme)

Les méthodes spéciales
-----------------------

méthodes spéciales correspondants aux interfaces des types de bases :

.. function:: __init__(self, *args, **kwargs)
     le constructeur de l'instance d'objet

.. function:: __add__(self, other)

    correspond à la notation `+`

exemple :

.. literalinclude:: snippets/specialmethods.py

>>> from specialmethods import *
>>> z = Zone("titi", 10)
>>> z2 = Zone("tutu", 40)
>>> z > z2
False
>>> z + z2
<specialmethods.Zone object at 0x7f02d95fb190>
>>> z3 = z + z2
>>> z3.name
'tititutu'
>>> z3.level
50
>>>

Attributs et accesseurs
---------------------------

python est un langage à attributs, c'est-à-dire que le protocole d'accès
aux attributs est règlable.

>>> class C(object):
...   classattr = "a class attribute"
...
>>> cobj = C()
>>> cobj.classattr
'a class attribute'
>>> cobj.insattr = "an instance attribute"
>>> cobj.insattr
'an instance attribute'
>>> C.__dict__['classattr']
'a class attribute'
>>> cobj.__dict__['insattr']
'an instance attribute'

les attributs ne sont pas systématiquement encapsulées en python.

pour contrôler l'accès aux attributs, on utilise les méthodes spéciales::

  __getattr__(self, name)

  __setattr__(self, name, value)

    class AnObject(object):
        ......
        def __setattr__(self, name, val):
            if name == 'src': #do something
            # this will assign the real object.name,
            #despite __setattr__
            self.__dict__[name]=val
        def __getattr__(self, name):
            # ...
    try:
        func = getattr(obj, "method")
    except AttributeError:
        ... deal with missing method ...
    else:
        result = func(args)

    func = getattr(obj, "method", None)
    if callable(func):
        func(args)

- un **attribut** spécial : `__slots__`

permet de fixer les attributs possibles d'une classe

::

    >>> class Bar(object):
    ...    __slots__ = ("a","b","c")
    ...
    >>> b = Bar()
    >>> b.a = "toto"
    >>> b.a
    'toto'
    >>> b.d = "titi"
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    AttributeError: 'Bar' object has no attribute 'd'

les slots
~~~~~~~~~~~

.. important:: l'encapsulation n'est pas une condition de base de la programmation
               par objects, surtout que le contrôle nuit à l'agilité.

>>> class Point(object):
...     __slots__ = 'x', 'y'
...
>>> p = Point()
>>> p.x = 2
>>> p.y = 3
>>> p.z = 4
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'z'
>>>

- notation `|` et notation `>`

::

    class Test:
        nombre = 1
        def __or__(self, other):
            return self.nombre + other.nombre

        def __lshift__(self, other):
            self.nombre = self.nombre + other.nombre

    t1 = Test()
    t2 = Test()
    t2.nombre = 2
    print t1 | t2
    t1 << t2
    print t1.nombre