##// END OF EJS Templates
core.interactiveshell.ofind: don't evaluate property.fget (it may raise)
immerrr -
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 try:
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 inf = Struct(self._ofind(oname, namespaces))
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