Repository: pandas-dev/pandas

Issue:
REPR: kwds in DateOffset
Currently the signature for constructing most `DateOffset` is `(n, **kwds)`.

Optional ags are then read using `get(key)` from the `kwds` and required args using `[key]`. This is fine, but it leaves what was extracted from `kwds`  still in `kwds` leading to a redundant `str` or `repr`: 

```
Week(weekday=0)
<1 Week: kwds={'weekday': 0}, weekday=0>
```

I would recommend using `pop` instead of `get` and using `del` after reading required values.

Thoughts?



Discussion:
you can just try it and see if it breaks tests (use a separate branch); if it looks ok, then submit a PR

(maybe add a test for the new repr)

Taking a look at how `kwds` is used (e.g for `copy`) by the base class, `DateOffset`, it will be easier to cleanup how `repr` works rather than changing what is left in `kwds`.

there's prob not a lot of testing on this area of pandas....go for it!


Resolving patch:
diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py
--- a/pandas/tseries/offsets.py
+++ b/pandas/tseries/offsets.py
@@ -117,19 +117,31 @@ def __repr__(self):
         className = getattr(self, '_outputName', type(self).__name__)
         exclude = set(['n', 'inc'])
         attrs = []
-        for attr in self.__dict__:
+        for attr in sorted(self.__dict__):
             if ((attr == 'kwds' and len(self.kwds) == 0)
                     or attr.startswith('_')):
                 continue
-            if attr not in exclude:
-                attrs.append('='.join((attr, repr(getattr(self, attr)))))
+            elif attr == 'kwds':
+                kwds_new = {}
+                for key in self.kwds:
+                    if not hasattr(self, key):
+                        kwds_new[key] = self.kwds[key]
+                if len(kwds_new) > 0:
+                    attrs.append('='.join((attr, repr(kwds_new))))
+            else:            
+                if attr not in exclude:
+                    attrs.append('='.join((attr, repr(getattr(self, attr)))))
 
         if abs(self.n) != 1:
             plural = 's'
         else:
             plural = ''
+        
+        n_str = ""
+        if self.n != 1:
+            n_str = "%s * " % self.n
 
-        out = '<%s ' % self.n + className + plural
+        out = '<%s' % n_str + className + plural
         if attrs:
             out += ': ' + ', '.join(attrs)
         out += '>'
@@ -247,7 +259,7 @@ def __init__(self, n=1, **kwds):
     def rule_code(self):
         return 'B'
 
-    def __repr__(self):
+    def __repr__(self): #TODO: Figure out if this should be merged into DateOffset
         if hasattr(self, 'name') and len(self.name):
             return self.name
 
@@ -261,8 +273,12 @@ def __repr__(self):
             plural = 's'
         else:
             plural = ''
+            
+        n_str = ""
+        if self.n != 1:
+            n_str = "%s * " % self.n
 
-        out = '<%s ' % self.n + className + plural
+        out = '<%s' % n_str + className + plural
         if attrs:
             out += ': ' + ', '.join(attrs)
         out += '>'
@@ -741,7 +757,6 @@ def __init__(self, n=1, **kwds):
         self.n = n
         self.startingMonth = kwds.get('startingMonth', 3)
 
-        self.offset = BMonthEnd(3)
         self.kwds = kwds
 
     def isAnchored(self):
@@ -803,7 +818,6 @@ def __init__(self, n=1, **kwds):
         self.n = n
         self.startingMonth = kwds.get('startingMonth', 3)
 
-        self.offset = BMonthBegin(3)
         self.kwds = kwds
 
     def isAnchored(self):
@@ -855,7 +869,6 @@ def __init__(self, n=1, **kwds):
         self.n = n
         self.startingMonth = kwds.get('startingMonth', 3)
 
-        self.offset = MonthEnd(3)
         self.kwds = kwds
 
     def isAnchored(self):
@@ -894,7 +907,6 @@ def __init__(self, n=1, **kwds):
         self.n = n
         self.startingMonth = kwds.get('startingMonth', 3)
 
-        self.offset = MonthBegin(3)
         self.kwds = kwds
 
     def isAnchored(self):


Test patch:
diff --git a/pandas/tseries/tests/test_offsets.py b/pandas/tseries/tests/test_offsets.py
--- a/pandas/tseries/tests/test_offsets.py
+++ b/pandas/tseries/tests/test_offsets.py
@@ -150,10 +150,10 @@ def test_different_normalize_equals(self):
         self.assertEqual(offset, offset2)
 
     def test_repr(self):
-        assert repr(self.offset) == '<1 BusinessDay>'
-        assert repr(self.offset2) == '<2 BusinessDays>'
+        self.assertEqual(repr(self.offset), '<BusinessDay>')
+        assert repr(self.offset2) == '<2 * BusinessDays>'
 
-        expected = '<1 BusinessDay: offset=datetime.timedelta(1)>'
+        expected = '<BusinessDay: offset=datetime.timedelta(1)>'
         assert repr(self.offset + timedelta(1)) == expected
 
     def test_with_offset(self):
@@ -324,10 +324,10 @@ def test_different_normalize_equals(self):
         self.assertEqual(offset, offset2)
 
     def test_repr(self):
-        assert repr(self.offset) == '<1 CustomBusinessDay>'
-        assert repr(self.offset2) == '<2 CustomBusinessDays>'
+        assert repr(self.offset) == '<CustomBusinessDay>'
+        assert repr(self.offset2) == '<2 * CustomBusinessDays>'
 
-        expected = '<1 BusinessDay: offset=datetime.timedelta(1)>'
+        expected = '<BusinessDay: offset=datetime.timedelta(1)>'
         assert repr(self.offset + timedelta(1)) == expected
 
     def test_with_offset(self):
@@ -526,6 +526,11 @@ def assertOnOffset(offset, date, expected):
 
 
 class TestWeek(unittest.TestCase):
+    def test_repr(self):
+        self.assertEqual(repr(Week(weekday=0)), "<Week: weekday=0>")
+        self.assertEqual(repr(Week(n=-1, weekday=0)), "<-1 * Week: weekday=0>")
+        self.assertEqual(repr(Week(n=-2, weekday=0)), "<-2 * Weeks: weekday=0>")
+        
     def test_corner(self):
         self.assertRaises(ValueError, Week, weekday=7)
         assertRaisesRegexp(ValueError, "Day must be", Week, weekday=-1)
@@ -598,6 +603,9 @@ def test_constructor(self):
         assertRaisesRegexp(ValueError, "^Day", WeekOfMonth, n=1, week=0, weekday=-1)
         assertRaisesRegexp(ValueError, "^Day", WeekOfMonth, n=1, week=0, weekday=7)
 
+    def test_repr(self):
+        self.assertEqual(repr(WeekOfMonth(weekday=1,week=2)), "<WeekOfMonth: week=2, weekday=1>")
+
     def test_offset(self):
         date1 = datetime(2011, 1, 4)  # 1st Tuesday of Month
         date2 = datetime(2011, 1, 11)  # 2nd Tuesday of Month
@@ -895,6 +903,11 @@ def test_onOffset(self):
 
 
 class TestBQuarterBegin(unittest.TestCase):
+    
+    def test_repr(self):
+        self.assertEqual(repr(BQuarterBegin()),"<BusinessQuarterBegin: startingMonth=3>")
+        self.assertEqual(repr(BQuarterBegin(startingMonth=3)), "<BusinessQuarterBegin: startingMonth=3>")
+        self.assertEqual(repr(BQuarterBegin(startingMonth=1)), "<BusinessQuarterBegin: startingMonth=1>")
 
     def test_isAnchored(self):
         self.assert_(BQuarterBegin(startingMonth=1).isAnchored())
@@ -981,6 +994,11 @@ def test_offset(self):
 
 class TestBQuarterEnd(unittest.TestCase):
 
+    def test_repr(self):
+        self.assertEqual(repr(BQuarterEnd()),"<BusinessQuarterEnd: startingMonth=3>")
+        self.assertEqual(repr(BQuarterEnd(startingMonth=3)), "<BusinessQuarterEnd: startingMonth=3>")
+        self.assertEqual(repr(BQuarterEnd(startingMonth=1)), "<BusinessQuarterEnd: startingMonth=1>")
+        
     def test_isAnchored(self):
         self.assert_(BQuarterEnd(startingMonth=1).isAnchored())
         self.assert_(BQuarterEnd().isAnchored())
@@ -1083,6 +1101,11 @@ def test_onOffset(self):
 
 
 class TestQuarterBegin(unittest.TestCase):
+    def test_repr(self):
+        self.assertEqual(repr(QuarterBegin()), "<QuarterBegin: startingMonth=3>")
+        self.assertEqual(repr(QuarterBegin(startingMonth=3)), "<QuarterBegin: startingMonth=3>")
+        self.assertEqual(repr(QuarterBegin(startingMonth=1)),"<QuarterBegin: startingMonth=1>")
+            
     def test_isAnchored(self):
         self.assert_(QuarterBegin(startingMonth=1).isAnchored())
         self.assert_(QuarterBegin().isAnchored())
@@ -1152,7 +1175,11 @@ def test_offset(self):
 
 
 class TestQuarterEnd(unittest.TestCase):
-
+    def test_repr(self):
+        self.assertEqual(repr(QuarterEnd()), "<QuarterEnd: startingMonth=3>")
+        self.assertEqual(repr(QuarterEnd(startingMonth=3)), "<QuarterEnd: startingMonth=3>")
+        self.assertEqual(repr(QuarterEnd(startingMonth=1)), "<QuarterEnd: startingMonth=1>")
+    
     def test_isAnchored(self):
         self.assert_(QuarterEnd(startingMonth=1).isAnchored())
         self.assert_(QuarterEnd().isAnchored())