Show More
@@ -1443,9 +1443,15 b' class InteractiveShell(SingletonConfigurable):' | |||
|
1443 | 1443 | continue |
|
1444 | 1444 | else: |
|
1445 | 1445 | #print 'oname_rest:', oname_rest # dbg |
|
1446 | for part in oname_rest: | |
|
1446 | for idx, part in enumerate(oname_rest): | |
|
1447 | 1447 | try: |
|
1448 | 1448 | parent = obj |
|
1449 | # The last part is looked up in a special way to avoid | |
|
1450 | # descriptor invocation as it may raise or have side | |
|
1451 | # effects. | |
|
1452 | if idx == len(oname_rest) - 1: | |
|
1453 | obj = self._getattr_property(obj, part) | |
|
1454 | else: | |
|
1449 | 1455 | obj = getattr(obj,part) |
|
1450 | 1456 | except: |
|
1451 | 1457 | # Blanket except b/c some badly implemented objects |
@@ -1486,33 +1492,48 b' class InteractiveShell(SingletonConfigurable):' | |||
|
1486 | 1492 | return {'found':found, 'obj':obj, 'namespace':ospace, |
|
1487 | 1493 | 'ismagic':ismagic, 'isalias':isalias, 'parent':parent} |
|
1488 | 1494 | |
|
1489 | def _ofind_property(self, oname, info): | |
|
1490 | """Second part of object finding, to look for property details.""" | |
|
1491 | if info.found: | |
|
1492 | # Get the docstring of the class property if it exists. | |
|
1493 | path = oname.split('.') | |
|
1494 | root = '.'.join(path[:-1]) | |
|
1495 | if info.parent is not None: | |
|
1496 | try: | |
|
1497 | target = getattr(info.parent, '__class__') | |
|
1498 | # The object belongs to a class instance. | |
|
1495 | @staticmethod | |
|
1496 | def _getattr_property(obj, attrname): | |
|
1497 | """Property-aware getattr to use in object finding. | |
|
1498 | ||
|
1499 | If attrname represents a property, return it unevaluated (in case it has | |
|
1500 | side effects or raises an error. | |
|
1501 | ||
|
1502 | """ | |
|
1503 | if not isinstance(obj, type): | |
|
1499 | 1504 |
|
|
1500 | target = getattr(target, path[-1]) | |
|
1501 | # The class defines the object. | |
|
1502 | if isinstance(target, property): | |
|
1503 | oname = root + '.__class__.' + path[-1] | |
|
1504 | info = Struct(self._ofind(oname)) | |
|
1505 | except AttributeError: pass | |
|
1506 | except AttributeError: pass | |
|
1507 | ||
|
1508 | # We return either the new info or the unmodified input if the object | |
|
1509 | # hadn't been found | |
|
1510 | return info | |
|
1505 | # `getattr(type(obj), attrname)` is not guaranteed to return | |
|
1506 | # `obj`, but does so for property: | |
|
1507 | # | |
|
1508 | # property.__get__(self, None, cls) -> self | |
|
1509 | # | |
|
1510 | # The universal alternative is to traverse the mro manually | |
|
1511 | # searching for attrname in class dicts. | |
|
1512 | attr = getattr(type(obj), attrname) | |
|
1513 | except AttributeError: | |
|
1514 | pass | |
|
1515 | else: | |
|
1516 | # This relies on the fact that data descriptors (with both | |
|
1517 | # __get__ & __set__ magic methods) take precedence over | |
|
1518 | # instance-level attributes: | |
|
1519 | # | |
|
1520 | # class A(object): | |
|
1521 | # @property | |
|
1522 | # def foobar(self): return 123 | |
|
1523 | # a = A() | |
|
1524 | # a.__dict__['foobar'] = 345 | |
|
1525 | # a.foobar # == 123 | |
|
1526 | # | |
|
1527 | # So, a property may be returned right away. | |
|
1528 | if isinstance(attr, property): | |
|
1529 | return attr | |
|
1530 | ||
|
1531 | # Nothing helped, fall back. | |
|
1532 | return getattr(obj, attrname) | |
|
1511 | 1533 | |
|
1512 | 1534 | def _object_find(self, oname, namespaces=None): |
|
1513 | 1535 | """Find an object and return a struct with info about it.""" |
|
1514 |
|
|
|
1515 | return Struct(self._ofind_property(oname, inf)) | |
|
1536 | return Struct(self._ofind(oname, namespaces)) | |
|
1516 | 1537 | |
|
1517 | 1538 | def _inspect(self, meth, oname, namespaces=None, **kw): |
|
1518 | 1539 | """Generic interface to the inspector system. |
@@ -376,6 +376,61 b' class InteractiveShellTestCase(unittest.TestCase):' | |||
|
376 | 376 | parent = None) |
|
377 | 377 | nt.assert_equal(find, info) |
|
378 | 378 | |
|
379 | def test_ofind_property_with_error(self): | |
|
380 | class A(object): | |
|
381 | @property | |
|
382 | def foo(self): | |
|
383 | raise NotImplementedError() | |
|
384 | a = A() | |
|
385 | ||
|
386 | found = ip._ofind('a.foo', [('locals', locals())]) | |
|
387 | info = dict(found=True, isalias=False, ismagic=False, | |
|
388 | namespace='locals', obj=A.foo, parent=a) | |
|
389 | nt.assert_equal(found, info) | |
|
390 | ||
|
391 | def test_ofind_multiple_attribute_lookups(self): | |
|
392 | class A(object): | |
|
393 | @property | |
|
394 | def foo(self): | |
|
395 | raise NotImplementedError() | |
|
396 | ||
|
397 | a = A() | |
|
398 | a.a = A() | |
|
399 | a.a.a = A() | |
|
400 | ||
|
401 | found = ip._ofind('a.a.a.foo', [('locals', locals())]) | |
|
402 | info = dict(found=True, isalias=False, ismagic=False, | |
|
403 | namespace='locals', obj=A.foo, parent=a.a.a) | |
|
404 | nt.assert_equal(found, info) | |
|
405 | ||
|
406 | def test_ofind_slotted_attributes(self): | |
|
407 | class A(object): | |
|
408 | __slots__ = ['foo'] | |
|
409 | def __init__(self): | |
|
410 | self.foo = 'bar' | |
|
411 | ||
|
412 | a = A() | |
|
413 | found = ip._ofind('a.foo', [('locals', locals())]) | |
|
414 | info = dict(found=True, isalias=False, ismagic=False, | |
|
415 | namespace='locals', obj=a.foo, parent=a) | |
|
416 | nt.assert_equal(found, info) | |
|
417 | ||
|
418 | found = ip._ofind('a.bar', [('locals', locals())]) | |
|
419 | info = dict(found=False, isalias=False, ismagic=False, | |
|
420 | namespace=None, obj=None, parent=a) | |
|
421 | nt.assert_equal(found, info) | |
|
422 | ||
|
423 | def test_ofind_prefers_property_to_instance_level_attribute(self): | |
|
424 | class A(object): | |
|
425 | @property | |
|
426 | def foo(self): | |
|
427 | return 'bar' | |
|
428 | a = A() | |
|
429 | a.__dict__['foo'] = 'baz' | |
|
430 | nt.assert_equal(a.foo, 'bar') | |
|
431 | found = ip._ofind('a.foo', [('locals', locals())]) | |
|
432 | nt.assert_is(found['obj'], A.foo) | |
|
433 | ||
|
379 | 434 | def test_custom_exception(self): |
|
380 | 435 | called = [] |
|
381 | 436 | def my_handler(shell, etype, value, tb, tb_offset=None): |
General Comments 0
You need to be logged in to leave comments.
Login now