実装課題の回答例

code1 (add) の例

# -*- coding: utf-8 -*-

def add(x, y):
    """
    関数 :func:`add` は引数2つをうけとり、足した値を返します。

        >>> add(1, 2)
        3

    数値以外にも文字列も足すことが出来ます。

        >>> add('foo', 'bar')
        'foobar'

    """
    return x + y

if __name__ == '__main__':
    import doctest
    doctest.testmod()


DateEx の例(シンプル版)

# -*- coding: utf-8 -*-
from datetime import date, timedelta

__all__ = ['DateEx']

class DateEx(date):
    """
    DateEx accept `+` and `-` operation. +/- means add/sub
    a day to DateEx instance::

        >>> d = DateEx(2010, 1, 1)
        >>> d + 1 == DateEx(2010, 1, 2)
        True

    otherwise, DateEx instance compareable with date instance::

        >>> d == date(2010, 1, 1)
        True

    """

    def __add__(self, num):
        """
        `+` operation can accept over 31 days::

            >>> d = DateEx(2010, 1, 1)
            >>> print d + 40
            2010-02-10

        and accept negative number for `+` operation::

            >>> print d + (-5)
            2009-12-27

        """
        if not isinstance(num, int):
            raise RuntimeError('add() accept only int')

        return date.__add__(self, timedelta(days=num))

    def __sub__(self, num):
        """
        DateEx accept - operation::

            >>> d = DateEx(2010, 1, 1)
            >>> d - 5 == date(2009, 12, 27)
            True
        """
        return self + (num * -1)


if __name__ == '__main__':
    import doctest
    doctest.testmod()

DateEx の例(高機能版)

# -*- coding: utf-8 -*-
from datetime import date, timedelta

__all__ = ['DateEx']

class DateEx(date):
    """
    DateEx accept `+` and `-` operation. +/- means add/sub
    a day to DateEx instance::

        >>> d = DateEx(2010, 1, 1)
        >>> d + 1 == DateEx(2010, 1, 2)
        True

    otherwise, DateEx instance compareable with date instance::

        >>> d == date(2010, 1, 1)
        True

    DateEx accept `>>` and `<<` operation for shift some month::

        >>> d >> 1
        DateEx(2010, 2, 1)

        >>> d << 2
        DateEx(2009, 11, 1)

    """

    def __add__(self, num):
        """
        `+` operation can accept over 31 days::

            >>> d = DateEx(2010, 1, 1)
            >>> d + 40
            DateEx(2010, 2, 10)

        and accept negative number for `+` operation::

            >>> d + (-5)
            DateEx(2009, 12, 27)

        if you send timedelta object instead of int, it's work too.

            >>> from datetime import timedelta
            >>> d + timedelta(days=5)
            DateEx(2010, 1, 6)

        """
        if isinstance(num, int):
            d = self + timedelta(days=num)
        elif isinstance(num, timedelta):
            d = date.__add__(self, num)

        return self.__class__.fromordinal(d.toordinal())

    def __sub__(self, num):
        """
        DateEx accept - operation::

            >>> d = DateEx(2010, 1, 1)
            >>> d - 5 == date(2009, 12, 27)
            True
        """
        return self + (num * -1)

    def __rshift__(self, num):
        """
        DateEx accept `>>` operation for shift some month::

            >>> d = DateEx(2010, 1, 1)
            >>> d >> 1
            DateEx(2010, 2, 1)
        """
        y = self.year
        m = self.month
        m += num
        if 1 <= m <= 12:
            pass
        else:
            m -= 1
            y += m / 12
            m = m % 12
            m += 1
        return self.replace(year=y, month=m)

    def __lshift__(self, num):
        """
        DateEx accept `<<` operation for shift some month::

            >>> d = DateEx(2010, 1, 1)
            >>> d << 2
            DateEx(2009, 11, 1)
        """
        return self >> (num * -1)

    def to(self, d):
        """
        iterate from self (as start day) to end day `d`

            >>> start = DateEx(2010, 1, 1)
            >>> end = DateEx(2010, 1, 4)
            >>> for x in start.to(end):
            ...     print x
            2010-01-01
            2010-01-02
            2010-01-03

        """
        for o in range(self.toordinal(), d.toordinal()):
            yield date.fromordinal(o)


if __name__ == '__main__':
    import doctest
    doctest.testmod()

Houganの例

# -*- coding: utf-8 -*-
from types import *
import re

__all__ = ['Hougan']

COLOR_PALETTE = {
    0: 'black',
    1: 'white',
    2: 'red',
    3: 'green',
    4: 'brue',
    5: 'yellow',
    6: 'magenda',
    7: 'cyan',
}

rowcol_finder = re.compile('^([a-zA-Z]+)(\d+)$').findall
range_finder = re.compile('^([a-zA-Z]+\d+):([a-zA-Z]+\d+)$').findall


class HouganError(Exception):
    pass


class UnknownIndexError(HouganError):
    pass

class CellIndexError(UnknownIndexError):
    pass

class RangeIndexError(UnknownIndexError):
    pass


def col_by_name(colname):
    """
    文字列で与えられた列座標を数値の座標に変換します。

        >>> col_by_name('A')
        0
        >>> col_by_name('AE')
        30

    """
    col = 0
    pow = 1
    for i in xrange(len(colname)-1, -1, -1):
        ch = colname[i]
        col += (ord(ch) - ord('A') + 1) * pow
        pow *= 26
    return col - 1


def rowcol_to_cell(row, col):
    """
    数値で与えられた座標を 'A1' のような文字列での座標に変換します。

        >>> rowcol_to_cell(0, 0)
        'A1'
        >>> rowcol_to_cell(100, 100)
        'CW101'

    """
    d = col // 26
    m = col % 26
    chr1 = ""    # Most significant character in AA1
    if d > 0:
        chr1 = chr(ord('A') + d  - 1)
    chr2 = chr(ord('A') + m)
    # Zero index to 1-index
    return chr1 + chr2 + str(row + 1)


def cell_index_by_name(name):
    """
    >>> cell_index_by_name((0,0))
    (0, 0)

    >>> cell_index_by_name([0, 0])
    (0, 0)

    >>> cell_index_by_name((-1, -1))
    Traceback (most recent call last):
    ...
    CellIndexError: (-1, -1)

    >>> cell_index_by_name((0,1,2))
    Traceback (most recent call last):
    ...
    CellIndexError: (0, 1, 2)

    >>> cell_index_by_name('A1')
    (0, 0)

    >>> cell_index_by_name('A')
    Traceback (most recent call last):
    ...
    CellIndexError: 'A'

    >>> cell_index_by_name('B10')
    (9, 1)

    >>> cell_index_by_name('c10')
    (9, 2)

    >>> cell_index_by_name('AB10')
    (9, 27)

    >>> cell_index_by_name(('A', 1))
    Traceback (most recent call last):
    ...
    CellIndexError: ('A', 1)
    """
    if type(name) in (ListType, TupleType):
        index = tuple(name)
        if len(index) != 2:
            raise CellIndexError(repr(name))

    elif isinstance(name, basestring):
        parsed = rowcol_finder(name)
        if not parsed:
            raise CellIndexError(repr(name))
        col_name, row_name = parsed[0]
        col_idx = col_by_name(col_name.upper())
        row_idx = int(row_name) - 1
        index = row_idx, col_idx

    if not all(isinstance(x, int) for x in index):
        raise CellIndexError(repr(name))

    if not all(x >= 0 for x in index):
        raise CellIndexError(repr(name))

    return index


def range_index_by_name(_range):
    """
    >>> range_index_by_name('A1:B2')
    ((0, 0), (1, 1))

    >>> range_index_by_name( ((0,10),(15,22)) )
    ((0, 10), (15, 22))

    >>> range_index_by_name('A1')
    Traceback (most recent call last):
    ...
    RangeIndexError: 'A1'

    >>> range_index_by_name('foo-bar')
    Traceback (most recent call last):
    ...
    RangeIndexError: 'foo-bar'
    """
    if type(_range) in (ListType, TupleType):
        index = tuple(_range)
        if len(index) != 2:
            raise RangeIndexError(repr(_range))

    elif isinstance(_range, basestring):
        parsed = range_finder(_range)
        if not parsed:
            raise RangeIndexError(repr(_range))
        index = parsed[0]

    return tuple(cell_index_by_name(x) for x in index)


class Cell(object):

    def __init__(self, index, value=None):
        """
        >>> Cell((0,0))
        <Cell 'A1' (0, 0), value=None, color=black at ...>
        >>> Cell('Z10')
        <Cell 'Z10' (9, 25), value=None, color=black at ...>
        """
        self.index = cell_index_by_name(index)
        self.value = None
        self.color = 0

    def __repr__(self):
        """
        >>> c = Cell((0,0))
        >>> c
        <Cell 'A1' (0, 0), value=None, color=black at ...>

        >>> c.value = 10
        >>> c
        <Cell 'A1' (0, 0), value=10, color=black at ...>

        >>> c.color = 7
        >>> c
        <Cell 'A1' (0, 0), value=10, color=cyan at ...>
        """
        class_name = self.__class__.__name__
        index = self.index
        rowcol = rowcol_to_cell(*index)
        value = self.value
        color = COLOR_PALETTE.get(self.color, 'unknown')
        addr = id(self)
        return "<%(class_name)s '%(rowcol)s' %(index)r, value=%(value)r, color=%(color)s at 0x%(addr)08x>" % locals()


class Range(object):
    """
    >>> hougan = Hougan()
    >>> r1 = Range(hougan, 'A1:B2')
    >>> r2 = hougan['A1:B2']

    >>> r1
    <Range 'A1:B2' (0, 0)-(1, 1), ...>
    >>> r2
    <Range 'A1:B2' (0, 0)-(1, 1), ...>
    >>> r1 is r2
    False
    >>> r1 == r2
    True

    >>> for cell in r1:
    ...     print cell
    <Cell 'A1' (0, 0), value=None, color=black at ...>
    <Cell 'B1' (0, 1), value=None, color=black at ...>
    <Cell 'A2' (1, 0), value=None, color=black at ...>
    <Cell 'B2' (1, 1), value=None, color=black at ...>

    >>> r1.value = 'foo'
    >>> for cell in r1:
    ...     print cell
    <Cell 'A1' (0, 0), value='foo', color=black at ...>
    <Cell 'B1' (0, 1), value='foo', color=black at ...>
    <Cell 'A2' (1, 0), value='foo', color=black at ...>
    <Cell 'B2' (1, 1), value='foo', color=black at ...>

    """

    def __init__(self, _hougan, _range):
        """
        >>> hougan = Hougan()
        >>> Range(hougan, 'A1:B2')
        <Range 'A1:B2' (0, 0)-(1, 1), ...>
        >>> Range(hougan, ((0,10),(15,22)) )
        <Range 'K1:W16' (0, 10)-(15, 22), ...>
        """
        self._hougan = _hougan
        self.index1, self.index2 = range_index_by_name(_range)

    def __repr__(self):
        class_name = self.__class__.__name__
        index1 = self.index1
        index2 = self.index2
        rowcol1 = rowcol_to_cell(*index1)
        rowcol2 = rowcol_to_cell(*index2)
        addr = id(self)
        return "<%(class_name)s '%(rowcol1)s:%(rowcol2)s' %(index1)r-%(index2)r, 0x%(addr)08x>" % locals()

    def __eq__(self, rhs):
        """
        >>> hougan = Hougan()
        >>> r1 = Range(hougan, 'A1:B2')
        >>> r2 = hougan['A1:B2']
        >>> r1 == r2
        True
        """
        if (self.index1, self.index2) == (rhs.index1, rhs.index2):
            return self._hougan is rhs._hougan
        return False

    def __iter__(self):
        """
        >>> hougan = Hougan()
        >>> for cell in Range(hougan, 'A1:B2'):
        ...     print cell
        <Cell 'A1' (0, 0), value=None, color=black at ...>
        <Cell 'B1' (0, 1), value=None, color=black at ...>
        <Cell 'A2' (1, 0), value=None, color=black at ...>
        <Cell 'B2' (1, 1), value=None, color=black at ...>
        """
        for row in range(self.index1[0], self.index2[0]+1):
            for col in range(self.index1[1], self.index2[1]+1):
                yield self._hougan[row,col]

    @property
    def value(self):
        raise NotImplementedError('Range.value setter is not implemented yet')

    @value.setter
    def value(self, value):
        """
        >>> hougan = Hougan()
        >>> hougan['A1']
        <Cell 'A1' (0, 0), value=None, color=black at ...>

        >>> r = Range(hougan, 'A1:B2')
        >>> r.value = 'foo'
        >>> hougan['A1']
        <Cell 'A1' (0, 0), value='foo', color=black at ...>
        >>> hougan['B2']
        <Cell 'B2' (1, 1), value='foo', color=black at ...>
        """
        for cell in self:
            cell.value = value
        return value

    @property
    def color(self):
        raise NotImplementedError('Range.color setter is not implemented yet')

    @color.setter
    def color(self, color):
        """
        >>> hougan = Hougan()
        >>> hougan['A1']
        <Cell 'A1' (0, 0), value=None, color=black at ...>

        >>> r = Range(hougan, 'A1:B2')
        >>> r.color = 2
        >>> hougan['A1']
        <Cell 'A1' (0, 0), value='foo', color=black at ...>
        >>> hougan['B2']
        <Cell 'B2' (1, 1), value='foo', color=black at ...>
        """
        for cell in self:
            cell.color = color
        return color

    # TODO: self.value と self.color は同じロジックなのでまとめるべきか?
    # このvalue, color のsetter/getter実装をまとめるのは早計と判断。


class Hougan(object):
    """
    Hougan は縦横のマス目のある方眼紙状のデータを簡単に扱うクラスです。

        >>> hougan = Hougan()
        >>> hougan
        <Hougan (1x1) at ...>

    方眼紙には行と列があり、それぞれが0行目、0列目からはじまります。
    方眼紙の各マス目をそれぞれセルとよび、その座標は行と列の
    インデックスで表されます。つまり一番左上は `セル(0, 0)` です。

    セルは、任意の座標(ここでは0,0)を指定してhouganから以下のように
    取り出すことが出来ます。

        >>> cell = hougan[0,0]
        >>> cell
        <Cell 'A1' (0, 0), value=None, color=black at ...>

    各セルには値(value)と背景色(color)の属性があり、値には数値や
    文字列を格納できますが初期値はNoneです。

        >>> cell.value is None
        True

        >>> cell.value = 1

    色は0から7までの値で表現され、それぞれ以下の色を意味しています。

        == ========
        値 色名
        -- --------
        0  black
        1  white
        2  red
        3  green
        4  brue
        5  yellow
        6  magenda
        7  cyan
        == ========

    colorは値で取得・設定します。

        >>> cell.color
        0

        >>> cell.color = 1


    座標1,1に値を設定するには、例えば以下のようにします。

        >>> hougan[1,1].value = 123

    Houganインスタンスは座標の最大値を保持しています。

        >>> hougan
        <Hougan (2x2) at ...>

    方眼紙はテキストで出力することができます。テキストの場合、色は
    表示されず、値が方眼紙状に表示されます。

        >>> print str(hougan)
        = ===
        1
          123
        = ===

    将来的にはcsv形式や表計算ソフトで読み込める形式の出力も実装
    する予定ですが、現在はサポートしていません。
    """

    def __init__(self):
        """
        Hougan は初期化状態で1x1のサイズのセルを持っています。
        この方眼紙のサイズは値を設定した際に自動的に拡張されます。

            >>> hougan = Hougan()
            >>> hougan
            <Hougan (1x1) at ...>
        """
        self._cell_pool = {}
        self._max_cell = (0, 0)

    def _update_max(self, index):
        r1, c1 = self._max_cell
        r2, c2 = index
        self._max_cell = (max(r1,r2), max(c1,c2))

    def __repr__(self):
        class_name = self.__class__.__name__
        row_max = self._max_cell[0] + 1
        col_max = self._max_cell[1] + 1
        addr = id(self)
        return "<%(class_name)s (%(row_max)dx%(col_max)d) at 0x%(addr)08x>" % locals()

    def __iter__(self):
        """
        >>> hougan = Hougan()
        >>> hougan[0,0].color = 6
        >>> hougan[0,1].value = 10
        >>> hougan[1,2].color = 3
        >>> hougan[1,3].value = 20
        >>> hougan[2,0].value = 30
        >>> hougan[2,0].color = 2
        >>> hougan[2,1].color = 5
        >>> for cell in hougan:
        ...     print cell
        <Cell 'A1' (0, 0), value=None, color=magenda at ...>
        <Cell 'B1' (0, 1), value=10, color=black at ...>
        <Cell 'C1' (0, 2), value=None, color=black at ...>
        <Cell 'D1' (0, 3), value=None, color=black at ...>
        <Cell 'A2' (1, 0), value=None, color=black at ...>
        <Cell 'B2' (1, 1), value=None, color=black at ...>
        <Cell 'C2' (1, 2), value=None, color=green at ...>
        <Cell 'D2' (1, 3), value=20, color=black at ...>
        <Cell 'A3' (2, 0), value=30, color=red at ...>
        <Cell 'B3' (2, 1), value=None, color=yellow at ...>
        <Cell 'C3' (2, 2), value=None, color=black at ...>
        <Cell 'D3' (2, 3), value=None, color=black at ...>
        """
        for cell in self[(0,0), self._max_cell]:
            yield cell

    def __getitem__(self, index):
        """
        >>> hougan = Hougan()

        セルを一つ取得します

        >>> hougan[0,0]
        <Cell 'A1' (0, 0), value=None, color=black at ...>
        >>> hougan['B10']
        <Cell 'B10' (9, 1), value=None, color=black at ...>
        >>> hougan['B10'].value is None
        True
        >>> hougan['B10'].color == 0
        True

        範囲オブジェクトRangeを取得します

        >>> hougan[(0,0), (1,1)]
        <Range 'A1:B2' (0, 0)-(1, 1), ...>
        >>> hougan['C1:E4']
        <Range 'C1:E4' (0, 2)-(3, 4), ...>
        >>> hougan['C1:E4'].value = 10
        >>> hougan['C1:E4'].color = 2
        """
        try:
            index = cell_index_by_name(index)
        except CellIndexError,e:
            return Range(self, index)

        if index not in self._cell_pool:
            self._cell_pool[index] = Cell(index)
            self._update_max(index)
        return self._cell_pool[index]

    def __setitem__(self, index, value):
        """
        >>> hougan = Hougan()
        >>> hougan[0,0] = 1
        Traceback (most recent call last):
        ...
        NotImplementedError: Cell is not replacable yet.
        """
        raise NotImplementedError('Cell is not replacable yet.')

    def __str__(self):
        """
        >>> hougan = Hougan()
        >>> hougan[0,0].value = 1
        >>> hougan[1,1].value = 123
        >>> print str(hougan)
        = ===
        1
          123
        = ===
        """
        def to_value(value):
            return str(value) if value is not None else ''

        col_widths = [1] * (self._max_cell[1] + 1)
        for cell in self:
            col_idx = cell.index[1]
            w = col_widths[col_idx]
            col_widths[col_idx] = max(len(to_value(cell.value)), w)

        results = []
        border = ['='*w for w in col_widths]

        results.extend(border)

        for cell in self:
            row_idx, col_idx = cell.index
            w = col_widths[col_idx]
            fmt = '%%-%ds' % w
            results.append(fmt % to_value(cell.value))

        results.extend(border)

        step = len(col_widths)
        rows = (results[s:s+step] for s in xrange(0, len(results), step))
        return '\n'.join(' '.join(row) for row in rows)


if __name__ == '__main__':
    import doctest
    doctest.testmod(
        optionflags = (doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS) )