You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

_compat.py 7.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. from __future__ import absolute_import, division, print_function
  2. import platform
  3. import sys
  4. import types
  5. import warnings
  6. PY2 = sys.version_info[0] == 2
  7. PYPY = platform.python_implementation() == "PyPy"
  8. if PYPY or sys.version_info[:2] >= (3, 6):
  9. ordered_dict = dict
  10. else:
  11. from collections import OrderedDict
  12. ordered_dict = OrderedDict
  13. if PY2:
  14. from UserDict import IterableUserDict
  15. from collections import Mapping, Sequence
  16. # We 'bundle' isclass instead of using inspect as importing inspect is
  17. # fairly expensive (order of 10-15 ms for a modern machine in 2016)
  18. def isclass(klass):
  19. return isinstance(klass, (type, types.ClassType))
  20. # TYPE is used in exceptions, repr(int) is different on Python 2 and 3.
  21. TYPE = "type"
  22. def iteritems(d):
  23. return d.iteritems()
  24. # Python 2 is bereft of a read-only dict proxy, so we make one!
  25. class ReadOnlyDict(IterableUserDict):
  26. """
  27. Best-effort read-only dict wrapper.
  28. """
  29. def __setitem__(self, key, val):
  30. # We gently pretend we're a Python 3 mappingproxy.
  31. raise TypeError(
  32. "'mappingproxy' object does not support item assignment"
  33. )
  34. def update(self, _):
  35. # We gently pretend we're a Python 3 mappingproxy.
  36. raise AttributeError(
  37. "'mappingproxy' object has no attribute 'update'"
  38. )
  39. def __delitem__(self, _):
  40. # We gently pretend we're a Python 3 mappingproxy.
  41. raise TypeError(
  42. "'mappingproxy' object does not support item deletion"
  43. )
  44. def clear(self):
  45. # We gently pretend we're a Python 3 mappingproxy.
  46. raise AttributeError(
  47. "'mappingproxy' object has no attribute 'clear'"
  48. )
  49. def pop(self, key, default=None):
  50. # We gently pretend we're a Python 3 mappingproxy.
  51. raise AttributeError(
  52. "'mappingproxy' object has no attribute 'pop'"
  53. )
  54. def popitem(self):
  55. # We gently pretend we're a Python 3 mappingproxy.
  56. raise AttributeError(
  57. "'mappingproxy' object has no attribute 'popitem'"
  58. )
  59. def setdefault(self, key, default=None):
  60. # We gently pretend we're a Python 3 mappingproxy.
  61. raise AttributeError(
  62. "'mappingproxy' object has no attribute 'setdefault'"
  63. )
  64. def __repr__(self):
  65. # Override to be identical to the Python 3 version.
  66. return "mappingproxy(" + repr(self.data) + ")"
  67. def metadata_proxy(d):
  68. res = ReadOnlyDict()
  69. res.data.update(d) # We blocked update, so we have to do it like this.
  70. return res
  71. def just_warn(*args, **kw): # pragma: nocover
  72. """
  73. We only warn on Python 3 because we are not aware of any concrete
  74. consequences of not setting the cell on Python 2.
  75. """
  76. else: # Python 3 and later.
  77. from collections.abc import Mapping, Sequence # noqa
  78. def just_warn(*args, **kw):
  79. """
  80. We only warn on Python 3 because we are not aware of any concrete
  81. consequences of not setting the cell on Python 2.
  82. """
  83. warnings.warn(
  84. "Running interpreter doesn't sufficiently support code object "
  85. "introspection. Some features like bare super() or accessing "
  86. "__class__ will not work with slotted classes.",
  87. RuntimeWarning,
  88. stacklevel=2,
  89. )
  90. def isclass(klass):
  91. return isinstance(klass, type)
  92. TYPE = "class"
  93. def iteritems(d):
  94. return d.items()
  95. def metadata_proxy(d):
  96. return types.MappingProxyType(dict(d))
  97. def make_set_closure_cell():
  98. """Return a function of two arguments (cell, value) which sets
  99. the value stored in the closure cell `cell` to `value`.
  100. """
  101. # pypy makes this easy. (It also supports the logic below, but
  102. # why not do the easy/fast thing?)
  103. if PYPY: # pragma: no cover
  104. def set_closure_cell(cell, value):
  105. cell.__setstate__((value,))
  106. return set_closure_cell
  107. # Otherwise gotta do it the hard way.
  108. # Create a function that will set its first cellvar to `value`.
  109. def set_first_cellvar_to(value):
  110. x = value
  111. return
  112. # This function will be eliminated as dead code, but
  113. # not before its reference to `x` forces `x` to be
  114. # represented as a closure cell rather than a local.
  115. def force_x_to_be_a_cell(): # pragma: no cover
  116. return x
  117. try:
  118. # Extract the code object and make sure our assumptions about
  119. # the closure behavior are correct.
  120. if PY2:
  121. co = set_first_cellvar_to.func_code
  122. else:
  123. co = set_first_cellvar_to.__code__
  124. if co.co_cellvars != ("x",) or co.co_freevars != ():
  125. raise AssertionError # pragma: no cover
  126. # Convert this code object to a code object that sets the
  127. # function's first _freevar_ (not cellvar) to the argument.
  128. if sys.version_info >= (3, 8):
  129. # CPython 3.8+ has an incompatible CodeType signature
  130. # (added a posonlyargcount argument) but also added
  131. # CodeType.replace() to do this without counting parameters.
  132. set_first_freevar_code = co.replace(
  133. co_cellvars=co.co_freevars, co_freevars=co.co_cellvars
  134. )
  135. else:
  136. args = [co.co_argcount]
  137. if not PY2:
  138. args.append(co.co_kwonlyargcount)
  139. args.extend(
  140. [
  141. co.co_nlocals,
  142. co.co_stacksize,
  143. co.co_flags,
  144. co.co_code,
  145. co.co_consts,
  146. co.co_names,
  147. co.co_varnames,
  148. co.co_filename,
  149. co.co_name,
  150. co.co_firstlineno,
  151. co.co_lnotab,
  152. # These two arguments are reversed:
  153. co.co_cellvars,
  154. co.co_freevars,
  155. ]
  156. )
  157. set_first_freevar_code = types.CodeType(*args)
  158. def set_closure_cell(cell, value):
  159. # Create a function using the set_first_freevar_code,
  160. # whose first closure cell is `cell`. Calling it will
  161. # change the value of that cell.
  162. setter = types.FunctionType(
  163. set_first_freevar_code, {}, "setter", (), (cell,)
  164. )
  165. # And call it to set the cell.
  166. setter(value)
  167. # Make sure it works on this interpreter:
  168. def make_func_with_cell():
  169. x = None
  170. def func():
  171. return x # pragma: no cover
  172. return func
  173. if PY2:
  174. cell = make_func_with_cell().func_closure[0]
  175. else:
  176. cell = make_func_with_cell().__closure__[0]
  177. set_closure_cell(cell, 100)
  178. if cell.cell_contents != 100:
  179. raise AssertionError # pragma: no cover
  180. except Exception:
  181. return just_warn
  182. else:
  183. return set_closure_cell
  184. set_closure_cell = make_set_closure_cell()