diff options
author | Sylvain Thénault <sylvain.thenault@logilab.fr> | 2009-03-24 12:57:46 +0100 |
---|---|---|
committer | Sylvain Thénault <sylvain.thenault@logilab.fr> | 2009-03-24 12:57:46 +0100 |
commit | 89a9270cb3dcf6c9dbcf8dcefd1e26e7517b099e (patch) | |
tree | 9a4eb3243705d2e64ff4faa7144d15eec204c1b6 | |
parent | 3e9d8e5bfac1ec9287c75cd8e1754d1749a2aa48 (diff) | |
download | astroid-git-89a9270cb3dcf6c9dbcf8dcefd1e26e7517b099e.tar.gz |
introduce UnboundMethod / rename InstanceMethod to BoundMethod, handle .im_func
-rw-r--r-- | ChangeLog | 1 | ||||
-rw-r--r-- | __init__.py | 2 | ||||
-rw-r--r-- | infutils.py | 35 | ||||
-rw-r--r-- | scoped_nodes.py | 12 | ||||
-rw-r--r-- | test/unittest_inference.py | 34 |
5 files changed, 64 insertions, 20 deletions
@@ -3,6 +3,7 @@ Change log for the astng package -- * some understanding of the __builtin__.property decorator + * inference: introduce UnboundMethod / rename InstanceMethod to BoundMethod 2009-03-19 -- 0.18.0 * major api / tree structure changes to make it works with compiler *and* diff --git a/__init__.py b/__init__.py index fd086622..d298ab80 100644 --- a/__init__.py +++ b/__init__.py @@ -62,4 +62,4 @@ from logilab.astng import scoped_nodes, lookup, inference # more stuff available from logilab.astng import raw_building -from logilab.astng.infutils import YES, Instance, InstanceMethod +from logilab.astng.infutils import YES, Instance, BoundMethod, UnboundMethod diff --git a/infutils.py b/infutils.py index 127a3846..5df5e399 100644 --- a/infutils.py +++ b/infutils.py @@ -273,12 +273,16 @@ class Instance(Proxy): def _wrap_attr(self, attrs, context=None): """wrap bound methods of attrs in a InstanceMethod proxies""" for attr in attrs: - if isinstance(attr, Function): + if isinstance(attr, UnboundMethod): if '__builtin__.property' in attr.decoratornames(): for infered in attr.infer_call_result(self, context): yield infered - elif attr.type == 'method': - yield InstanceMethod(attr) + elif attr.type in ('method', 'classmethod'): + # XXX could get some information from the bound node: + # self (if method) or self._proxied (if class method) + yield BoundMethod(attr) + else: + yield attr else: yield attr @@ -311,16 +315,25 @@ class Instance(Proxy): return self._proxied.qname() -class InstanceMethod(Proxy): - """a special node representing a function bound to an instance""" +class UnboundMethod(Proxy): + """a special node representing a method not bound to an instance""" def __repr__(self): - instance = self._proxied.parent.frame() - return '<Bound method %s of %s.%s at 0x%s' % (self._proxied.name, - instance.root().name, - instance.name, - id(self)) - __str__ = __repr__ + frame = self._proxied.parent.frame() + return '<%s %s of %s at 0x%s' % (self.__class__.__name__, + self._proxied.name, + frame.qname(), id(self)) + + def is_bound(self): + return False + + def igetattr(self, name, context=None): + if name == 'im_func': + return iter((self._proxied,)) + return super(UnboundMethod, self).igetattr(name, context) + +class BoundMethod(UnboundMethod): + """a special node representing a method bound to an instance""" def is_bound(self): return True diff --git a/scoped_nodes.py b/scoped_nodes.py index fa1f6dae..10344dd4 100644 --- a/scoped_nodes.py +++ b/scoped_nodes.py @@ -39,8 +39,8 @@ from logilab.astng.nodes import Arguments, Class, Const, Function, GenExpr, \ From, Lambda, Module, Name, Pass, Raise, Tuple, List, Dict, Yield, \ DelAttr, DelName, const_factory as cf from logilab.astng.utils import extend_class -from logilab.astng.infutils import YES, InferenceContext, Instance, copy_context, \ - unpack_infer, _infer_stmts +from logilab.astng.infutils import YES, InferenceContext, Instance, \ + UnboundMethod, copy_context, unpack_infer, _infer_stmts from logilab.astng.nodes_as_string import as_string @@ -52,6 +52,12 @@ def remove_nodes(func, cls): return nodes return wrapper +def function_to_unbound_method(func): + def wrapper(*args, **kwargs): + return [isinstance(n, Function) and UnboundMethod(n) or n + for n in func(*args, **kwargs)] + return wrapper + # module class dict/iterator interface ######################################## class LocalsDictMixIn(object): @@ -718,7 +724,7 @@ class ClassNG(object): except NotFoundError: continue raise NotFoundError(name) - getattr = remove_nodes(getattr, DelAttr) + getattr = function_to_unbound_method(remove_nodes(getattr, DelAttr)) def igetattr(self, name, context=None): """infered getattr, need special treatment in class to handle diff --git a/test/unittest_inference.py b/test/unittest_inference.py index f105972d..46a8f848 100644 --- a/test/unittest_inference.py +++ b/test/unittest_inference.py @@ -18,7 +18,7 @@ from StringIO import StringIO from logilab.common.testlib import TestCase, unittest_main from logilab.astng import InferenceError, builder, nodes, inference -from logilab.astng.infutils import YES, Instance, InstanceMethod, path_wrapper +from logilab.astng.infutils import YES, Instance, BoundMethod, UnboundMethod, path_wrapper def get_name_node(start_from, name, index=0): return [n for n in start_from.nodes_of_class(nodes.Name) if n.name == name][index] @@ -220,7 +220,7 @@ a, b= b, a # Gasp ! def test_unbound_method_inference(self): infered = self.astng['m_unbound'].infer() meth1 = infered.next() - self.assertIsInstance(meth1, nodes.Function) + self.assertIsInstance(meth1, UnboundMethod) self.failUnlessEqual(meth1.name, 'meth1') self.failUnlessEqual(meth1.parent.frame().name, 'C') self.failUnlessRaises(StopIteration, infered.next) @@ -228,7 +228,7 @@ a, b= b, a # Gasp ! def test_bound_method_inference(self): infered = self.astng['m_bound'].infer() meth1 = infered.next() - self.assertIsInstance(meth1, InstanceMethod) + self.assertIsInstance(meth1, BoundMethod) self.failUnlessEqual(meth1.name, 'meth1') self.failUnlessEqual(meth1.parent.frame().name, 'C') self.failUnlessRaises(StopIteration, infered.next) @@ -912,7 +912,7 @@ def f(x): # self.assertIsInstance(b, Instance) # bopen = list(b.igetattr('open')) # self.assertEquals(len(bopen), 1) -# self.assertIsInstance(bopen[0], InstanceMethod) +# self.assertIsInstance(bopen[0], BoundMethod) # self.failUnless(bopen[0].callable()) def test_property(self): @@ -948,6 +948,30 @@ print SendMailController().me self.assertIsInstance(propinfered, Instance) self.assertEquals(propinfered.name, 'SendMailController') self.assertEquals(propinfered.root().name, __name__) - + + + def test_im_func_unwrap(self): + code = ''' +class EnvBasedTC: + def pactions(self): + print "hop" +pactions = EnvBasedTC.pactions.im_func +print pactions + +class EnvBasedTC2: + pactions = EnvBasedTC.pactions.im_func + print pactions + +''' + astng = builder.string_build(code, __name__, __file__) + pactions = get_name_node(astng, 'pactions') + infered = list(pactions.infer()) + self.assertEquals(len(infered), 1) + self.assertIsInstance(infered[0], nodes.Function) + pactions = get_name_node(astng['EnvBasedTC2'], 'pactions') + infered = list(pactions.infer()) + self.assertEquals(len(infered), 1) + self.assertIsInstance(infered[0], nodes.Function) + if __name__ == '__main__': unittest_main() |