getattr() / __getattribute__ / __getattr__ implementation

This commit is contained in:
David Halter
2012-09-13 02:09:50 +02:00
parent dd8d1217c6
commit 1a4de1bc68
3 changed files with 113 additions and 9 deletions

View File

@@ -47,6 +47,7 @@ Jedi supports many of the widely used Python features:
- support for list.append, set.add, list.extend, etc. - support for list.append, set.add, list.extend, etc.
- (nested) list comprehensions / ternary expressions - (nested) list comprehensions / ternary expressions
- relative imports - relative imports
- `getattr()` / `__getattr__` / `__getattribute__`
- function annotations (py3k feature, are ignored right now, but being parsed. - function annotations (py3k feature, are ignored right now, but being parsed.
I don't know what to do with them.) I don't know what to do with them.)
- class decorators (py3k feature, are being ignored too, until I find a use - class decorators (py3k feature, are being ignored too, until I find a use
@@ -55,13 +56,11 @@ Jedi supports many of the widely used Python features:
However, it does not yet support (and probably will in future versions, because However, it does not yet support (and probably will in future versions, because
they are on my todo list): they are on my todo list):
- `getattr()` / `__getattr__` / `__getattribute__`
- `sys.path` modifications - `sys.path` modifications
- assert / isinstance
- manipulations of instances outside the instance variables, without using - manipulations of instances outside the instance variables, without using
functions functions
- mro
- operation support -> \_\_mul\_\_, \_\_add\_\_, etc. - operation support -> \_\_mul\_\_, \_\_add\_\_, etc.
- assert / isinstance
It does not support (and most probably will not in future versions): It does not support (and most probably will not in future versions):
- metaclasses (how could an auto-completion ever support this) - metaclasses (how could an auto-completion ever support this)
@@ -72,7 +71,7 @@ Caveats
------- -------
This framework should work for both Python 2/3. However, some things were just This framework should work for both Python 2/3. However, some things were just
not as *pythonic* Python 2 as things should be. To keep things simple, some not as *pythonic* in Python 2 as things should be. To keep things simple, some
things have been held back: things have been held back:
- Classes: Always Python 3 like, therefore all classes inherit from `object`. - Classes: Always Python 3 like, therefore all classes inherit from `object`.

View File

@@ -216,10 +216,8 @@ class Instance(use_metaclass(CachedMetaClass, Executable)):
return names return names
def get_subscope_by_name(self, name): def get_subscope_by_name(self, name):
for sub in reversed(self.base.subscopes): sub = self.base.get_subscope_by_name(name)
if sub.name.get_code() == name: return InstanceElement(self, sub, True)
return InstanceElement(self, sub, True)
raise KeyError("Couldn't find subscope.")
def execute_subscope_by_name(self, name, args=None): def execute_subscope_by_name(self, name, args=None):
if args is None: if args is None:
@@ -383,6 +381,12 @@ class Class(use_metaclass(CachedMetaClass, parsing.Base)):
result += super_result result += super_result
return result return result
def get_subscope_by_name(self, name):
for sub in reversed(self.subscopes):
if sub.name.get_code() == name:
return sub
raise KeyError("Couldn't find subscope.")
@property @property
def name(self): def name(self):
return self.base.name return self.base.name
@@ -480,6 +484,37 @@ class Execution(Executable):
def get_return_types(self, evaluate_generator=False): def get_return_types(self, evaluate_generator=False):
""" Get the return types of a function. """ """ Get the return types of a function. """
stmts = [] stmts = []
if self.base.parent() == builtin.builtin_scope \
and not isinstance(self.base, (Generator, Array)):
func_name = str(self.base.name)
# some implementations of builtins:
if func_name == 'getattr':
# follow the first param
try:
objects = follow_call_list([self.var_args[0]])
names = follow_call_list([self.var_args[1]])
except IndexError:
debug.warning('getattr() called with to few args.')
return []
for obj in objects:
if not isinstance(obj, (Instance, Class)):
debug.warning('getattr called without instance')
return []
for name in names:
key = name.var_args.get_only_subelement()
try:
stmts.append(obj.get_subscope_by_name(key))
except KeyError:
debug.warning('called getattr() without string')
#if not (isinstance(name, Instance) \
#and name.var_args:
#debug.warning('getattr called without instance')
#return []
return stmts
if self.base.isinstance(Class): if self.base.isinstance(Class):
# There maybe executions of executions. # There maybe executions of executions.
stmts = [Instance(self.base, self.var_args)] stmts = [Instance(self.base, self.var_args)]
@@ -1119,6 +1154,10 @@ def get_scopes_for_name(scope, name_str, position=None, search_global=False):
# if there are results, ignore the other scopes # if there are results, ignore the other scopes
if result: if result:
break break
if not result and isinstance(scope, Instance):
# getattr() / __getattr__ / __getattribute__
result += check_getattr(scope, name_str)
debug.dbg('sfn filter "%s" in %s: %s' % (name_str, scope, result)) debug.dbg('sfn filter "%s" in %s: %s' % (name_str, scope, result))
return result return result
@@ -1143,11 +1182,36 @@ def get_scopes_for_name(scope, name_str, position=None, search_global=False):
if isinstance(scope, Instance): if isinstance(scope, Instance):
scope_generator = scope.scope_generator() scope_generator = scope.scope_generator()
else: else:
names = get_defined_names_for_position(scope, position) if isinstance(scope, Class):
# classes are only available directly via chaining?
# strange stuff...
names = scope.get_defined_names()
else:
names = get_defined_names_for_position(scope, position)
scope_generator = iter([(scope, names)]) scope_generator = iter([(scope, names)])
return descriptor_check(remove_statements(filter_name(scope_generator))) return descriptor_check(remove_statements(filter_name(scope_generator)))
def check_getattr(inst, name_str):
result = []
# str is important to lose the NamePart!
name = parsing.Call(str(name_str), parsing.Call.STRING, (0, 0), inst)
args = helpers.generate_param_array([name])
try:
result = inst.execute_subscope_by_name('__getattr__', args)
except KeyError:
pass
if not result:
# this is a little bit special. `__getattribute__` is executed
# before anything else. But: I know no use case, where this
# could be practical and the jedi would return wrong types. If
# you ever have something, let me know!
try:
result = inst.execute_subscope_by_name('__getattribute__', args)
except KeyError:
pass
return result
def get_iterator_types(inputs): def get_iterator_types(inputs):
""" Returns the types of any iterator (arrays, yields, __iter__, etc). """ """ Returns the types of any iterator (arrays, yields, __iter__, etc). """

View File

@@ -464,3 +464,44 @@ a[1]
WithoutMethod.blub(WithoutMethod()) WithoutMethod.blub(WithoutMethod())
#? str() #? str()
WithoutMethod.blub(B()) WithoutMethod.blub(B())
# -----------------
# __getattr__ / getattr() / __getattribute__
# -----------------
#? str().upper
getattr(str(), 'upper')
#? str.upper
getattr(str, 'upper')
# some strange getattr calls
#?
getattr(str, 1)
#?
getattr()
#?
getattr(str)
#?
getattr(getattr, 1)
class Base():
def ret(self, b):
return b
class Wrapper():
def __init__(self, obj):
self.obj = obj
def __getattr__(self, name):
return getattr(self.obj, name)
class Wrapper2():
def __getattribute__(self, name):
return getattr(Base(), name)
#? int()
Wrapper(Base()).ret(3)
#? int()
Wrapper2(Base()).ret(3)