Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/tensorflow/python/autograph/pyct/inspect_utils.py: 17%
129 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-03 07:57 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-03 07:57 +0000
1# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ==============================================================================
15"""Live entity inspection utilities.
17This module contains whatever inspect doesn't offer out of the box.
18"""
20import builtins
21import inspect
22import itertools
23import linecache
24import sys
25import threading
26import types
28from tensorflow.python.util import tf_inspect
30# This lock seems to help avoid linecache concurrency errors.
31_linecache_lock = threading.Lock()
33# Cache all the builtin elements in a frozen set for faster lookup.
34_BUILTIN_FUNCTION_IDS = frozenset(id(v) for v in builtins.__dict__.values())
37def islambda(f):
38 if not tf_inspect.isfunction(f):
39 return False
40 # TODO(mdan): Look into checking the only the code object.
41 if not (hasattr(f, '__name__') and hasattr(f, '__code__')):
42 return False
43 # Some wrappers can rename the function, but changing the name of the
44 # code object is harder.
45 return ((f.__name__ == '<lambda>') or (f.__code__.co_name == '<lambda>'))
48def isnamedtuple(f):
49 """Returns True if the argument is a namedtuple-like."""
50 if not (tf_inspect.isclass(f) and issubclass(f, tuple)):
51 return False
52 if not hasattr(f, '_fields'):
53 return False
54 fields = getattr(f, '_fields')
55 if not isinstance(fields, tuple):
56 return False
57 if not all(isinstance(f, str) for f in fields):
58 return False
59 return True
62def isbuiltin(f):
63 """Returns True if the argument is a built-in function."""
64 if id(f) in _BUILTIN_FUNCTION_IDS:
65 return True
66 elif isinstance(f, types.BuiltinFunctionType):
67 return True
68 elif inspect.isbuiltin(f):
69 return True
70 elif f is eval:
71 return True
72 else:
73 return False
76def isconstructor(cls):
77 """Returns True if the argument is an object constructor.
79 In general, any object of type class is a constructor, with the exception
80 of classes created using a callable metaclass.
81 See below for why a callable metaclass is not a trivial combination:
82 https://docs.python.org/2.7/reference/datamodel.html#customizing-class-creation
84 Args:
85 cls: Any
87 Returns:
88 Bool
89 """
90 return (inspect.isclass(cls) and
91 not (issubclass(cls.__class__, type) and
92 hasattr(cls.__class__, '__call__') and
93 cls.__class__.__call__ is not type.__call__))
96def _fix_linecache_record(obj):
97 """Fixes potential corruption of linecache in the presence of functools.wraps.
99 functools.wraps modifies the target object's __module__ field, which seems
100 to confuse linecache in special instances, for example when the source is
101 loaded from a .par file (see https://google.github.io/subpar/subpar.html).
103 This function simply triggers a call to linecache.updatecache when a mismatch
104 was detected between the object's __module__ property and the object's source
105 file.
107 Args:
108 obj: Any
109 """
110 if hasattr(obj, '__module__'):
111 obj_file = inspect.getfile(obj)
112 obj_module = obj.__module__
114 # A snapshot of the loaded modules helps avoid "dict changed size during
115 # iteration" errors.
116 loaded_modules = tuple(sys.modules.values())
117 for m in loaded_modules:
118 if hasattr(m, '__file__') and m.__file__ == obj_file:
119 if obj_module is not m:
120 linecache.updatecache(obj_file, m.__dict__)
123def getimmediatesource(obj):
124 """A variant of inspect.getsource that ignores the __wrapped__ property."""
125 with _linecache_lock:
126 _fix_linecache_record(obj)
127 lines, lnum = inspect.findsource(obj)
128 return ''.join(inspect.getblock(lines[lnum:]))
131def getnamespace(f):
132 """Returns the complete namespace of a function.
134 Namespace is defined here as the mapping of all non-local variables to values.
135 This includes the globals and the closure variables. Note that this captures
136 the entire globals collection of the function, and may contain extra symbols
137 that it does not actually use.
139 Args:
140 f: User defined function.
142 Returns:
143 A dict mapping symbol names to values.
144 """
145 namespace = dict(f.__globals__)
146 closure = f.__closure__
147 freevars = f.__code__.co_freevars
148 if freevars and closure:
149 for name, cell in zip(freevars, closure):
150 try:
151 namespace[name] = cell.cell_contents
152 except ValueError:
153 # Cell contains undefined variable, omit it from the namespace.
154 pass
155 return namespace
158def getqualifiedname(namespace, object_, max_depth=5, visited=None):
159 """Returns the name by which a value can be referred to in a given namespace.
161 If the object defines a parent module, the function attempts to use it to
162 locate the object.
164 This function will recurse inside modules, but it will not search objects for
165 attributes. The recursion depth is controlled by max_depth.
167 Args:
168 namespace: Dict[str, Any], the namespace to search into.
169 object_: Any, the value to search.
170 max_depth: Optional[int], a limit to the recursion depth when searching
171 inside modules.
172 visited: Optional[Set[int]], ID of modules to avoid visiting.
173 Returns: Union[str, None], the fully-qualified name that resolves to the value
174 o, or None if it couldn't be found.
175 """
176 if visited is None:
177 visited = set()
179 # Copy the dict to avoid "changed size error" during concurrent invocations.
180 # TODO(mdan): This is on the hot path. Can we avoid the copy?
181 namespace = dict(namespace)
183 for name in namespace:
184 # The value may be referenced by more than one symbol, case in which
185 # any symbol will be fine. If the program contains symbol aliases that
186 # change over time, this may capture a symbol that will later point to
187 # something else.
188 # TODO(mdan): Prefer the symbol that matches the value type name.
189 if object_ is namespace[name]:
190 return name
192 # If an object is not found, try to search its parent modules.
193 parent = tf_inspect.getmodule(object_)
194 if (parent is not None and parent is not object_ and parent is not namespace):
195 # No limit to recursion depth because of the guard above.
196 parent_name = getqualifiedname(
197 namespace, parent, max_depth=0, visited=visited)
198 if parent_name is not None:
199 name_in_parent = getqualifiedname(
200 parent.__dict__, object_, max_depth=0, visited=visited)
201 assert name_in_parent is not None, (
202 'An object should always be found in its owner module')
203 return '{}.{}'.format(parent_name, name_in_parent)
205 if max_depth:
206 # Iterating over a copy prevents "changed size due to iteration" errors.
207 # It's unclear why those occur - suspecting new modules may load during
208 # iteration.
209 for name in namespace.keys():
210 value = namespace[name]
211 if tf_inspect.ismodule(value) and id(value) not in visited:
212 visited.add(id(value))
213 name_in_module = getqualifiedname(value.__dict__, object_,
214 max_depth - 1, visited)
215 if name_in_module is not None:
216 return '{}.{}'.format(name, name_in_module)
217 return None
220def getdefiningclass(m, owner_class):
221 """Resolves the class (e.g. one of the superclasses) that defined a method."""
222 method_name = m.__name__
223 for super_class in inspect.getmro(owner_class):
224 if ((hasattr(super_class, '__dict__') and
225 method_name in super_class.__dict__) or
226 (hasattr(super_class, '__slots__') and
227 method_name in super_class.__slots__)):
228 return super_class
229 return owner_class
232def getmethodclass(m):
233 """Resolves a function's owner, e.g.
235 a method's class.
237 Note that this returns the object that the function was retrieved from, not
238 necessarily the class where it was defined.
240 This function relies on Python stack frame support in the interpreter, and
241 has the same limitations that inspect.currentframe.
243 Limitations. This function will only work correctly if the owned class is
244 visible in the caller's global or local variables.
246 Args:
247 m: A user defined function
249 Returns:
250 The class that this function was retrieved from, or None if the function
251 is not an object or class method, or the class that owns the object or
252 method is not visible to m.
254 Raises:
255 ValueError: if the class could not be resolved for any unexpected reason.
256 """
258 # Callable objects: return their own class.
259 if (not hasattr(m, '__name__') and hasattr(m, '__class__') and
260 hasattr(m, '__call__')):
261 if isinstance(m.__class__, type):
262 return m.__class__
264 # Instance and class: return the class of "self".
265 m_self = getattr(m, '__self__', None)
266 if m_self is not None:
267 if inspect.isclass(m_self):
268 return m_self
269 return m_self.__class__
271 # Class, static and unbound methods: search all defined classes in any
272 # namespace. This is inefficient but more robust a method.
273 owners = []
274 caller_frame = tf_inspect.currentframe().f_back
275 try:
276 # TODO(mdan): This doesn't consider cell variables.
277 # TODO(mdan): This won't work if the owner is hidden inside a container.
278 # Cell variables may be pulled using co_freevars and the closure.
279 for v in itertools.chain(caller_frame.f_locals.values(),
280 caller_frame.f_globals.values()):
281 if hasattr(v, m.__name__):
282 candidate = getattr(v, m.__name__)
283 # Py2 methods may be bound or unbound, extract im_func to get the
284 # underlying function.
285 if hasattr(candidate, 'im_func'):
286 candidate = candidate.im_func
287 if hasattr(m, 'im_func'):
288 m = m.im_func
289 if candidate is m:
290 owners.append(v)
291 finally:
292 del caller_frame
294 if owners:
295 if len(owners) == 1:
296 return owners[0]
298 # If multiple owners are found, and are not subclasses, raise an error.
299 owner_types = tuple(o if tf_inspect.isclass(o) else type(o) for o in owners)
300 for o in owner_types:
301 if tf_inspect.isclass(o) and issubclass(o, tuple(owner_types)):
302 return o
303 raise ValueError('Found too many owners of %s: %s' % (m, owners))
305 return None
308def getfutureimports(entity):
309 """Detects what future imports are necessary to safely execute entity source.
311 Args:
312 entity: Any object
314 Returns:
315 A tuple of future strings
316 """
317 if not (tf_inspect.isfunction(entity) or tf_inspect.ismethod(entity)):
318 return tuple()
319 return tuple(
320 sorted(name for name, value in entity.__globals__.items()
321 if getattr(value, '__module__', None) == '__future__'))