Repository: pandas-dev/pandas

Issue:
API: DatetimeIndex and PeriodIndex string representation
`DatetimeIndex` and `PeriodIndex` have inconsistent representations. Should `PeriodIndex` behaves as the same as `DatetimeIndex`, or `DatetimeIndex` should have one-liner representation in some methods?

```
import pandas and pd

# DatetimeIndex ---------------------------------------------------------------
idx = pd.DatetimeIndex(['2011-01-01', '2011-01-02', '2011-01-03'], freq='D')

print(idx)
# <class 'pandas.tseries.index.DatetimeIndex'>
# [2011-01-01, ..., 2011-01-03]
# Length: 3, Freq: D, Timezone: None

idx.__repr__()
# <class 'pandas.tseries.index.DatetimeIndex'>
# [2011-01-01, ..., 2011-01-03]
# Length: 3, Freq: D, Timezone: None

idx.__unicode__()
# <class 'pandas.tseries.index.DatetimeIndex'>
# [2011-01-01, ..., 2011-01-03]
# Length: 3, Freq: D, Timezone: None

idx.__str__()
# <class 'pandas.tseries.index.DatetimeIndex'>
# [2011-01-01, ..., 2011-01-03]
# Length: 3, Freq: D, Timezone: None

idx.__bytes__()
# <class 'pandas.tseries.index.DatetimeIndex'>
# [2011-01-01, ..., 2011-01-03]
# Length: 3, Freq: D, Timezone: None

# PeriodIndex ---------------------------------------------------------------
idx = pd.PeriodIndex(['2011-01-01', '2011-01-02', '2011-01-03'], freq='D')

print(idx)
# PeriodIndex([u'2011-01-01', u'2011-01-02', u'2011-01-03'], freq='D')

idx.__repr__()
# <class 'pandas.tseries.period.PeriodIndex'>
# freq: D
# [2011-01-01, ..., 2011-01-03]
# length: 3

idx.__unicode__()
# PeriodIndex([u'2011-01-01', u'2011-01-02', u'2011-01-03'], freq='D')

idx.__str__()
# PeriodIndex([u'2011-01-01', u'2011-01-02', u'2011-01-03'], freq='D')

idx.__bytes__()
# PeriodIndex([u'2011-01-01', u'2011-01-02', u'2011-01-03'], freq='D')
```



Discussion:

Resolving patch:
diff --git a/pandas/core/base.py b/pandas/core/base.py
--- a/pandas/core/base.py
+++ b/pandas/core/base.py
@@ -432,4 +432,36 @@ def max(self, axis=None):
             max_stamp = masked.asi8.max()
             return self._box_func(max_stamp)
 
+    @property
+    def _formatter_func(self):
+        """
+        Format function to convert value to representation
+        """
+        return str
+
+    def _format_footer(self):
+        tagline = 'Length: %d, Freq: %s, Timezone: %s'
+        return tagline % (len(self), self.freqstr, self.tz)
+
+    def __unicode__(self):
+        formatter = self._formatter_func
+        summary = str(self.__class__) + '\n'
+
+        n = len(self)
+        if n == 0:
+            pass
+        elif n == 1:
+            first = formatter(self[0])
+            summary += '[%s]\n' % first
+        elif n == 2:
+            first = formatter(self[0])
+            last = formatter(self[-1])
+            summary += '[%s, %s]\n' % (first, last)
+        else:
+            first = formatter(self[0])
+            last = formatter(self[-1])
+            summary += '[%s, ..., %s]\n' % (first, last)
+
+        summary += self._format_footer()
+        return summary
 
diff --git a/pandas/tseries/index.py b/pandas/tseries/index.py
--- a/pandas/tseries/index.py
+++ b/pandas/tseries/index.py
@@ -565,31 +565,11 @@ def _is_dates_only(self):
         from pandas.core.format import _is_dates_only
         return _is_dates_only(self.values)
 
-    def __unicode__(self):
+    @property
+    def _formatter_func(self):
         from pandas.core.format import _get_format_datetime64
-
         formatter = _get_format_datetime64(is_dates_only=self._is_dates_only)
-
-        values = self.values
-
-        freq = self.freqstr
-        summary = str(self.__class__)
-        if len(self) == 1:
-            first = formatter(values[0], tz=self.tz)
-            summary += '\n[%s]' % first
-        elif len(self) == 2:
-            first = formatter(values[0], tz=self.tz)
-            last = formatter(values[-1], tz=self.tz)
-            summary += '\n[%s, %s]' % (first, last)
-        elif len(self) > 2:
-            first = formatter(values[0], tz=self.tz)
-            last = formatter(values[-1], tz=self.tz)
-            summary += '\n[%s, ..., %s]' % (first, last)
-
-        tagline = '\nLength: %d, Freq: %s, Timezone: %s'
-        summary += tagline % (len(self), freq, self.tz)
-
-        return summary
+        return lambda x: formatter(x, tz=self.tz)
 
     def __reduce__(self):
         """Necessary for making this object picklable"""
diff --git a/pandas/tseries/period.py b/pandas/tseries/period.py
--- a/pandas/tseries/period.py
+++ b/pandas/tseries/period.py
@@ -1129,37 +1129,9 @@ def __array_finalize__(self, obj):
         self.name = getattr(obj, 'name', None)
         self._reset_identity()
 
-    def __repr__(self):
-        output = com.pprint_thing(self.__class__) + '\n'
-        output += 'freq: %s\n' % self.freq
-        n = len(self)
-        if n == 1:
-            output += '[%s]\n' % (self[0])
-        elif n == 2:
-            output += '[%s, %s]\n' % (self[0], self[-1])
-        elif n:
-            output += '[%s, ..., %s]\n' % (self[0], self[-1])
-        output += 'length: %d' % n
-        return output
-
-    def __unicode__(self):
-        output = self.__class__.__name__
-        output += u('(')
-        prefix = '' if compat.PY3 else 'u'
-        mapper = "{0}'{{0}}'".format(prefix)
-        output += '[{0}]'.format(', '.join(map(mapper.format, self)))
-        output += ", freq='{0}'".format(self.freq)
-        output += ')'
-        return output
-
-    def __bytes__(self):
-        encoding = com.get_option('display.encoding')
-        return self.__unicode__().encode(encoding, 'replace')
-
-    def __str__(self):
-        if compat.PY3:
-            return self.__unicode__()
-        return self.__bytes__()
+    def _format_footer(self):
+        tagline = 'Length: %d, Freq: %s'
+        return tagline % (len(self), self.freqstr)
 
     def take(self, indices, axis=None):
         """


Test patch:
diff --git a/pandas/tests/test_base.py b/pandas/tests/test_base.py
--- a/pandas/tests/test_base.py
+++ b/pandas/tests/test_base.py
@@ -572,6 +572,40 @@ def test_minmax(self):
             obj = DatetimeIndex([pd.NaT, pd.NaT, pd.NaT])
             self.assertTrue(pd.isnull(getattr(obj, op)()))
 
+    def test_representation(self):
+        idx1 = DatetimeIndex([], freq='D')
+        idx2 = DatetimeIndex(['2011-01-01'], freq='D')
+        idx3 = DatetimeIndex(['2011-01-01', '2011-01-02'], freq='D')
+        idx4 = DatetimeIndex(['2011-01-01', '2011-01-02', '2011-01-03'], freq='D')
+        idx5 = DatetimeIndex(['2011-01-01 09:00', '2011-01-01 10:00', '2011-01-01 11:00'],
+                             freq='H', tz='Asia/Tokyo')
+        idx6 = DatetimeIndex(['2011-01-01 09:00', '2011-01-01 10:00', pd.NaT],
+                             tz='US/Eastern')
+
+        exp1 = """<class 'pandas.tseries.index.DatetimeIndex'>
+Length: 0, Freq: D, Timezone: None"""
+        exp2 = """<class 'pandas.tseries.index.DatetimeIndex'>
+[2011-01-01]
+Length: 1, Freq: D, Timezone: None"""
+        exp3 = """<class 'pandas.tseries.index.DatetimeIndex'>
+[2011-01-01, 2011-01-02]
+Length: 2, Freq: D, Timezone: None"""
+        exp4 = """<class 'pandas.tseries.index.DatetimeIndex'>
+[2011-01-01, ..., 2011-01-03]
+Length: 3, Freq: D, Timezone: None"""
+        exp5 = """<class 'pandas.tseries.index.DatetimeIndex'>
+[2011-01-01 09:00:00+09:00, ..., 2011-01-01 11:00:00+09:00]
+Length: 3, Freq: H, Timezone: Asia/Tokyo"""
+        exp6 = """<class 'pandas.tseries.index.DatetimeIndex'>
+[2011-01-01 09:00:00-05:00, ..., NaT]
+Length: 3, Freq: None, Timezone: US/Eastern"""
+
+        for idx, expected in zip([idx1, idx2, idx3, idx4, idx5, idx6],
+                                 [exp1, exp2, exp3, exp4, exp5, exp6]):
+            for func in ['__repr__', '__unicode__', '__str__']:
+                result = getattr(idx, func)()
+                self.assertEqual(result, expected)
+
 
 class TestPeriodIndexOps(Ops):
     _allowed = '_allow_period_index_ops'
@@ -650,6 +684,52 @@ def test_minmax(self):
             self.assertEqual(result.ordinal, tslib.iNaT)
             self.assertEqual(result.freq, 'M')
 
+    def test_representation(self):
+        # GH 7601
+        idx1 = PeriodIndex([], freq='D')
+        idx2 = PeriodIndex(['2011-01-01'], freq='D')
+        idx3 = PeriodIndex(['2011-01-01', '2011-01-02'], freq='D')
+        idx4 = PeriodIndex(['2011-01-01', '2011-01-02', '2011-01-03'], freq='D')
+        idx5 = PeriodIndex(['2011', '2012', '2013'], freq='A')
+        idx6 = PeriodIndex(['2011-01-01 09:00', '2012-02-01 10:00', 'NaT'], freq='H')
+
+        idx7 = pd.period_range('2013Q1', periods=1, freq="Q")
+        idx8 = pd.period_range('2013Q1', periods=2, freq="Q")
+        idx9 = pd.period_range('2013Q1', periods=3, freq="Q")
+
+        exp1 = """<class 'pandas.tseries.period.PeriodIndex'>
+Length: 0, Freq: D"""
+        exp2 = """<class 'pandas.tseries.period.PeriodIndex'>
+[2011-01-01]
+Length: 1, Freq: D"""
+        exp3 = """<class 'pandas.tseries.period.PeriodIndex'>
+[2011-01-01, 2011-01-02]
+Length: 2, Freq: D"""
+        exp4 = """<class 'pandas.tseries.period.PeriodIndex'>
+[2011-01-01, ..., 2011-01-03]
+Length: 3, Freq: D"""
+        exp5 = """<class 'pandas.tseries.period.PeriodIndex'>
+[2011, ..., 2013]
+Length: 3, Freq: A-DEC"""
+        exp6 = """<class 'pandas.tseries.period.PeriodIndex'>
+[2011-01-01 09:00, ..., NaT]
+Length: 3, Freq: H"""
+        exp7 = """<class 'pandas.tseries.period.PeriodIndex'>
+[2013Q1]
+Length: 1, Freq: Q-DEC"""
+        exp8 = """<class 'pandas.tseries.period.PeriodIndex'>
+[2013Q1, 2013Q2]
+Length: 2, Freq: Q-DEC"""
+        exp9 = """<class 'pandas.tseries.period.PeriodIndex'>
+[2013Q1, ..., 2013Q3]
+Length: 3, Freq: Q-DEC"""
+
+        for idx, expected in zip([idx1, idx2, idx3, idx4, idx5, idx6, idx7, idx8, idx9],
+                                 [exp1, exp2, exp3, exp4, exp5, exp6, exp7, exp8, exp9]):
+            for func in ['__repr__', '__unicode__', '__str__']:
+                result = getattr(idx, func)()
+                self.assertEqual(result, expected)
+
 
 if __name__ == '__main__':
     import nose
diff --git a/pandas/tseries/tests/test_period.py b/pandas/tseries/tests/test_period.py
--- a/pandas/tseries/tests/test_period.py
+++ b/pandas/tseries/tests/test_period.py
@@ -1798,65 +1798,39 @@ def test_asfreq_nat(self):
         expected = PeriodIndex(['2011Q1', '2011Q1', 'NaT', '2011Q2'], freq='Q')
         self.assertTrue(result.equals(expected))
 
-    def test_ts_repr(self):
-        index = PeriodIndex(freq='A', start='1/1/2001', end='12/31/2010')
-        ts = Series(np.random.randn(len(index)), index=index)
-        repr(ts) # ??
-
-        val = period_range('2013Q1', periods=1, freq="Q")
-        expected = "<class 'pandas.tseries.period.PeriodIndex'>\nfreq: Q-DEC\n[2013Q1]\nlength: 1"
-        assert_equal(repr(val), expected)
-
-        val = period_range('2013Q1', periods=2, freq="Q")
-        expected = "<class 'pandas.tseries.period.PeriodIndex'>\nfreq: Q-DEC\n[2013Q1, 2013Q2]\nlength: 2"
-        assert_equal(repr(val), expected)
-
-        val = period_range('2013Q1', periods=3, freq="Q")
-        expected = "<class 'pandas.tseries.period.PeriodIndex'>\nfreq: Q-DEC\n[2013Q1, ..., 2013Q3]\nlength: 3"
-        assert_equal(repr(val), expected)
-
-    def test_period_index_unicode(self):
+    def test_period_index_length(self):
         pi = PeriodIndex(freq='A', start='1/1/2001', end='12/1/2009')
         assert_equal(len(pi), 9)
-        assert_equal(pi, eval(compat.text_type(pi)))
 
         pi = PeriodIndex(freq='Q', start='1/1/2001', end='12/1/2009')
         assert_equal(len(pi), 4 * 9)
-        assert_equal(pi, eval(compat.text_type(pi)))
 
         pi = PeriodIndex(freq='M', start='1/1/2001', end='12/1/2009')
         assert_equal(len(pi), 12 * 9)
-        assert_equal(pi, eval(compat.text_type(pi)))
 
         start = Period('02-Apr-2005', 'B')
         i1 = PeriodIndex(start=start, periods=20)
         assert_equal(len(i1), 20)
         assert_equal(i1.freq, start.freq)
         assert_equal(i1[0], start)
-        assert_equal(i1, eval(compat.text_type(i1)))
 
         end_intv = Period('2006-12-31', 'W')
         i1 = PeriodIndex(end=end_intv, periods=10)
         assert_equal(len(i1), 10)
         assert_equal(i1.freq, end_intv.freq)
         assert_equal(i1[-1], end_intv)
-        assert_equal(i1, eval(compat.text_type(i1)))
 
         end_intv = Period('2006-12-31', '1w')
         i2 = PeriodIndex(end=end_intv, periods=10)
         assert_equal(len(i1), len(i2))
         self.assertTrue((i1 == i2).all())
         assert_equal(i1.freq, i2.freq)
-        assert_equal(i1, eval(compat.text_type(i1)))
-        assert_equal(i2, eval(compat.text_type(i2)))
 
         end_intv = Period('2006-12-31', ('w', 1))
         i2 = PeriodIndex(end=end_intv, periods=10)
         assert_equal(len(i1), len(i2))
         self.assertTrue((i1 == i2).all())
         assert_equal(i1.freq, i2.freq)
-        assert_equal(i1, eval(compat.text_type(i1)))
-        assert_equal(i2, eval(compat.text_type(i2)))
 
         try:
             PeriodIndex(start=start, end=end_intv)
@@ -1866,7 +1840,6 @@ def test_period_index_unicode(self):
 
         end_intv = Period('2005-05-01', 'B')
         i1 = PeriodIndex(start=start, end=end_intv)
-        assert_equal(i1, eval(compat.text_type(i1)))
 
         try:
             PeriodIndex(start=start)
@@ -1879,12 +1852,10 @@ def test_period_index_unicode(self):
         i2 = PeriodIndex([end_intv, Period('2005-05-05', 'B')])
         assert_equal(len(i2), 2)
         assert_equal(i2[0], end_intv)
-        assert_equal(i2, eval(compat.text_type(i2)))
 
         i2 = PeriodIndex(np.array([end_intv, Period('2005-05-05', 'B')]))
         assert_equal(len(i2), 2)
         assert_equal(i2[0], end_intv)
-        assert_equal(i2, eval(compat.text_type(i2)))
 
         # Mixed freq should fail
         vals = [end_intv, Period('2006-12-31', 'w')]