| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -231,65 +231,107 @@ class attrgetter: | |||
| 231 | 231 | After h = attrgetter('name.first', 'name.last'), the call h(r) returns | |
| 232 | 232 | (r.name.first, r.name.last). | |
| 233 | 233 | """ | |
| 234 | + __slots__ = ('_attrs', '_call') | ||
| 235 | + | ||
| 234 | 236 | def __init__(self, attr, *attrs): | |
| 235 | 237 | if not attrs: | |
| 236 | 238 | if not isinstance(attr, str): | |
| 237 | 239 | raise TypeError('attribute name must be a string') | |
| 240 | + self._attrs = (attr,) | ||
| 238 | 241 | names = attr.split('.') | |
| 239 | 242 | def func(obj): | |
| 240 | 243 | for name in names: | |
| 241 | 244 | obj = getattr(obj, name) | |
| 242 | 245 | return obj | |
| 243 | 246 | self._call = func | |
| 244 | 247 | else: | |
| 245 | - getters = tuple(map(attrgetter, (attr,) + attrs)) | ||
| 248 | + self._attrs = (attr,) + attrs | ||
| 249 | + getters = tuple(map(attrgetter, self._attrs)) | ||
| 246 | 250 | def func(obj): | |
| 247 | 251 | return tuple(getter(obj) for getter in getters) | |
| 248 | 252 | self._call = func | |
| 249 | 253 | ||
| 250 | 254 | def __call__(self, obj): | |
| 251 | 255 | return self._call(obj) | |
| 252 | 256 | ||
| 257 | + def __repr__(self): | ||
| 258 | + return '%s.%s(%s)' % (self.__class__.__module__, | ||
| 259 | + self.__class__.__qualname__, | ||
| 260 | + ', '.join(map(repr, self._attrs))) | ||
| 261 | + | ||
| 262 | + def __reduce__(self): | ||
| 263 | + return self.__class__, self._attrs | ||
| 264 | + | ||
| 253 | 265 | class itemgetter: | |
| 254 | 266 | """ | |
| 255 | 267 | Return a callable object that fetches the given item(s) from its operand. | |
| 256 | 268 | After f = itemgetter(2), the call f(r) returns r[2]. | |
| 257 | 269 | After g = itemgetter(2, 5, 3), the call g(r) returns (r[2], r[5], r[3]) | |
| 258 | 270 | """ | |
| 271 | + __slots__ = ('_items', '_call') | ||
| 272 | + | ||
| 259 | 273 | def __init__(self, item, *items): | |
| 260 | 274 | if not items: | |
| 275 | + self._items = (item,) | ||
| 261 | 276 | def func(obj): | |
| 262 | 277 | return obj[item] | |
| 263 | 278 | self._call = func | |
| 264 | 279 | else: | |
| 265 | - items = (item,) + items | ||
| 280 | + self._items = items = (item,) + items | ||
| 266 | 281 | def func(obj): | |
| 267 | 282 | return tuple(obj[i] for i in items) | |
| 268 | 283 | self._call = func | |
| 269 | 284 | ||
| 270 | 285 | def __call__(self, obj): | |
| 271 | 286 | return self._call(obj) | |
| 272 | 287 | ||
| 288 | + def __repr__(self): | ||
| 289 | + return '%s.%s(%s)' % (self.__class__.__module__, | ||
| 290 | + self.__class__.__name__, | ||
| 291 | + ', '.join(map(repr, self._items))) | ||
| 292 | + | ||
| 293 | + def __reduce__(self): | ||
| 294 | + return self.__class__, self._items | ||
| 295 | + | ||
| 273 | 296 | class methodcaller: | |
| 274 | 297 | """ | |
| 275 | 298 | Return a callable object that calls the given method on its operand. | |
| 276 | 299 | After f = methodcaller('name'), the call f(r) returns r.name(). | |
| 277 | 300 | After g = methodcaller('name', 'date', foo=1), the call g(r) returns | |
| 278 | 301 | r.name('date', foo=1). | |
| 279 | 302 | """ | |
| 303 | + __slots__ = ('_name', '_args', '_kwargs') | ||
| 280 | 304 | ||
| 281 | 305 | def __init__(*args, **kwargs): | |
| 282 | 306 | if len(args) < 2: | |
| 283 | 307 | msg = "methodcaller needs at least one argument, the method name" | |
| 284 | 308 | raise TypeError(msg) | |
| 285 | 309 | self = args[0] | |
| 286 | 310 | self._name = args[1] | |
| 311 | + if not isinstance(self._name, str): | ||
| 312 | + raise TypeError('method name must be a string') | ||
| 287 | 313 | self._args = args[2:] | |
| 288 | 314 | self._kwargs = kwargs | |
| 289 | 315 | ||
| 290 | 316 | def __call__(self, obj): | |
| 291 | 317 | return getattr(obj, self._name)(*self._args, **self._kwargs) | |
| 292 | 318 | ||
| 319 | + def __repr__(self): | ||
| 320 | + args = [repr(self._name)] | ||
| 321 | + args.extend(map(repr, self._args)) | ||
| 322 | + args.extend('%s=%r' % (k, v) for k, v in self._kwargs.items()) | ||
| 323 | + return '%s.%s(%s)' % (self.__class__.__module__, | ||
| 324 | + self.__class__.__name__, | ||
| 325 | + ', '.join(args)) | ||
| 326 | + | ||
| 327 | + def __reduce__(self): | ||
| 328 | + if not self._kwargs: | ||
| 329 | + return self.__class__, (self._name,) + self._args | ||
| 330 | + else: | ||
| 331 | + from functools import partial | ||
| 332 | + return partial(self.__class__, self._name, **self._kwargs), self._args | ||
| 333 | + | ||
| 334 | + | ||
| 293 | 335 | # In-place Operations *********************************************************# | |
| 294 | 336 | ||
| 295 | 337 | def iadd(a, b): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,4 +1,6 @@ | |||
| 1 | 1 | import unittest | |
| 2 | + import pickle | ||
| 3 | + import sys | ||
| 2 | 4 | ||
| 3 | 5 | from test import support | |
| 4 | 6 | ||
@@ -35,6 +37,9 @@ def __rmul__(self, other): | |||
| 35 | 37 | ||
| 36 | 38 | ||
| 37 | 39 | class OperatorTestCase: | |
| 40 | + def setUp(self): | ||
| 41 | + sys.modules['operator'] = self.module | ||
| 42 | + | ||
| 38 | 43 | def test_lt(self): | |
| 39 | 44 | operator = self.module | |
| 40 | 45 | self.assertRaises(TypeError, operator.lt) | |
@@ -396,6 +401,7 @@ def __getitem__(self, name): | |||
| 396 | 401 | def test_methodcaller(self): | |
| 397 | 402 | operator = self.module | |
| 398 | 403 | self.assertRaises(TypeError, operator.methodcaller) | |
| 404 | + self.assertRaises(TypeError, operator.methodcaller, 12) | ||
| 399 | 405 | class A: | |
| 400 | 406 | def foo(self, *args, **kwds): | |
| 401 | 407 | return args[0] + args[1] | |
@@ -491,5 +497,108 @@ class PyOperatorTestCase(OperatorTestCase, unittest.TestCase): | |||
| 491 | 497 | class COperatorTestCase(OperatorTestCase, unittest.TestCase): | |
| 492 | 498 | module = c_operator | |
| 493 | 499 | ||
| 500 | + | ||
| 501 | + class OperatorPickleTestCase: | ||
| 502 | + def copy(self, obj, proto): | ||
| 503 | + with support.swap_item(sys.modules, 'operator', self.module): | ||
| 504 | + pickled = pickle.dumps(obj, proto) | ||
| 505 | + with support.swap_item(sys.modules, 'operator', self.module2): | ||
| 506 | + return pickle.loads(pickled) | ||
| 507 | + | ||
| 508 | + def test_attrgetter(self): | ||
| 509 | + attrgetter = self.module.attrgetter | ||
| 510 | + attrgetter = self.module.attrgetter | ||
| 511 | + class A: | ||
| 512 | + pass | ||
| 513 | + a = A() | ||
| 514 | + a.x = 'X' | ||
| 515 | + a.y = 'Y' | ||
| 516 | + a.z = 'Z' | ||
| 517 | + a.t = A() | ||
| 518 | + a.t.u = A() | ||
| 519 | + a.t.u.v = 'V' | ||
| 520 | + for proto in range(pickle.HIGHEST_PROTOCOL + 1): | ||
| 521 | + with self.subTest(proto=proto): | ||
| 522 | + f = attrgetter('x') | ||
| 523 | + f2 = self.copy(f, proto) | ||
| 524 | + self.assertEqual(repr(f2), repr(f)) | ||
| 525 | + self.assertEqual(f2(a), f(a)) | ||
| 526 | + # multiple gets | ||
| 527 | + f = attrgetter('x', 'y', 'z') | ||
| 528 | + f2 = self.copy(f, proto) | ||
| 529 | + self.assertEqual(repr(f2), repr(f)) | ||
| 530 | + self.assertEqual(f2(a), f(a)) | ||
| 531 | + # recursive gets | ||
| 532 | + f = attrgetter('t.u.v') | ||
| 533 | + f2 = self.copy(f, proto) | ||
| 534 | + self.assertEqual(repr(f2), repr(f)) | ||
| 535 | + self.assertEqual(f2(a), f(a)) | ||
| 536 | + | ||
| 537 | + def test_itemgetter(self): | ||
| 538 | + itemgetter = self.module.itemgetter | ||
| 539 | + a = 'ABCDE' | ||
| 540 | + for proto in range(pickle.HIGHEST_PROTOCOL + 1): | ||
| 541 | + with self.subTest(proto=proto): | ||
| 542 | + f = itemgetter(2) | ||
| 543 | + f2 = self.copy(f, proto) | ||
| 544 | + self.assertEqual(repr(f2), repr(f)) | ||
| 545 | + self.assertEqual(f2(a), f(a)) | ||
| 546 | + # multiple gets | ||
| 547 | + f = itemgetter(2, 0, 4) | ||
| 548 | + f2 = self.copy(f, proto) | ||
| 549 | + self.assertEqual(repr(f2), repr(f)) | ||
| 550 | + self.assertEqual(f2(a), f(a)) | ||
| 551 | + | ||
| 552 | + def test_methodcaller(self): | ||
| 553 | + methodcaller = self.module.methodcaller | ||
| 554 | + class A: | ||
| 555 | + def foo(self, *args, **kwds): | ||
| 556 | + return args[0] + args[1] | ||
| 557 | + def bar(self, f=42): | ||
| 558 | + return f | ||
| 559 | + def baz(*args, **kwds): | ||
| 560 | + return kwds['name'], kwds['self'] | ||
| 561 | + a = A() | ||
| 562 | + for proto in range(pickle.HIGHEST_PROTOCOL + 1): | ||
| 563 | + with self.subTest(proto=proto): | ||
| 564 | + f = methodcaller('bar') | ||
| 565 | + f2 = self.copy(f, proto) | ||
| 566 | + self.assertEqual(repr(f2), repr(f)) | ||
| 567 | + self.assertEqual(f2(a), f(a)) | ||
| 568 | + # positional args | ||
| 569 | + f = methodcaller('foo', 1, 2) | ||
| 570 | + f2 = self.copy(f, proto) | ||
| 571 | + self.assertEqual(repr(f2), repr(f)) | ||
| 572 | + self.assertEqual(f2(a), f(a)) | ||
| 573 | + # keyword args | ||
| 574 | + f = methodcaller('bar', f=5) | ||
| 575 | + f2 = self.copy(f, proto) | ||
| 576 | + self.assertEqual(repr(f2), repr(f)) | ||
| 577 | + self.assertEqual(f2(a), f(a)) | ||
| 578 | + f = methodcaller('baz', self='eggs', name='spam') | ||
| 579 | + f2 = self.copy(f, proto) | ||
| 580 | + # Can't test repr consistently with multiple keyword args | ||
| 581 | + self.assertEqual(f2(a), f(a)) | ||
| 582 | + | ||
| 583 | + class PyPyOperatorPickleTestCase(OperatorPickleTestCase, unittest.TestCase): | ||
| 584 | + module = py_operator | ||
| 585 | + module2 = py_operator | ||
| 586 | + | ||
| 587 | + @unittest.skipUnless(c_operator, 'requires _operator') | ||
| 588 | + class PyCOperatorPickleTestCase(OperatorPickleTestCase, unittest.TestCase): | ||
| 589 | + module = py_operator | ||
| 590 | + module2 = c_operator | ||
| 591 | + | ||
| 592 | + @unittest.skipUnless(c_operator, 'requires _operator') | ||
| 593 | + class CPyOperatorPickleTestCase(OperatorPickleTestCase, unittest.TestCase): | ||
| 594 | + module = c_operator | ||
| 595 | + module2 = py_operator | ||
| 596 | + | ||
| 597 | + @unittest.skipUnless(c_operator, 'requires _operator') | ||
| 598 | + class CCOperatorPickleTestCase(OperatorPickleTestCase, unittest.TestCase): | ||
| 599 | + module = c_operator | ||
| 600 | + module2 = c_operator | ||
| 601 | + | ||
| 602 | + | ||
| 494 | 603 | if __name__ == "__main__": | |
| 495 | 604 | unittest.main() | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -52,6 +52,10 @@ Core and Builtins | |||
| 52 | 52 | Library | |
| 53 | 53 | ------- | |
| 54 | 54 | ||
| 55 | + - Issue #22955: attrgetter, itemgetter and methodcaller objects in the operator | ||
| 56 | + module now support pickling. Added readable and evaluable repr for these | ||
| 57 | + objects. Based on patch by Josh Rosenberg. | ||
| 58 | + | ||
| 55 | 59 | - Issue #22107: tempfile.gettempdir() and tempfile.mkdtemp() now try again | |
| 56 | 60 | when a directory with the chosen name already exists on Windows as well as | |
| 57 | 61 | on Unix. tempfile.mkstemp() now fails early if parent directory is not | |
| Back | FazBrowse Home | New Git URL |
0 commit comments