Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/scipy/_lib/_bunch.py: 79%

70 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-12 06:31 +0000

1 

2import sys as _sys 

3from keyword import iskeyword as _iskeyword 

4 

5 

6def _validate_names(typename, field_names, extra_field_names): 

7 """ 

8 Ensure that all the given names are valid Python identifiers that 

9 do not start with '_'. Also check that there are no duplicates 

10 among field_names + extra_field_names. 

11 """ 

12 for name in [typename] + field_names + extra_field_names: 

13 if type(name) is not str: 

14 raise TypeError('typename and all field names must be strings') 

15 if not name.isidentifier(): 

16 raise ValueError('typename and all field names must be valid ' 

17 f'identifiers: {name!r}') 

18 if _iskeyword(name): 

19 raise ValueError('typename and all field names cannot be a ' 

20 f'keyword: {name!r}') 

21 

22 seen = set() 

23 for name in field_names + extra_field_names: 

24 if name.startswith('_'): 

25 raise ValueError('Field names cannot start with an underscore: ' 

26 f'{name!r}') 

27 if name in seen: 

28 raise ValueError(f'Duplicate field name: {name!r}') 

29 seen.add(name) 

30 

31 

32# Note: This code is adapted from CPython:Lib/collections/__init__.py 

33def _make_tuple_bunch(typename, field_names, extra_field_names=None, 

34 module=None): 

35 """ 

36 Create a namedtuple-like class with additional attributes. 

37 

38 This function creates a subclass of tuple that acts like a namedtuple 

39 and that has additional attributes. 

40 

41 The additional attributes are listed in `extra_field_names`. The 

42 values assigned to these attributes are not part of the tuple. 

43 

44 The reason this function exists is to allow functions in SciPy 

45 that currently return a tuple or a namedtuple to returned objects 

46 that have additional attributes, while maintaining backwards 

47 compatibility. 

48 

49 This should only be used to enhance *existing* functions in SciPy. 

50 New functions are free to create objects as return values without 

51 having to maintain backwards compatibility with an old tuple or 

52 namedtuple return value. 

53 

54 Parameters 

55 ---------- 

56 typename : str 

57 The name of the type. 

58 field_names : list of str 

59 List of names of the values to be stored in the tuple. These names 

60 will also be attributes of instances, so the values in the tuple 

61 can be accessed by indexing or as attributes. At least one name 

62 is required. See the Notes for additional restrictions. 

63 extra_field_names : list of str, optional 

64 List of names of values that will be stored as attributes of the 

65 object. See the notes for additional restrictions. 

66 

67 Returns 

68 ------- 

69 cls : type 

70 The new class. 

71 

72 Notes 

73 ----- 

74 There are restrictions on the names that may be used in `field_names` 

75 and `extra_field_names`: 

76 

77 * The names must be unique--no duplicates allowed. 

78 * The names must be valid Python identifiers, and must not begin with 

79 an underscore. 

80 * The names must not be Python keywords (e.g. 'def', 'and', etc., are 

81 not allowed). 

82 

83 Examples 

84 -------- 

85 >>> from scipy._lib._bunch import _make_tuple_bunch 

86 

87 Create a class that acts like a namedtuple with length 2 (with field 

88 names `x` and `y`) that will also have the attributes `w` and `beta`: 

89 

90 >>> Result = _make_tuple_bunch('Result', ['x', 'y'], ['w', 'beta']) 

91 

92 `Result` is the new class. We call it with keyword arguments to create 

93 a new instance with given values. 

94 

95 >>> result1 = Result(x=1, y=2, w=99, beta=0.5) 

96 >>> result1 

97 Result(x=1, y=2, w=99, beta=0.5) 

98 

99 `result1` acts like a tuple of length 2: 

100 

101 >>> len(result1) 

102 2 

103 >>> result1[:] 

104 (1, 2) 

105 

106 The values assigned when the instance was created are available as 

107 attributes: 

108 

109 >>> result1.y 

110 2 

111 >>> result1.beta 

112 0.5 

113 """ 

114 if len(field_names) == 0: 

115 raise ValueError('field_names must contain at least one name') 

116 

117 if extra_field_names is None: 

118 extra_field_names = [] 

119 _validate_names(typename, field_names, extra_field_names) 

120 

121 typename = _sys.intern(str(typename)) 

122 field_names = tuple(map(_sys.intern, field_names)) 

123 extra_field_names = tuple(map(_sys.intern, extra_field_names)) 

124 

125 all_names = field_names + extra_field_names 

126 arg_list = ', '.join(field_names) 

127 full_list = ', '.join(all_names) 

128 repr_fmt = ''.join(('(', 

129 ', '.join(f'{name}=%({name})r' for name in all_names), 

130 ')')) 

131 tuple_new = tuple.__new__ 

132 _dict, _tuple, _zip = dict, tuple, zip 

133 

134 # Create all the named tuple methods to be added to the class namespace 

135 

136 s = f"""\ 

137def __new__(_cls, {arg_list}, **extra_fields): 

138 return _tuple_new(_cls, ({arg_list},)) 

139 

140def __init__(self, {arg_list}, **extra_fields): 

141 for key in self._extra_fields: 

142 if key not in extra_fields: 

143 raise TypeError("missing keyword argument '%s'" % (key,)) 

144 for key, val in extra_fields.items(): 

145 if key not in self._extra_fields: 

146 raise TypeError("unexpected keyword argument '%s'" % (key,)) 

147 self.__dict__[key] = val 

148 

149def __setattr__(self, key, val): 

150 if key in {repr(field_names)}: 

151 raise AttributeError("can't set attribute %r of class %r" 

152 % (key, self.__class__.__name__)) 

153 else: 

154 self.__dict__[key] = val 

155""" 

156 del arg_list 

157 namespace = {'_tuple_new': tuple_new, 

158 '__builtins__': dict(TypeError=TypeError, 

159 AttributeError=AttributeError), 

160 '__name__': f'namedtuple_{typename}'} 

161 exec(s, namespace) 

162 __new__ = namespace['__new__'] 

163 __new__.__doc__ = f'Create new instance of {typename}({full_list})' 

164 __init__ = namespace['__init__'] 

165 __init__.__doc__ = f'Instantiate instance of {typename}({full_list})' 

166 __setattr__ = namespace['__setattr__'] 

167 

168 def __repr__(self): 

169 'Return a nicely formatted representation string' 

170 return self.__class__.__name__ + repr_fmt % self._asdict() 

171 

172 def _asdict(self): 

173 'Return a new dict which maps field names to their values.' 

174 out = _dict(_zip(self._fields, self)) 

175 out.update(self.__dict__) 

176 return out 

177 

178 def __getnewargs_ex__(self): 

179 'Return self as a plain tuple. Used by copy and pickle.' 

180 return _tuple(self), self.__dict__ 

181 

182 # Modify function metadata to help with introspection and debugging 

183 for method in (__new__, __repr__, _asdict, __getnewargs_ex__): 

184 method.__qualname__ = f'{typename}.{method.__name__}' 

185 

186 # Build-up the class namespace dictionary 

187 # and use type() to build the result class 

188 class_namespace = { 

189 '__doc__': f'{typename}({full_list})', 

190 '_fields': field_names, 

191 '__new__': __new__, 

192 '__init__': __init__, 

193 '__repr__': __repr__, 

194 '__setattr__': __setattr__, 

195 '_asdict': _asdict, 

196 '_extra_fields': extra_field_names, 

197 '__getnewargs_ex__': __getnewargs_ex__, 

198 } 

199 for index, name in enumerate(field_names): 

200 

201 def _get(self, index=index): 

202 return self[index] 

203 class_namespace[name] = property(_get) 

204 for name in extra_field_names: 

205 

206 def _get(self, name=name): 

207 return self.__dict__[name] 

208 class_namespace[name] = property(_get) 

209 

210 result = type(typename, (tuple,), class_namespace) 

211 

212 # For pickling to work, the __module__ variable needs to be set to the 

213 # frame where the named tuple is created. Bypass this step in environments 

214 # where sys._getframe is not defined (Jython for example) or sys._getframe 

215 # is not defined for arguments greater than 0 (IronPython), or where the 

216 # user has specified a particular module. 

217 if module is None: 

218 try: 

219 module = _sys._getframe(1).f_globals.get('__name__', '__main__') 

220 except (AttributeError, ValueError): 

221 pass 

222 if module is not None: 

223 result.__module__ = module 

224 __new__.__module__ = module 

225 

226 return result