Show More
@@ -1443,9 +1443,15 b' class InteractiveShell(SingletonConfigurable):' | |||||
1443 | continue |
|
1443 | continue | |
1444 | else: |
|
1444 | else: | |
1445 | #print 'oname_rest:', oname_rest # dbg |
|
1445 | #print 'oname_rest:', oname_rest # dbg | |
1446 | for part in oname_rest: |
|
1446 | for idx, part in enumerate(oname_rest): | |
1447 | try: |
|
1447 | try: | |
1448 | parent = obj |
|
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 | obj = getattr(obj,part) |
|
1455 | obj = getattr(obj, part) | |
1450 | except: |
|
1456 | except: | |
1451 | # Blanket except b/c some badly implemented objects |
|
1457 | # Blanket except b/c some badly implemented objects | |
@@ -1486,33 +1492,48 b' class InteractiveShell(SingletonConfigurable):' | |||||
1486 | return {'found':found, 'obj':obj, 'namespace':ospace, |
|
1492 | return {'found':found, 'obj':obj, 'namespace':ospace, | |
1487 | 'ismagic':ismagic, 'isalias':isalias, 'parent':parent} |
|
1493 | 'ismagic':ismagic, 'isalias':isalias, 'parent':parent} | |
1488 |
|
1494 | |||
1489 | def _ofind_property(self, oname, info): |
|
1495 | @staticmethod | |
1490 | """Second part of object finding, to look for property details.""" |
|
1496 | def _getattr_property(obj, attrname): | |
1491 | if info.found: |
|
1497 | """Property-aware getattr to use in object finding. | |
1492 | # Get the docstring of the class property if it exists. |
|
1498 | ||
1493 | path = oname.split('.') |
|
1499 | If attrname represents a property, return it unevaluated (in case it has | |
1494 | root = '.'.join(path[:-1]) |
|
1500 | side effects or raises an error. | |
1495 | if info.parent is not None: |
|
1501 | ||
1496 | try: |
|
1502 | """ | |
1497 | target = getattr(info.parent, '__class__') |
|
1503 | if not isinstance(obj, type): | |
1498 | # The object belongs to a class instance. |
|
|||
1499 |
|
|
1504 | try: | |
1500 | target = getattr(target, path[-1]) |
|
1505 | # `getattr(type(obj), attrname)` is not guaranteed to return | |
1501 | # The class defines the object. |
|
1506 | # `obj`, but does so for property: | |
1502 | if isinstance(target, property): |
|
1507 | # | |
1503 | oname = root + '.__class__.' + path[-1] |
|
1508 | # property.__get__(self, None, cls) -> self | |
1504 | info = Struct(self._ofind(oname)) |
|
1509 | # | |
1505 | except AttributeError: pass |
|
1510 | # The universal alternative is to traverse the mro manually | |
1506 | except AttributeError: pass |
|
1511 | # searching for attrname in class dicts. | |
1507 |
|
1512 | attr = getattr(type(obj), attrname) | ||
1508 | # We return either the new info or the unmodified input if the object |
|
1513 | except AttributeError: | |
1509 | # hadn't been found |
|
1514 | pass | |
1510 | return info |
|
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 | def _object_find(self, oname, namespaces=None): |
|
1534 | def _object_find(self, oname, namespaces=None): | |
1513 | """Find an object and return a struct with info about it.""" |
|
1535 | """Find an object and return a struct with info about it.""" | |
1514 |
|
|
1536 | return Struct(self._ofind(oname, namespaces)) | |
1515 | return Struct(self._ofind_property(oname, inf)) |
|
|||
1516 |
|
1537 | |||
1517 | def _inspect(self, meth, oname, namespaces=None, **kw): |
|
1538 | def _inspect(self, meth, oname, namespaces=None, **kw): | |
1518 | """Generic interface to the inspector system. |
|
1539 | """Generic interface to the inspector system. |
@@ -376,6 +376,61 b' class InteractiveShellTestCase(unittest.TestCase):' | |||||
376 | parent = None) |
|
376 | parent = None) | |
377 | nt.assert_equal(find, info) |
|
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 | def test_custom_exception(self): |
|
434 | def test_custom_exception(self): | |
380 | called = [] |
|
435 | called = [] | |
381 | def my_handler(shell, etype, value, tb, tb_offset=None): |
|
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