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

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. 

16 

17This module contains whatever inspect doesn't offer out of the box. 

18""" 

19 

20import builtins 

21import inspect 

22import itertools 

23import linecache 

24import sys 

25import threading 

26import types 

27 

28from tensorflow.python.util import tf_inspect 

29 

30# This lock seems to help avoid linecache concurrency errors. 

31_linecache_lock = threading.Lock() 

32 

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()) 

35 

36 

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>')) 

46 

47 

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 

60 

61 

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 

74 

75 

76def isconstructor(cls): 

77 """Returns True if the argument is an object constructor. 

78 

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 

83 

84 Args: 

85 cls: Any 

86 

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__)) 

94 

95 

96def _fix_linecache_record(obj): 

97 """Fixes potential corruption of linecache in the presence of functools.wraps. 

98 

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). 

102 

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. 

106 

107 Args: 

108 obj: Any 

109 """ 

110 if hasattr(obj, '__module__'): 

111 obj_file = inspect.getfile(obj) 

112 obj_module = obj.__module__ 

113 

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__) 

121 

122 

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:])) 

129 

130 

131def getnamespace(f): 

132 """Returns the complete namespace of a function. 

133 

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. 

138 

139 Args: 

140 f: User defined function. 

141 

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 

156 

157 

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. 

160 

161 If the object defines a parent module, the function attempts to use it to 

162 locate the object. 

163 

164 This function will recurse inside modules, but it will not search objects for 

165 attributes. The recursion depth is controlled by max_depth. 

166 

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() 

178 

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) 

182 

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 

191 

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) 

204 

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 

218 

219 

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 

230 

231 

232def getmethodclass(m): 

233 """Resolves a function's owner, e.g. 

234 

235 a method's class. 

236 

237 Note that this returns the object that the function was retrieved from, not 

238 necessarily the class where it was defined. 

239 

240 This function relies on Python stack frame support in the interpreter, and 

241 has the same limitations that inspect.currentframe. 

242 

243 Limitations. This function will only work correctly if the owned class is 

244 visible in the caller's global or local variables. 

245 

246 Args: 

247 m: A user defined function 

248 

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. 

253 

254 Raises: 

255 ValueError: if the class could not be resolved for any unexpected reason. 

256 """ 

257 

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__ 

263 

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__ 

270 

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 

293 

294 if owners: 

295 if len(owners) == 1: 

296 return owners[0] 

297 

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)) 

304 

305 return None 

306 

307 

308def getfutureimports(entity): 

309 """Detects what future imports are necessary to safely execute entity source. 

310 

311 Args: 

312 entity: Any object 

313 

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__'))