mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-08 14:54:47 +08:00
getattr() / __getattribute__ / __getattr__ implementation
This commit is contained in:
@@ -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`.
|
||||||
|
|||||||
74
evaluate.py
74
evaluate.py
@@ -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). """
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user