mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Add CPython library modules needed for unittest
This commit is contained in:
8 files changed
+5590
-1
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,2 @@
|
||||
from _collections_abc import *
|
||||
from _collections_abc import __all__
|
||||
+2097
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,828 @@
|
||||
"""functools.py - Tools for working with functions and callable objects
|
||||
"""
|
||||
# Python module wrapper for _functools C module
|
||||
# to allow utilities written in Python to be added
|
||||
# to the functools module.
|
||||
# Written by Nick Coghlan <ncoghlan at gmail.com>,
|
||||
# Raymond Hettinger <python at rcn.com>,
|
||||
# and Łukasz Langa <lukasz at langa.pl>.
|
||||
# Copyright (C) 2006-2013 Python Software Foundation.
|
||||
# See C source code for _functools credits/copyright
|
||||
|
||||
__all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES',
|
||||
'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial',
|
||||
'partialmethod', 'singledispatch']
|
||||
|
||||
try:
|
||||
from _functools import reduce
|
||||
except ImportError:
|
||||
pass
|
||||
from abc import get_cache_token
|
||||
from collections import namedtuple
|
||||
# import types, weakref # Deferred to single_dispatch()
|
||||
from reprlib import recursive_repr
|
||||
from _thread import RLock
|
||||
|
||||
|
||||
################################################################################
|
||||
### update_wrapper() and wraps() decorator
|
||||
################################################################################
|
||||
|
||||
# update_wrapper() and wraps() are tools to help write
|
||||
# wrapper functions that can handle naive introspection
|
||||
|
||||
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
|
||||
'__annotations__')
|
||||
WRAPPER_UPDATES = ('__dict__',)
|
||||
def update_wrapper(wrapper,
|
||||
wrapped,
|
||||
assigned = WRAPPER_ASSIGNMENTS,
|
||||
updated = WRAPPER_UPDATES):
|
||||
"""Update a wrapper function to look like the wrapped function
|
||||
|
||||
wrapper is the function to be updated
|
||||
wrapped is the original function
|
||||
assigned is a tuple naming the attributes assigned directly
|
||||
from the wrapped function to the wrapper function (defaults to
|
||||
functools.WRAPPER_ASSIGNMENTS)
|
||||
updated is a tuple naming the attributes of the wrapper that
|
||||
are updated with the corresponding attribute from the wrapped
|
||||
function (defaults to functools.WRAPPER_UPDATES)
|
||||
"""
|
||||
for attr in assigned:
|
||||
try:
|
||||
value = getattr(wrapped, attr)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
setattr(wrapper, attr, value)
|
||||
for attr in updated:
|
||||
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
|
||||
# Issue #17482: set __wrapped__ last so we don't inadvertently copy it
|
||||
# from the wrapped function when updating __dict__
|
||||
wrapper.__wrapped__ = wrapped
|
||||
# Return the wrapper so this can be used as a decorator via partial()
|
||||
return wrapper
|
||||
|
||||
def wraps(wrapped,
|
||||
assigned = WRAPPER_ASSIGNMENTS,
|
||||
updated = WRAPPER_UPDATES):
|
||||
"""Decorator factory to apply update_wrapper() to a wrapper function
|
||||
|
||||
Returns a decorator that invokes update_wrapper() with the decorated
|
||||
function as the wrapper argument and the arguments to wraps() as the
|
||||
remaining arguments. Default arguments are as for update_wrapper().
|
||||
This is a convenience function to simplify applying partial() to
|
||||
update_wrapper().
|
||||
"""
|
||||
return partial(update_wrapper, wrapped=wrapped,
|
||||
assigned=assigned, updated=updated)
|
||||
|
||||
|
||||
################################################################################
|
||||
### total_ordering class decorator
|
||||
################################################################################
|
||||
|
||||
# The total ordering functions all invoke the root magic method directly
|
||||
# rather than using the corresponding operator. This avoids possible
|
||||
# infinite recursion that could occur when the operator dispatch logic
|
||||
# detects a NotImplemented result and then calls a reflected method.
|
||||
|
||||
def _gt_from_lt(self, other, NotImplemented=NotImplemented):
|
||||
'Return a > b. Computed by @total_ordering from (not a < b) and (a != b).'
|
||||
op_result = self.__lt__(other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return not op_result and self != other
|
||||
|
||||
def _le_from_lt(self, other, NotImplemented=NotImplemented):
|
||||
'Return a <= b. Computed by @total_ordering from (a < b) or (a == b).'
|
||||
op_result = self.__lt__(other)
|
||||
return op_result or self == other
|
||||
|
||||
def _ge_from_lt(self, other, NotImplemented=NotImplemented):
|
||||
'Return a >= b. Computed by @total_ordering from (not a < b).'
|
||||
op_result = self.__lt__(other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return not op_result
|
||||
|
||||
def _ge_from_le(self, other, NotImplemented=NotImplemented):
|
||||
'Return a >= b. Computed by @total_ordering from (not a <= b) or (a == b).'
|
||||
op_result = self.__le__(other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return not op_result or self == other
|
||||
|
||||
def _lt_from_le(self, other, NotImplemented=NotImplemented):
|
||||
'Return a < b. Computed by @total_ordering from (a <= b) and (a != b).'
|
||||
op_result = self.__le__(other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return op_result and self != other
|
||||
|
||||
def _gt_from_le(self, other, NotImplemented=NotImplemented):
|
||||
'Return a > b. Computed by @total_ordering from (not a <= b).'
|
||||
op_result = self.__le__(other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return not op_result
|
||||
|
||||
def _lt_from_gt(self, other, NotImplemented=NotImplemented):
|
||||
'Return a < b. Computed by @total_ordering from (not a > b) and (a != b).'
|
||||
op_result = self.__gt__(other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return not op_result and self != other
|
||||
|
||||
def _ge_from_gt(self, other, NotImplemented=NotImplemented):
|
||||
'Return a >= b. Computed by @total_ordering from (a > b) or (a == b).'
|
||||
op_result = self.__gt__(other)
|
||||
return op_result or self == other
|
||||
|
||||
def _le_from_gt(self, other, NotImplemented=NotImplemented):
|
||||
'Return a <= b. Computed by @total_ordering from (not a > b).'
|
||||
op_result = self.__gt__(other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return not op_result
|
||||
|
||||
def _le_from_ge(self, other, NotImplemented=NotImplemented):
|
||||
'Return a <= b. Computed by @total_ordering from (not a >= b) or (a == b).'
|
||||
op_result = self.__ge__(other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return not op_result or self == other
|
||||
|
||||
def _gt_from_ge(self, other, NotImplemented=NotImplemented):
|
||||
'Return a > b. Computed by @total_ordering from (a >= b) and (a != b).'
|
||||
op_result = self.__ge__(other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return op_result and self != other
|
||||
|
||||
def _lt_from_ge(self, other, NotImplemented=NotImplemented):
|
||||
'Return a < b. Computed by @total_ordering from (not a >= b).'
|
||||
op_result = self.__ge__(other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return not op_result
|
||||
|
||||
_convert = {
|
||||
'__lt__': [('__gt__', _gt_from_lt),
|
||||
('__le__', _le_from_lt),
|
||||
('__ge__', _ge_from_lt)],
|
||||
'__le__': [('__ge__', _ge_from_le),
|
||||
('__lt__', _lt_from_le),
|
||||
('__gt__', _gt_from_le)],
|
||||
'__gt__': [('__lt__', _lt_from_gt),
|
||||
('__ge__', _ge_from_gt),
|
||||
('__le__', _le_from_gt)],
|
||||
'__ge__': [('__le__', _le_from_ge),
|
||||
('__gt__', _gt_from_ge),
|
||||
('__lt__', _lt_from_ge)]
|
||||
}
|
||||
|
||||
def total_ordering(cls):
|
||||
"""Class decorator that fills in missing ordering methods"""
|
||||
# Find user-defined comparisons (not those inherited from object).
|
||||
roots = {op for op in _convert if getattr(cls, op, None) is not getattr(object, op, None)}
|
||||
if not roots:
|
||||
raise ValueError('must define at least one ordering operation: < > <= >=')
|
||||
root = max(roots) # prefer __lt__ to __le__ to __gt__ to __ge__
|
||||
for opname, opfunc in _convert[root]:
|
||||
if opname not in roots:
|
||||
opfunc.__name__ = opname
|
||||
setattr(cls, opname, opfunc)
|
||||
return cls
|
||||
|
||||
|
||||
################################################################################
|
||||
### cmp_to_key() function converter
|
||||
################################################################################
|
||||
|
||||
def cmp_to_key(mycmp):
|
||||
"""Convert a cmp= function into a key= function"""
|
||||
class K(object):
|
||||
__slots__ = ['obj']
|
||||
def __init__(self, obj):
|
||||
self.obj = obj
|
||||
def __lt__(self, other):
|
||||
return mycmp(self.obj, other.obj) < 0
|
||||
def __gt__(self, other):
|
||||
return mycmp(self.obj, other.obj) > 0
|
||||
def __eq__(self, other):
|
||||
return mycmp(self.obj, other.obj) == 0
|
||||
def __le__(self, other):
|
||||
return mycmp(self.obj, other.obj) <= 0
|
||||
def __ge__(self, other):
|
||||
return mycmp(self.obj, other.obj) >= 0
|
||||
__hash__ = None
|
||||
return K
|
||||
|
||||
try:
|
||||
from _functools import cmp_to_key
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
################################################################################
|
||||
### partial() argument application
|
||||
################################################################################
|
||||
|
||||
# Purely functional, no descriptor behaviour
|
||||
class partial:
|
||||
"""New function with partial application of the given arguments
|
||||
and keywords.
|
||||
"""
|
||||
|
||||
__slots__ = "func", "args", "keywords", "__dict__", "__weakref__"
|
||||
|
||||
def __new__(*args, **keywords):
|
||||
if not args:
|
||||
raise TypeError("descriptor '__new__' of partial needs an argument")
|
||||
if len(args) < 2:
|
||||
raise TypeError("type 'partial' takes at least one argument")
|
||||
cls, func, *args = args
|
||||
if not callable(func):
|
||||
raise TypeError("the first argument must be callable")
|
||||
args = tuple(args)
|
||||
|
||||
if hasattr(func, "func"):
|
||||
args = func.args + args
|
||||
tmpkw = func.keywords.copy()
|
||||
tmpkw.update(keywords)
|
||||
keywords = tmpkw
|
||||
del tmpkw
|
||||
func = func.func
|
||||
|
||||
self = super(partial, cls).__new__(cls)
|
||||
|
||||
self.func = func
|
||||
self.args = args
|
||||
self.keywords = keywords
|
||||
return self
|
||||
|
||||
def __call__(*args, **keywords):
|
||||
if not args:
|
||||
raise TypeError("descriptor '__call__' of partial needs an argument")
|
||||
self, *args = args
|
||||
newkeywords = self.keywords.copy()
|
||||
newkeywords.update(keywords)
|
||||
return self.func(*self.args, *args, **newkeywords)
|
||||
|
||||
@recursive_repr()
|
||||
def __repr__(self):
|
||||
qualname = type(self).__qualname__
|
||||
args = [repr(self.func)]
|
||||
args.extend(repr(x) for x in self.args)
|
||||
args.extend(f"{k}={v!r}" for (k, v) in self.keywords.items())
|
||||
if type(self).__module__ == "functools":
|
||||
return f"functools.{qualname}({', '.join(args)})"
|
||||
return f"{qualname}({', '.join(args)})"
|
||||
|
||||
def __reduce__(self):
|
||||
return type(self), (self.func,), (self.func, self.args,
|
||||
self.keywords or None, self.__dict__ or None)
|
||||
|
||||
def __setstate__(self, state):
|
||||
if not isinstance(state, tuple):
|
||||
raise TypeError("argument to __setstate__ must be a tuple")
|
||||
if len(state) != 4:
|
||||
raise TypeError(f"expected 4 items in state, got {len(state)}")
|
||||
func, args, kwds, namespace = state
|
||||
if (not callable(func) or not isinstance(args, tuple) or
|
||||
(kwds is not None and not isinstance(kwds, dict)) or
|
||||
(namespace is not None and not isinstance(namespace, dict))):
|
||||
raise TypeError("invalid partial state")
|
||||
|
||||
args = tuple(args) # just in case it's a subclass
|
||||
if kwds is None:
|
||||
kwds = {}
|
||||
elif type(kwds) is not dict: # XXX does it need to be *exactly* dict?
|
||||
kwds = dict(kwds)
|
||||
if namespace is None:
|
||||
namespace = {}
|
||||
|
||||
self.__dict__ = namespace
|
||||
self.func = func
|
||||
self.args = args
|
||||
self.keywords = kwds
|
||||
|
||||
try:
|
||||
from _functools import partial
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# Descriptor version
|
||||
class partialmethod(object):
|
||||
"""Method descriptor with partial application of the given arguments
|
||||
and keywords.
|
||||
|
||||
Supports wrapping existing descriptors and handles non-descriptor
|
||||
callables as instance methods.
|
||||
"""
|
||||
|
||||
def __init__(self, func, *args, **keywords):
|
||||
if not callable(func) and not hasattr(func, "__get__"):
|
||||
raise TypeError("{!r} is not callable or a descriptor"
|
||||
.format(func))
|
||||
|
||||
# func could be a descriptor like classmethod which isn't callable,
|
||||
# so we can't inherit from partial (it verifies func is callable)
|
||||
if isinstance(func, partialmethod):
|
||||
# flattening is mandatory in order to place cls/self before all
|
||||
# other arguments
|
||||
# it's also more efficient since only one function will be called
|
||||
self.func = func.func
|
||||
self.args = func.args + args
|
||||
self.keywords = func.keywords.copy()
|
||||
self.keywords.update(keywords)
|
||||
else:
|
||||
self.func = func
|
||||
self.args = args
|
||||
self.keywords = keywords
|
||||
|
||||
def __repr__(self):
|
||||
args = ", ".join(map(repr, self.args))
|
||||
keywords = ", ".join("{}={!r}".format(k, v)
|
||||
for k, v in self.keywords.items())
|
||||
format_string = "{module}.{cls}({func}, {args}, {keywords})"
|
||||
return format_string.format(module=self.__class__.__module__,
|
||||
cls=self.__class__.__qualname__,
|
||||
func=self.func,
|
||||
args=args,
|
||||
keywords=keywords)
|
||||
|
||||
def _make_unbound_method(self):
|
||||
def _method(*args, **keywords):
|
||||
call_keywords = self.keywords.copy()
|
||||
call_keywords.update(keywords)
|
||||
cls_or_self, *rest = args
|
||||
call_args = (cls_or_self,) + self.args + tuple(rest)
|
||||
return self.func(*call_args, **call_keywords)
|
||||
_method.__isabstractmethod__ = self.__isabstractmethod__
|
||||
_method._partialmethod = self
|
||||
return _method
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
get = getattr(self.func, "__get__", None)
|
||||
result = None
|
||||
if get is not None:
|
||||
new_func = get(obj, cls)
|
||||
if new_func is not self.func:
|
||||
# Assume __get__ returning something new indicates the
|
||||
# creation of an appropriate callable
|
||||
result = partial(new_func, *self.args, **self.keywords)
|
||||
try:
|
||||
result.__self__ = new_func.__self__
|
||||
except AttributeError:
|
||||
pass
|
||||
if result is None:
|
||||
# If the underlying descriptor didn't do anything, treat this
|
||||
# like an instance method
|
||||
result = self._make_unbound_method().__get__(obj, cls)
|
||||
return result
|
||||
|
||||
@property
|
||||
def __isabstractmethod__(self):
|
||||
return getattr(self.func, "__isabstractmethod__", False)
|
||||
|
||||
|
||||
################################################################################
|
||||
### LRU Cache function decorator
|
||||
################################################################################
|
||||
|
||||
_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])
|
||||
|
||||
class _HashedSeq(list):
|
||||
""" This class guarantees that hash() will be called no more than once
|
||||
per element. This is important because the lru_cache() will hash
|
||||
the key multiple times on a cache miss.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = 'hashvalue'
|
||||
|
||||
def __init__(self, tup, hash=hash):
|
||||
self[:] = tup
|
||||
self.hashvalue = hash(tup)
|
||||
|
||||
def __hash__(self):
|
||||
return self.hashvalue
|
||||
|
||||
def _make_key(args, kwds, typed,
|
||||
kwd_mark = (object(),),
|
||||
fasttypes = {int, str, frozenset, type(None)},
|
||||
tuple=tuple, type=type, len=len):
|
||||
"""Make a cache key from optionally typed positional and keyword arguments
|
||||
|
||||
The key is constructed in a way that is flat as possible rather than
|
||||
as a nested structure that would take more memory.
|
||||
|
||||
If there is only a single argument and its data type is known to cache
|
||||
its hash value, then that argument is returned without a wrapper. This
|
||||
saves space and improves lookup speed.
|
||||
|
||||
"""
|
||||
# All of code below relies on kwds preserving the order input by the user.
|
||||
# Formerly, we sorted() the kwds before looping. The new way is *much*
|
||||
# faster; however, it means that f(x=1, y=2) will now be treated as a
|
||||
# distinct call from f(y=2, x=1) which will be cached separately.
|
||||
key = args
|
||||
if kwds:
|
||||
key += kwd_mark
|
||||
for item in kwds.items():
|
||||
key += item
|
||||
if typed:
|
||||
key += tuple(type(v) for v in args)
|
||||
if kwds:
|
||||
key += tuple(type(v) for v in kwds.values())
|
||||
elif len(key) == 1 and type(key[0]) in fasttypes:
|
||||
return key[0]
|
||||
return _HashedSeq(key)
|
||||
|
||||
def lru_cache(maxsize=128, typed=False):
|
||||
"""Least-recently-used cache decorator.
|
||||
|
||||
If *maxsize* is set to None, the LRU features are disabled and the cache
|
||||
can grow without bound.
|
||||
|
||||
If *typed* is True, arguments of different types will be cached separately.
|
||||
For example, f(3.0) and f(3) will be treated as distinct calls with
|
||||
distinct results.
|
||||
|
||||
Arguments to the cached function must be hashable.
|
||||
|
||||
View the cache statistics named tuple (hits, misses, maxsize, currsize)
|
||||
with f.cache_info(). Clear the cache and statistics with f.cache_clear().
|
||||
Access the underlying function with f.__wrapped__.
|
||||
|
||||
See: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used
|
||||
|
||||
"""
|
||||
|
||||
# Users should only access the lru_cache through its public API:
|
||||
# cache_info, cache_clear, and f.__wrapped__
|
||||
# The internals of the lru_cache are encapsulated for thread safety and
|
||||
# to allow the implementation to change (including a possible C version).
|
||||
|
||||
# Early detection of an erroneous call to @lru_cache without any arguments
|
||||
# resulting in the inner function being passed to maxsize instead of an
|
||||
# integer or None.
|
||||
if maxsize is not None and not isinstance(maxsize, int):
|
||||
raise TypeError('Expected maxsize to be an integer or None')
|
||||
|
||||
def decorating_function(user_function):
|
||||
wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
|
||||
return update_wrapper(wrapper, user_function)
|
||||
|
||||
return decorating_function
|
||||
|
||||
def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo):
|
||||
# Constants shared by all lru cache instances:
|
||||
sentinel = object() # unique object used to signal cache misses
|
||||
make_key = _make_key # build a key from the function arguments
|
||||
PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields
|
||||
|
||||
cache = {}
|
||||
hits = misses = 0
|
||||
full = False
|
||||
cache_get = cache.get # bound method to lookup a key or return None
|
||||
cache_len = cache.__len__ # get cache size without calling len()
|
||||
lock = RLock() # because linkedlist updates aren't threadsafe
|
||||
root = [] # root of the circular doubly linked list
|
||||
root[:] = [root, root, None, None] # initialize by pointing to self
|
||||
|
||||
if maxsize == 0:
|
||||
|
||||
def wrapper(*args, **kwds):
|
||||
# No caching -- just a statistics update after a successful call
|
||||
nonlocal misses
|
||||
result = user_function(*args, **kwds)
|
||||
misses += 1
|
||||
return result
|
||||
|
||||
elif maxsize is None:
|
||||
|
||||
def wrapper(*args, **kwds):
|
||||
# Simple caching without ordering or size limit
|
||||
nonlocal hits, misses
|
||||
key = make_key(args, kwds, typed)
|
||||
result = cache_get(key, sentinel)
|
||||
if result is not sentinel:
|
||||
hits += 1
|
||||
return result
|
||||
result = user_function(*args, **kwds)
|
||||
cache[key] = result
|
||||
misses += 1
|
||||
return result
|
||||
|
||||
else:
|
||||
|
||||
def wrapper(*args, **kwds):
|
||||
# Size limited caching that tracks accesses by recency
|
||||
nonlocal root, hits, misses, full
|
||||
key = make_key(args, kwds, typed)
|
||||
with lock:
|
||||
link = cache_get(key)
|
||||
if link is not None:
|
||||
# Move the link to the front of the circular queue
|
||||
link_prev, link_next, _key, result = link
|
||||
link_prev[NEXT] = link_next
|
||||
link_next[PREV] = link_prev
|
||||
last = root[PREV]
|
||||
last[NEXT] = root[PREV] = link
|
||||
link[PREV] = last
|
||||
link[NEXT] = root
|
||||
hits += 1
|
||||
return result
|
||||
result = user_function(*args, **kwds)
|
||||
with lock:
|
||||
if key in cache:
|
||||
# Getting here means that this same key was added to the
|
||||
# cache while the lock was released. Since the link
|
||||
# update is already done, we need only return the
|
||||
# computed result and update the count of misses.
|
||||
pass
|
||||
elif full:
|
||||
# Use the old root to store the new key and result.
|
||||
oldroot = root
|
||||
oldroot[KEY] = key
|
||||
oldroot[RESULT] = result
|
||||
# Empty the oldest link and make it the new root.
|
||||
# Keep a reference to the old key and old result to
|
||||
# prevent their ref counts from going to zero during the
|
||||
# update. That will prevent potentially arbitrary object
|
||||
# clean-up code (i.e. __del__) from running while we're
|
||||
# still adjusting the links.
|
||||
root = oldroot[NEXT]
|
||||
oldkey = root[KEY]
|
||||
oldresult = root[RESULT]
|
||||
root[KEY] = root[RESULT] = None
|
||||
# Now update the cache dictionary.
|
||||
del cache[oldkey]
|
||||
# Save the potentially reentrant cache[key] assignment
|
||||
# for last, after the root and links have been put in
|
||||
# a consistent state.
|
||||
cache[key] = oldroot
|
||||
else:
|
||||
# Put result in a new link at the front of the queue.
|
||||
last = root[PREV]
|
||||
link = [last, root, key, result]
|
||||
last[NEXT] = root[PREV] = cache[key] = link
|
||||
# Use the cache_len bound method instead of the len() function
|
||||
# which could potentially be wrapped in an lru_cache itself.
|
||||
full = (cache_len() >= maxsize)
|
||||
misses += 1
|
||||
return result
|
||||
|
||||
def cache_info():
|
||||
"""Report cache statistics"""
|
||||
with lock:
|
||||
return _CacheInfo(hits, misses, maxsize, cache_len())
|
||||
|
||||
def cache_clear():
|
||||
"""Clear the cache and cache statistics"""
|
||||
nonlocal hits, misses, full
|
||||
with lock:
|
||||
cache.clear()
|
||||
root[:] = [root, root, None, None]
|
||||
hits = misses = 0
|
||||
full = False
|
||||
|
||||
wrapper.cache_info = cache_info
|
||||
wrapper.cache_clear = cache_clear
|
||||
return wrapper
|
||||
|
||||
try:
|
||||
from _functools import _lru_cache_wrapper
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
################################################################################
|
||||
### singledispatch() - single-dispatch generic function decorator
|
||||
################################################################################
|
||||
|
||||
def _c3_merge(sequences):
|
||||
"""Merges MROs in *sequences* to a single MRO using the C3 algorithm.
|
||||
|
||||
Adapted from http://www.python.org/download/releases/2.3/mro/.
|
||||
|
||||
"""
|
||||
result = []
|
||||
while True:
|
||||
sequences = [s for s in sequences if s] # purge empty sequences
|
||||
if not sequences:
|
||||
return result
|
||||
for s1 in sequences: # find merge candidates among seq heads
|
||||
candidate = s1[0]
|
||||
for s2 in sequences:
|
||||
if candidate in s2[1:]:
|
||||
candidate = None
|
||||
break # reject the current head, it appears later
|
||||
else:
|
||||
break
|
||||
if candidate is None:
|
||||
raise RuntimeError("Inconsistent hierarchy")
|
||||
result.append(candidate)
|
||||
# remove the chosen candidate
|
||||
for seq in sequences:
|
||||
if seq[0] == candidate:
|
||||
del seq[0]
|
||||
|
||||
def _c3_mro(cls, abcs=None):
|
||||
"""Computes the method resolution order using extended C3 linearization.
|
||||
|
||||
If no *abcs* are given, the algorithm works exactly like the built-in C3
|
||||
linearization used for method resolution.
|
||||
|
||||
If given, *abcs* is a list of abstract base classes that should be inserted
|
||||
into the resulting MRO. Unrelated ABCs are ignored and don't end up in the
|
||||
result. The algorithm inserts ABCs where their functionality is introduced,
|
||||
i.e. issubclass(cls, abc) returns True for the class itself but returns
|
||||
False for all its direct base classes. Implicit ABCs for a given class
|
||||
(either registered or inferred from the presence of a special method like
|
||||
__len__) are inserted directly after the last ABC explicitly listed in the
|
||||
MRO of said class. If two implicit ABCs end up next to each other in the
|
||||
resulting MRO, their ordering depends on the order of types in *abcs*.
|
||||
|
||||
"""
|
||||
for i, base in enumerate(reversed(cls.__bases__)):
|
||||
if hasattr(base, '__abstractmethods__'):
|
||||
boundary = len(cls.__bases__) - i
|
||||
break # Bases up to the last explicit ABC are considered first.
|
||||
else:
|
||||
boundary = 0
|
||||
abcs = list(abcs) if abcs else []
|
||||
explicit_bases = list(cls.__bases__[:boundary])
|
||||
abstract_bases = []
|
||||
other_bases = list(cls.__bases__[boundary:])
|
||||
for base in abcs:
|
||||
if issubclass(cls, base) and not any(
|
||||
issubclass(b, base) for b in cls.__bases__
|
||||
):
|
||||
# If *cls* is the class that introduces behaviour described by
|
||||
# an ABC *base*, insert said ABC to its MRO.
|
||||
abstract_bases.append(base)
|
||||
for base in abstract_bases:
|
||||
abcs.remove(base)
|
||||
explicit_c3_mros = [_c3_mro(base, abcs=abcs) for base in explicit_bases]
|
||||
abstract_c3_mros = [_c3_mro(base, abcs=abcs) for base in abstract_bases]
|
||||
other_c3_mros = [_c3_mro(base, abcs=abcs) for base in other_bases]
|
||||
return _c3_merge(
|
||||
[[cls]] +
|
||||
explicit_c3_mros + abstract_c3_mros + other_c3_mros +
|
||||
[explicit_bases] + [abstract_bases] + [other_bases]
|
||||
)
|
||||
|
||||
def _compose_mro(cls, types):
|
||||
"""Calculates the method resolution order for a given class *cls*.
|
||||
|
||||
Includes relevant abstract base classes (with their respective bases) from
|
||||
the *types* iterable. Uses a modified C3 linearization algorithm.
|
||||
|
||||
"""
|
||||
bases = set(cls.__mro__)
|
||||
# Remove entries which are already present in the __mro__ or unrelated.
|
||||
def is_related(typ):
|
||||
return (typ not in bases and hasattr(typ, '__mro__')
|
||||
and issubclass(cls, typ))
|
||||
types = [n for n in types if is_related(n)]
|
||||
# Remove entries which are strict bases of other entries (they will end up
|
||||
# in the MRO anyway.
|
||||
def is_strict_base(typ):
|
||||
for other in types:
|
||||
if typ != other and typ in other.__mro__:
|
||||
return True
|
||||
return False
|
||||
types = [n for n in types if not is_strict_base(n)]
|
||||
# Subclasses of the ABCs in *types* which are also implemented by
|
||||
# *cls* can be used to stabilize ABC ordering.
|
||||
type_set = set(types)
|
||||
mro = []
|
||||
for typ in types:
|
||||
found = []
|
||||
for sub in typ.__subclasses__():
|
||||
if sub not in bases and issubclass(cls, sub):
|
||||
found.append([s for s in sub.__mro__ if s in type_set])
|
||||
if not found:
|
||||
mro.append(typ)
|
||||
continue
|
||||
# Favor subclasses with the biggest number of useful bases
|
||||
found.sort(key=len, reverse=True)
|
||||
for sub in found:
|
||||
for subcls in sub:
|
||||
if subcls not in mro:
|
||||
mro.append(subcls)
|
||||
return _c3_mro(cls, abcs=mro)
|
||||
|
||||
def _find_impl(cls, registry):
|
||||
"""Returns the best matching implementation from *registry* for type *cls*.
|
||||
|
||||
Where there is no registered implementation for a specific type, its method
|
||||
resolution order is used to find a more generic implementation.
|
||||
|
||||
Note: if *registry* does not contain an implementation for the base
|
||||
*object* type, this function may return None.
|
||||
|
||||
"""
|
||||
mro = _compose_mro(cls, registry.keys())
|
||||
match = None
|
||||
for t in mro:
|
||||
if match is not None:
|
||||
# If *match* is an implicit ABC but there is another unrelated,
|
||||
# equally matching implicit ABC, refuse the temptation to guess.
|
||||
if (t in registry and t not in cls.__mro__
|
||||
and match not in cls.__mro__
|
||||
and not issubclass(match, t)):
|
||||
raise RuntimeError("Ambiguous dispatch: {} or {}".format(
|
||||
match, t))
|
||||
break
|
||||
if t in registry:
|
||||
match = t
|
||||
return registry.get(match)
|
||||
|
||||
def singledispatch(func):
|
||||
"""Single-dispatch generic function decorator.
|
||||
|
||||
Transforms a function into a generic function, which can have different
|
||||
behaviours depending upon the type of its first argument. The decorated
|
||||
function acts as the default implementation, and additional
|
||||
implementations can be registered using the register() attribute of the
|
||||
generic function.
|
||||
"""
|
||||
# There are many programs that use functools without singledispatch, so we
|
||||
# trade-off making singledispatch marginally slower for the benefit of
|
||||
# making start-up of such applications slightly faster.
|
||||
import types, weakref
|
||||
|
||||
registry = {}
|
||||
dispatch_cache = weakref.WeakKeyDictionary()
|
||||
cache_token = None
|
||||
|
||||
def dispatch(cls):
|
||||
"""generic_func.dispatch(cls) -> <function implementation>
|
||||
|
||||
Runs the dispatch algorithm to return the best available implementation
|
||||
for the given *cls* registered on *generic_func*.
|
||||
|
||||
"""
|
||||
nonlocal cache_token
|
||||
if cache_token is not None:
|
||||
current_token = get_cache_token()
|
||||
if cache_token != current_token:
|
||||
dispatch_cache.clear()
|
||||
cache_token = current_token
|
||||
try:
|
||||
impl = dispatch_cache[cls]
|
||||
except KeyError:
|
||||
try:
|
||||
impl = registry[cls]
|
||||
except KeyError:
|
||||
impl = _find_impl(cls, registry)
|
||||
dispatch_cache[cls] = impl
|
||||
return impl
|
||||
|
||||
def register(cls, func=None):
|
||||
"""generic_func.register(cls, func) -> func
|
||||
|
||||
Registers a new implementation for the given *cls* on a *generic_func*.
|
||||
|
||||
"""
|
||||
nonlocal cache_token
|
||||
if func is None:
|
||||
if isinstance(cls, type):
|
||||
return lambda f: register(cls, f)
|
||||
ann = getattr(cls, '__annotations__', {})
|
||||
if not ann:
|
||||
raise TypeError(
|
||||
f"Invalid first argument to `register()`: {cls!r}. "
|
||||
f"Use either `@register(some_class)` or plain `@register` "
|
||||
f"on an annotated function."
|
||||
)
|
||||
func = cls
|
||||
|
||||
# only import typing if annotation parsing is necessary
|
||||
from typing import get_type_hints
|
||||
argname, cls = next(iter(get_type_hints(func).items()))
|
||||
assert isinstance(cls, type), (
|
||||
f"Invalid annotation for {argname!r}. {cls!r} is not a class."
|
||||
)
|
||||
registry[cls] = func
|
||||
if cache_token is None and hasattr(cls, '__abstractmethods__'):
|
||||
cache_token = get_cache_token()
|
||||
dispatch_cache.clear()
|
||||
return func
|
||||
|
||||
def wrapper(*args, **kw):
|
||||
return dispatch(args[0].__class__)(*args, **kw)
|
||||
|
||||
registry[object] = func
|
||||
wrapper.register = register
|
||||
wrapper.dispatch = dispatch
|
||||
wrapper.registry = types.MappingProxyType(registry)
|
||||
wrapper._clear_cache = dispatch_cache.clear
|
||||
update_wrapper(wrapper, func)
|
||||
return wrapper
|
||||
@@ -0,0 +1,177 @@
|
||||
"""Cache lines from Python source files.
|
||||
|
||||
This is intended to read lines from modules imported -- hence if a filename
|
||||
is not found, it will look down the module search path for a file by
|
||||
that name.
|
||||
"""
|
||||
|
||||
import functools
|
||||
import sys
|
||||
import os
|
||||
import tokenize
|
||||
|
||||
__all__ = ["getline", "clearcache", "checkcache"]
|
||||
|
||||
def getline(filename, lineno, module_globals=None):
|
||||
lines = getlines(filename, module_globals)
|
||||
if 1 <= lineno <= len(lines):
|
||||
return lines[lineno-1]
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
# The cache
|
||||
|
||||
# The cache. Maps filenames to either a thunk which will provide source code,
|
||||
# or a tuple (size, mtime, lines, fullname) once loaded.
|
||||
cache = {}
|
||||
|
||||
|
||||
def clearcache():
|
||||
"""Clear the cache entirely."""
|
||||
|
||||
global cache
|
||||
cache = {}
|
||||
|
||||
|
||||
def getlines(filename, module_globals=None):
|
||||
"""Get the lines for a Python source file from the cache.
|
||||
Update the cache if it doesn't contain an entry for this file already."""
|
||||
|
||||
if filename in cache:
|
||||
entry = cache[filename]
|
||||
if len(entry) != 1:
|
||||
return cache[filename][2]
|
||||
|
||||
try:
|
||||
return updatecache(filename, module_globals)
|
||||
except MemoryError:
|
||||
clearcache()
|
||||
return []
|
||||
|
||||
|
||||
def checkcache(filename=None):
|
||||
"""Discard cache entries that are out of date.
|
||||
(This is not checked upon each call!)"""
|
||||
|
||||
if filename is None:
|
||||
filenames = list(cache.keys())
|
||||
else:
|
||||
if filename in cache:
|
||||
filenames = [filename]
|
||||
else:
|
||||
return
|
||||
|
||||
for filename in filenames:
|
||||
entry = cache[filename]
|
||||
if len(entry) == 1:
|
||||
# lazy cache entry, leave it lazy.
|
||||
continue
|
||||
size, mtime, lines, fullname = entry
|
||||
if mtime is None:
|
||||
continue # no-op for files loaded via a __loader__
|
||||
try:
|
||||
stat = os.stat(fullname)
|
||||
except OSError:
|
||||
del cache[filename]
|
||||
continue
|
||||
if size != stat.st_size or mtime != stat.st_mtime:
|
||||
del cache[filename]
|
||||
|
||||
|
||||
def updatecache(filename, module_globals=None):
|
||||
"""Update a cache entry and return its list of lines.
|
||||
If something's wrong, print a message, discard the cache entry,
|
||||
and return an empty list."""
|
||||
|
||||
if filename in cache:
|
||||
if len(cache[filename]) != 1:
|
||||
del cache[filename]
|
||||
if not filename or (filename.startswith('<') and filename.endswith('>')):
|
||||
return []
|
||||
|
||||
fullname = filename
|
||||
try:
|
||||
stat = os.stat(fullname)
|
||||
except OSError:
|
||||
basename = filename
|
||||
|
||||
# Realise a lazy loader based lookup if there is one
|
||||
# otherwise try to lookup right now.
|
||||
if lazycache(filename, module_globals):
|
||||
try:
|
||||
data = cache[filename][0]()
|
||||
except (ImportError, OSError):
|
||||
pass
|
||||
else:
|
||||
if data is None:
|
||||
# No luck, the PEP302 loader cannot find the source
|
||||
# for this module.
|
||||
return []
|
||||
cache[filename] = (
|
||||
len(data), None,
|
||||
[line+'\n' for line in data.splitlines()], fullname
|
||||
)
|
||||
return cache[filename][2]
|
||||
|
||||
# Try looking through the module search path, which is only useful
|
||||
# when handling a relative filename.
|
||||
if os.path.isabs(filename):
|
||||
return []
|
||||
|
||||
for dirname in sys.path:
|
||||
try:
|
||||
fullname = os.path.join(dirname, basename)
|
||||
except (TypeError, AttributeError):
|
||||
# Not sufficiently string-like to do anything useful with.
|
||||
continue
|
||||
try:
|
||||
stat = os.stat(fullname)
|
||||
break
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
return []
|
||||
try:
|
||||
with tokenize.open(fullname) as fp:
|
||||
lines = fp.readlines()
|
||||
except OSError:
|
||||
return []
|
||||
if lines and not lines[-1].endswith('\n'):
|
||||
lines[-1] += '\n'
|
||||
size, mtime = stat.st_size, stat.st_mtime
|
||||
cache[filename] = size, mtime, lines, fullname
|
||||
return lines
|
||||
|
||||
|
||||
def lazycache(filename, module_globals):
|
||||
"""Seed the cache for filename with module_globals.
|
||||
|
||||
The module loader will be asked for the source only when getlines is
|
||||
called, not immediately.
|
||||
|
||||
If there is an entry in the cache already, it is not altered.
|
||||
|
||||
:return: True if a lazy load is registered in the cache,
|
||||
otherwise False. To register such a load a module loader with a
|
||||
get_source method must be found, the filename must be a cachable
|
||||
filename, and the filename must not be already cached.
|
||||
"""
|
||||
if filename in cache:
|
||||
if len(cache[filename]) == 1:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
if not filename or (filename.startswith('<') and filename.endswith('>')):
|
||||
return False
|
||||
# Try for a __loader__, if available
|
||||
if module_globals and '__loader__' in module_globals:
|
||||
name = module_globals.get('__name__')
|
||||
loader = module_globals['__loader__']
|
||||
get_source = getattr(loader, 'get_source', None)
|
||||
|
||||
if name and get_source:
|
||||
get_lines = functools.partial(get_source, name)
|
||||
cache[filename] = (get_lines,)
|
||||
return True
|
||||
return False
|
||||
+554
@@ -0,0 +1,554 @@
|
||||
"""Python part of the warnings subsystem."""
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
__all__ = ["warn", "warn_explicit", "showwarning",
|
||||
"formatwarning", "filterwarnings", "simplefilter",
|
||||
"resetwarnings", "catch_warnings"]
|
||||
|
||||
def showwarning(message, category, filename, lineno, file=None, line=None):
|
||||
"""Hook to write a warning to a file; replace if you like."""
|
||||
msg = WarningMessage(message, category, filename, lineno, file, line)
|
||||
_showwarnmsg_impl(msg)
|
||||
|
||||
def formatwarning(message, category, filename, lineno, line=None):
|
||||
"""Function to format a warning the standard way."""
|
||||
msg = WarningMessage(message, category, filename, lineno, None, line)
|
||||
return _formatwarnmsg_impl(msg)
|
||||
|
||||
def _showwarnmsg_impl(msg):
|
||||
file = msg.file
|
||||
if file is None:
|
||||
file = sys.stderr
|
||||
if file is None:
|
||||
# sys.stderr is None when run with pythonw.exe:
|
||||
# warnings get lost
|
||||
return
|
||||
text = _formatwarnmsg(msg)
|
||||
try:
|
||||
file.write(text)
|
||||
except OSError:
|
||||
# the file (probably stderr) is invalid - this warning gets lost.
|
||||
pass
|
||||
|
||||
def _formatwarnmsg_impl(msg):
|
||||
s = ("%s:%s: %s: %s\n"
|
||||
% (msg.filename, msg.lineno, msg.category.__name__,
|
||||
msg.message))
|
||||
|
||||
if msg.line is None:
|
||||
try:
|
||||
import linecache
|
||||
line = linecache.getline(msg.filename, msg.lineno)
|
||||
except Exception:
|
||||
# When a warning is logged during Python shutdown, linecache
|
||||
# and the import machinery don't work anymore
|
||||
line = None
|
||||
linecache = None
|
||||
else:
|
||||
line = msg.line
|
||||
if line:
|
||||
line = line.strip()
|
||||
s += " %s\n" % line
|
||||
|
||||
if msg.source is not None:
|
||||
try:
|
||||
import tracemalloc
|
||||
tb = tracemalloc.get_object_traceback(msg.source)
|
||||
except Exception:
|
||||
# When a warning is logged during Python shutdown, tracemalloc
|
||||
# and the import machinery don't work anymore
|
||||
tb = None
|
||||
|
||||
if tb is not None:
|
||||
s += 'Object allocated at (most recent call last):\n'
|
||||
for frame in tb:
|
||||
s += (' File "%s", lineno %s\n'
|
||||
% (frame.filename, frame.lineno))
|
||||
|
||||
try:
|
||||
if linecache is not None:
|
||||
line = linecache.getline(frame.filename, frame.lineno)
|
||||
else:
|
||||
line = None
|
||||
except Exception:
|
||||
line = None
|
||||
if line:
|
||||
line = line.strip()
|
||||
s += ' %s\n' % line
|
||||
return s
|
||||
|
||||
# Keep a reference to check if the function was replaced
|
||||
_showwarning_orig = showwarning
|
||||
|
||||
def _showwarnmsg(msg):
|
||||
"""Hook to write a warning to a file; replace if you like."""
|
||||
try:
|
||||
sw = showwarning
|
||||
except NameError:
|
||||
pass
|
||||
else:
|
||||
if sw is not _showwarning_orig:
|
||||
# warnings.showwarning() was replaced
|
||||
if not callable(sw):
|
||||
raise TypeError("warnings.showwarning() must be set to a "
|
||||
"function or method")
|
||||
|
||||
sw(msg.message, msg.category, msg.filename, msg.lineno,
|
||||
msg.file, msg.line)
|
||||
return
|
||||
_showwarnmsg_impl(msg)
|
||||
|
||||
# Keep a reference to check if the function was replaced
|
||||
_formatwarning_orig = formatwarning
|
||||
|
||||
def _formatwarnmsg(msg):
|
||||
"""Function to format a warning the standard way."""
|
||||
try:
|
||||
fw = formatwarning
|
||||
except NameError:
|
||||
pass
|
||||
else:
|
||||
if fw is not _formatwarning_orig:
|
||||
# warnings.formatwarning() was replaced
|
||||
return fw(msg.message, msg.category,
|
||||
msg.filename, msg.lineno, line=msg.line)
|
||||
return _formatwarnmsg_impl(msg)
|
||||
|
||||
def filterwarnings(action, message="", category=Warning, module="", lineno=0,
|
||||
append=False):
|
||||
"""Insert an entry into the list of warnings filters (at the front).
|
||||
|
||||
'action' -- one of "error", "ignore", "always", "default", "module",
|
||||
or "once"
|
||||
'message' -- a regex that the warning message must match
|
||||
'category' -- a class that the warning must be a subclass of
|
||||
'module' -- a regex that the module name must match
|
||||
'lineno' -- an integer line number, 0 matches all warnings
|
||||
'append' -- if true, append to the list of filters
|
||||
"""
|
||||
assert action in ("error", "ignore", "always", "default", "module",
|
||||
"once"), "invalid action: %r" % (action,)
|
||||
assert isinstance(message, str), "message must be a string"
|
||||
assert isinstance(category, type), "category must be a class"
|
||||
assert issubclass(category, Warning), "category must be a Warning subclass"
|
||||
assert isinstance(module, str), "module must be a string"
|
||||
assert isinstance(lineno, int) and lineno >= 0, \
|
||||
"lineno must be an int >= 0"
|
||||
|
||||
if message or module:
|
||||
import re
|
||||
|
||||
if message:
|
||||
message = re.compile(message, re.I)
|
||||
else:
|
||||
message = None
|
||||
if module:
|
||||
module = re.compile(module)
|
||||
else:
|
||||
module = None
|
||||
|
||||
_add_filter(action, message, category, module, lineno, append=append)
|
||||
|
||||
def simplefilter(action, category=Warning, lineno=0, append=False):
|
||||
"""Insert a simple entry into the list of warnings filters (at the front).
|
||||
|
||||
A simple filter matches all modules and messages.
|
||||
'action' -- one of "error", "ignore", "always", "default", "module",
|
||||
or "once"
|
||||
'category' -- a class that the warning must be a subclass of
|
||||
'lineno' -- an integer line number, 0 matches all warnings
|
||||
'append' -- if true, append to the list of filters
|
||||
"""
|
||||
assert action in ("error", "ignore", "always", "default", "module",
|
||||
"once"), "invalid action: %r" % (action,)
|
||||
assert isinstance(lineno, int) and lineno >= 0, \
|
||||
"lineno must be an int >= 0"
|
||||
_add_filter(action, None, category, None, lineno, append=append)
|
||||
|
||||
def _add_filter(*item, append):
|
||||
# Remove possible duplicate filters, so new one will be placed
|
||||
# in correct place. If append=True and duplicate exists, do nothing.
|
||||
if not append:
|
||||
try:
|
||||
filters.remove(item)
|
||||
except ValueError:
|
||||
pass
|
||||
filters.insert(0, item)
|
||||
else:
|
||||
if item not in filters:
|
||||
filters.append(item)
|
||||
_filters_mutated()
|
||||
|
||||
def resetwarnings():
|
||||
"""Clear the list of warning filters, so that no filters are active."""
|
||||
filters[:] = []
|
||||
_filters_mutated()
|
||||
|
||||
class _OptionError(Exception):
|
||||
"""Exception used by option processing helpers."""
|
||||
pass
|
||||
|
||||
# Helper to process -W options passed via sys.warnoptions
|
||||
def _processoptions(args):
|
||||
for arg in args:
|
||||
try:
|
||||
_setoption(arg)
|
||||
except _OptionError as msg:
|
||||
print("Invalid -W option ignored:", msg, file=sys.stderr)
|
||||
|
||||
# Helper for _processoptions()
|
||||
def _setoption(arg):
|
||||
import re
|
||||
parts = arg.split(':')
|
||||
if len(parts) > 5:
|
||||
raise _OptionError("too many fields (max 5): %r" % (arg,))
|
||||
while len(parts) < 5:
|
||||
parts.append('')
|
||||
action, message, category, module, lineno = [s.strip()
|
||||
for s in parts]
|
||||
action = _getaction(action)
|
||||
message = re.escape(message)
|
||||
category = _getcategory(category)
|
||||
module = re.escape(module)
|
||||
if module:
|
||||
module = module + '$'
|
||||
if lineno:
|
||||
try:
|
||||
lineno = int(lineno)
|
||||
if lineno < 0:
|
||||
raise ValueError
|
||||
except (ValueError, OverflowError):
|
||||
raise _OptionError("invalid lineno %r" % (lineno,)) from None
|
||||
else:
|
||||
lineno = 0
|
||||
filterwarnings(action, message, category, module, lineno)
|
||||
|
||||
# Helper for _setoption()
|
||||
def _getaction(action):
|
||||
if not action:
|
||||
return "default"
|
||||
if action == "all": return "always" # Alias
|
||||
for a in ('default', 'always', 'ignore', 'module', 'once', 'error'):
|
||||
if a.startswith(action):
|
||||
return a
|
||||
raise _OptionError("invalid action: %r" % (action,))
|
||||
|
||||
# Helper for _setoption()
|
||||
def _getcategory(category):
|
||||
import re
|
||||
if not category:
|
||||
return Warning
|
||||
if re.match("^[a-zA-Z0-9_]+$", category):
|
||||
try:
|
||||
cat = eval(category)
|
||||
except NameError:
|
||||
raise _OptionError("unknown warning category: %r" % (category,)) from None
|
||||
else:
|
||||
i = category.rfind(".")
|
||||
module = category[:i]
|
||||
klass = category[i+1:]
|
||||
try:
|
||||
m = __import__(module, None, None, [klass])
|
||||
except ImportError:
|
||||
raise _OptionError("invalid module name: %r" % (module,)) from None
|
||||
try:
|
||||
cat = getattr(m, klass)
|
||||
except AttributeError:
|
||||
raise _OptionError("unknown warning category: %r" % (category,)) from None
|
||||
if not issubclass(cat, Warning):
|
||||
raise _OptionError("invalid warning category: %r" % (category,))
|
||||
return cat
|
||||
|
||||
|
||||
def _is_internal_frame(frame):
|
||||
"""Signal whether the frame is an internal CPython implementation detail."""
|
||||
filename = frame.f_code.co_filename
|
||||
return 'importlib' in filename and '_bootstrap' in filename
|
||||
|
||||
|
||||
def _next_external_frame(frame):
|
||||
"""Find the next frame that doesn't involve CPython internals."""
|
||||
frame = frame.f_back
|
||||
while frame is not None and _is_internal_frame(frame):
|
||||
frame = frame.f_back
|
||||
return frame
|
||||
|
||||
|
||||
# Code typically replaced by _warnings
|
||||
def warn(message, category=None, stacklevel=1, source=None):
|
||||
"""Issue a warning, or maybe ignore it or raise an exception."""
|
||||
# Check if message is already a Warning object
|
||||
if isinstance(message, Warning):
|
||||
category = message.__class__
|
||||
# Check category argument
|
||||
if category is None:
|
||||
category = UserWarning
|
||||
if not (isinstance(category, type) and issubclass(category, Warning)):
|
||||
raise TypeError("category must be a Warning subclass, "
|
||||
"not '{:s}'".format(type(category).__name__))
|
||||
# Get context information
|
||||
try:
|
||||
if stacklevel <= 1 or _is_internal_frame(sys._getframe(1)):
|
||||
# If frame is too small to care or if the warning originated in
|
||||
# internal code, then do not try to hide any frames.
|
||||
frame = sys._getframe(stacklevel)
|
||||
else:
|
||||
frame = sys._getframe(1)
|
||||
# Look for one frame less since the above line starts us off.
|
||||
for x in range(stacklevel-1):
|
||||
frame = _next_external_frame(frame)
|
||||
if frame is None:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
globals = sys.__dict__
|
||||
lineno = 1
|
||||
else:
|
||||
globals = frame.f_globals
|
||||
lineno = frame.f_lineno
|
||||
if '__name__' in globals:
|
||||
module = globals['__name__']
|
||||
else:
|
||||
module = "<string>"
|
||||
filename = globals.get('__file__')
|
||||
if filename:
|
||||
fnl = filename.lower()
|
||||
if fnl.endswith(".pyc"):
|
||||
filename = filename[:-1]
|
||||
else:
|
||||
if module == "__main__":
|
||||
try:
|
||||
filename = sys.argv[0]
|
||||
except AttributeError:
|
||||
# embedded interpreters don't have sys.argv, see bug #839151
|
||||
filename = '__main__'
|
||||
if not filename:
|
||||
filename = module
|
||||
registry = globals.setdefault("__warningregistry__", {})
|
||||
warn_explicit(message, category, filename, lineno, module, registry,
|
||||
globals, source)
|
||||
|
||||
def warn_explicit(message, category, filename, lineno,
|
||||
module=None, registry=None, module_globals=None,
|
||||
source=None):
|
||||
lineno = int(lineno)
|
||||
if module is None:
|
||||
module = filename or "<unknown>"
|
||||
if module[-3:].lower() == ".py":
|
||||
module = module[:-3] # XXX What about leading pathname?
|
||||
if registry is None:
|
||||
registry = {}
|
||||
if registry.get('version', 0) != _filters_version:
|
||||
registry.clear()
|
||||
registry['version'] = _filters_version
|
||||
if isinstance(message, Warning):
|
||||
text = str(message)
|
||||
category = message.__class__
|
||||
else:
|
||||
text = message
|
||||
message = category(message)
|
||||
key = (text, category, lineno)
|
||||
# Quick test for common case
|
||||
if registry.get(key):
|
||||
return
|
||||
# Search the filters
|
||||
for item in filters:
|
||||
action, msg, cat, mod, ln = item
|
||||
if ((msg is None or msg.match(text)) and
|
||||
issubclass(category, cat) and
|
||||
(mod is None or mod.match(module)) and
|
||||
(ln == 0 or lineno == ln)):
|
||||
break
|
||||
else:
|
||||
action = defaultaction
|
||||
# Early exit actions
|
||||
if action == "ignore":
|
||||
return
|
||||
|
||||
# Prime the linecache for formatting, in case the
|
||||
# "file" is actually in a zipfile or something.
|
||||
import linecache
|
||||
linecache.getlines(filename, module_globals)
|
||||
|
||||
if action == "error":
|
||||
raise message
|
||||
# Other actions
|
||||
if action == "once":
|
||||
registry[key] = 1
|
||||
oncekey = (text, category)
|
||||
if onceregistry.get(oncekey):
|
||||
return
|
||||
onceregistry[oncekey] = 1
|
||||
elif action == "always":
|
||||
pass
|
||||
elif action == "module":
|
||||
registry[key] = 1
|
||||
altkey = (text, category, 0)
|
||||
if registry.get(altkey):
|
||||
return
|
||||
registry[altkey] = 1
|
||||
elif action == "default":
|
||||
registry[key] = 1
|
||||
else:
|
||||
# Unrecognized actions are errors
|
||||
raise RuntimeError(
|
||||
"Unrecognized action (%r) in warnings.filters:\n %s" %
|
||||
(action, item))
|
||||
# Print message and context
|
||||
msg = WarningMessage(message, category, filename, lineno, source)
|
||||
_showwarnmsg(msg)
|
||||
|
||||
|
||||
class WarningMessage(object):
|
||||
|
||||
_WARNING_DETAILS = ("message", "category", "filename", "lineno", "file",
|
||||
"line", "source")
|
||||
|
||||
def __init__(self, message, category, filename, lineno, file=None,
|
||||
line=None, source=None):
|
||||
self.message = message
|
||||
self.category = category
|
||||
self.filename = filename
|
||||
self.lineno = lineno
|
||||
self.file = file
|
||||
self.line = line
|
||||
self.source = source
|
||||
self._category_name = category.__name__ if category else None
|
||||
|
||||
def __str__(self):
|
||||
return ("{message : %r, category : %r, filename : %r, lineno : %s, "
|
||||
"line : %r}" % (self.message, self._category_name,
|
||||
self.filename, self.lineno, self.line))
|
||||
|
||||
|
||||
class catch_warnings(object):
|
||||
|
||||
"""A context manager that copies and restores the warnings filter upon
|
||||
exiting the context.
|
||||
|
||||
The 'record' argument specifies whether warnings should be captured by a
|
||||
custom implementation of warnings.showwarning() and be appended to a list
|
||||
returned by the context manager. Otherwise None is returned by the context
|
||||
manager. The objects appended to the list are arguments whose attributes
|
||||
mirror the arguments to showwarning().
|
||||
|
||||
The 'module' argument is to specify an alternative module to the module
|
||||
named 'warnings' and imported under that name. This argument is only useful
|
||||
when testing the warnings module itself.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *, record=False, module=None):
|
||||
"""Specify whether to record warnings and if an alternative module
|
||||
should be used other than sys.modules['warnings'].
|
||||
|
||||
For compatibility with Python 3.0, please consider all arguments to be
|
||||
keyword-only.
|
||||
|
||||
"""
|
||||
self._record = record
|
||||
self._module = sys.modules['warnings'] if module is None else module
|
||||
self._entered = False
|
||||
|
||||
def __repr__(self):
|
||||
args = []
|
||||
if self._record:
|
||||
args.append("record=True")
|
||||
if self._module is not sys.modules['warnings']:
|
||||
args.append("module=%r" % self._module)
|
||||
name = type(self).__name__
|
||||
return "%s(%s)" % (name, ", ".join(args))
|
||||
|
||||
def __enter__(self):
|
||||
if self._entered:
|
||||
raise RuntimeError("Cannot enter %r twice" % self)
|
||||
self._entered = True
|
||||
self._filters = self._module.filters
|
||||
self._module.filters = self._filters[:]
|
||||
self._module._filters_mutated()
|
||||
self._showwarning = self._module.showwarning
|
||||
self._showwarnmsg_impl = self._module._showwarnmsg_impl
|
||||
if self._record:
|
||||
log = []
|
||||
self._module._showwarnmsg_impl = log.append
|
||||
# Reset showwarning() to the default implementation to make sure
|
||||
# that _showwarnmsg() calls _showwarnmsg_impl()
|
||||
self._module.showwarning = self._module._showwarning_orig
|
||||
return log
|
||||
else:
|
||||
return None
|
||||
|
||||
def __exit__(self, *exc_info):
|
||||
if not self._entered:
|
||||
raise RuntimeError("Cannot exit %r without entering first" % self)
|
||||
self._module.filters = self._filters
|
||||
self._module._filters_mutated()
|
||||
self._module.showwarning = self._showwarning
|
||||
self._module._showwarnmsg_impl = self._showwarnmsg_impl
|
||||
|
||||
|
||||
# Private utility function called by _PyErr_WarnUnawaitedCoroutine
|
||||
def _warn_unawaited_coroutine(coro):
|
||||
msg_lines = [
|
||||
f"coroutine '{coro.__qualname__}' was never awaited\n"
|
||||
]
|
||||
if coro.cr_origin is not None:
|
||||
import linecache, traceback
|
||||
def extract():
|
||||
for filename, lineno, funcname in reversed(coro.cr_origin):
|
||||
line = linecache.getline(filename, lineno)
|
||||
yield (filename, lineno, funcname, line)
|
||||
msg_lines.append("Coroutine created at (most recent call last)\n")
|
||||
msg_lines += traceback.format_list(list(extract()))
|
||||
msg = "".join(msg_lines).rstrip("\n")
|
||||
# Passing source= here means that if the user happens to have tracemalloc
|
||||
# enabled and tracking where the coroutine was created, the warning will
|
||||
# contain that traceback. This does mean that if they have *both*
|
||||
# coroutine origin tracking *and* tracemalloc enabled, they'll get two
|
||||
# partially-redundant tracebacks. If we wanted to be clever we could
|
||||
# probably detect this case and avoid it, but for now we don't bother.
|
||||
warn(msg, category=RuntimeWarning, stacklevel=2, source=coro)
|
||||
|
||||
|
||||
# filters contains a sequence of filter 5-tuples
|
||||
# The components of the 5-tuple are:
|
||||
# - an action: error, ignore, always, default, module, or once
|
||||
# - a compiled regex that must match the warning message
|
||||
# - a class representing the warning category
|
||||
# - a compiled regex that must match the module that is being warned
|
||||
# - a line number for the line being warning, or 0 to mean any line
|
||||
# If either if the compiled regexs are None, match anything.
|
||||
try:
|
||||
from _warnings import (filters, _defaultaction, _onceregistry,
|
||||
warn, warn_explicit, _filters_mutated)
|
||||
defaultaction = _defaultaction
|
||||
onceregistry = _onceregistry
|
||||
_warnings_defaults = True
|
||||
except ImportError:
|
||||
filters = []
|
||||
defaultaction = "default"
|
||||
onceregistry = {}
|
||||
|
||||
_filters_version = 1
|
||||
|
||||
def _filters_mutated():
|
||||
global _filters_version
|
||||
_filters_version += 1
|
||||
|
||||
_warnings_defaults = False
|
||||
|
||||
|
||||
# Module initialization
|
||||
_processoptions(sys.warnoptions)
|
||||
if not _warnings_defaults:
|
||||
# Several warning categories are ignored by default in regular builds
|
||||
if not hasattr(sys, 'gettotalrefcount'):
|
||||
filterwarnings("default", category=DeprecationWarning,
|
||||
module="__main__", append=1)
|
||||
simplefilter("ignore", category=DeprecationWarning, append=1)
|
||||
simplefilter("ignore", category=PendingDeprecationWarning, append=1)
|
||||
simplefilter("ignore", category=ImportWarning, append=1)
|
||||
simplefilter("ignore", category=ResourceWarning, append=1)
|
||||
|
||||
del _warnings_defaults
|
||||
+632
@@ -0,0 +1,632 @@
|
||||
"""Weak reference support for Python.
|
||||
|
||||
This module is an implementation of PEP 205:
|
||||
|
||||
http://www.python.org/dev/peps/pep-0205/
|
||||
"""
|
||||
|
||||
# Naming convention: Variables named "wr" are weak reference objects;
|
||||
# they are called this instead of "ref" to avoid name collisions with
|
||||
# the module-global ref() function imported from _weakref.
|
||||
|
||||
from _weakref import (
|
||||
getweakrefcount,
|
||||
getweakrefs,
|
||||
ref,
|
||||
proxy,
|
||||
CallableProxyType,
|
||||
ProxyType,
|
||||
ReferenceType,
|
||||
_remove_dead_weakref)
|
||||
|
||||
from _weakrefset import WeakSet, _IterationGuard
|
||||
|
||||
import _collections_abc # Import after _weakref to avoid circular import.
|
||||
import sys
|
||||
import itertools
|
||||
|
||||
ProxyTypes = (ProxyType, CallableProxyType)
|
||||
|
||||
__all__ = ["ref", "proxy", "getweakrefcount", "getweakrefs",
|
||||
"WeakKeyDictionary", "ReferenceType", "ProxyType",
|
||||
"CallableProxyType", "ProxyTypes", "WeakValueDictionary",
|
||||
"WeakSet", "WeakMethod", "finalize"]
|
||||
|
||||
|
||||
class WeakMethod(ref):
|
||||
"""
|
||||
A custom `weakref.ref` subclass which simulates a weak reference to
|
||||
a bound method, working around the lifetime problem of bound methods.
|
||||
"""
|
||||
|
||||
__slots__ = "_func_ref", "_meth_type", "_alive", "__weakref__"
|
||||
|
||||
def __new__(cls, meth, callback=None):
|
||||
try:
|
||||
obj = meth.__self__
|
||||
func = meth.__func__
|
||||
except AttributeError:
|
||||
raise TypeError("argument should be a bound method, not {}"
|
||||
.format(type(meth))) from None
|
||||
def _cb(arg):
|
||||
# The self-weakref trick is needed to avoid creating a reference
|
||||
# cycle.
|
||||
self = self_wr()
|
||||
if self._alive:
|
||||
self._alive = False
|
||||
if callback is not None:
|
||||
callback(self)
|
||||
self = ref.__new__(cls, obj, _cb)
|
||||
self._func_ref = ref(func, _cb)
|
||||
self._meth_type = type(meth)
|
||||
self._alive = True
|
||||
self_wr = ref(self)
|
||||
return self
|
||||
|
||||
def __call__(self):
|
||||
obj = super().__call__()
|
||||
func = self._func_ref()
|
||||
if obj is None or func is None:
|
||||
return None
|
||||
return self._meth_type(func, obj)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, WeakMethod):
|
||||
if not self._alive or not other._alive:
|
||||
return self is other
|
||||
return ref.__eq__(self, other) and self._func_ref == other._func_ref
|
||||
return False
|
||||
|
||||
def __ne__(self, other):
|
||||
if isinstance(other, WeakMethod):
|
||||
if not self._alive or not other._alive:
|
||||
return self is not other
|
||||
return ref.__ne__(self, other) or self._func_ref != other._func_ref
|
||||
return True
|
||||
|
||||
__hash__ = ref.__hash__
|
||||
|
||||
|
||||
class WeakValueDictionary(_collections_abc.MutableMapping):
|
||||
"""Mapping class that references values weakly.
|
||||
|
||||
Entries in the dictionary will be discarded when no strong
|
||||
reference to the value exists anymore
|
||||
"""
|
||||
# We inherit the constructor without worrying about the input
|
||||
# dictionary; since it uses our .update() method, we get the right
|
||||
# checks (if the other dictionary is a WeakValueDictionary,
|
||||
# objects are unwrapped on the way out, and we always wrap on the
|
||||
# way in).
|
||||
|
||||
def __init__(*args, **kw):
|
||||
if not args:
|
||||
raise TypeError("descriptor '__init__' of 'WeakValueDictionary' "
|
||||
"object needs an argument")
|
||||
self, *args = args
|
||||
if len(args) > 1:
|
||||
raise TypeError('expected at most 1 arguments, got %d' % len(args))
|
||||
def remove(wr, selfref=ref(self), _atomic_removal=_remove_dead_weakref):
|
||||
self = selfref()
|
||||
if self is not None:
|
||||
if self._iterating:
|
||||
self._pending_removals.append(wr.key)
|
||||
else:
|
||||
# Atomic removal is necessary since this function
|
||||
# can be called asynchronously by the GC
|
||||
_atomic_removal(d, wr.key)
|
||||
self._remove = remove
|
||||
# A list of keys to be removed
|
||||
self._pending_removals = []
|
||||
self._iterating = set()
|
||||
self.data = d = {}
|
||||
self.update(*args, **kw)
|
||||
|
||||
def _commit_removals(self):
|
||||
l = self._pending_removals
|
||||
d = self.data
|
||||
# We shouldn't encounter any KeyError, because this method should
|
||||
# always be called *before* mutating the dict.
|
||||
while l:
|
||||
key = l.pop()
|
||||
_remove_dead_weakref(d, key)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
o = self.data[key]()
|
||||
if o is None:
|
||||
raise KeyError(key)
|
||||
else:
|
||||
return o
|
||||
|
||||
def __delitem__(self, key):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
del self.data[key]
|
||||
|
||||
def __len__(self):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
return len(self.data)
|
||||
|
||||
def __contains__(self, key):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
try:
|
||||
o = self.data[key]()
|
||||
except KeyError:
|
||||
return False
|
||||
return o is not None
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s at %#x>" % (self.__class__.__name__, id(self))
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
self.data[key] = KeyedRef(value, self._remove, key)
|
||||
|
||||
def copy(self):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
new = WeakValueDictionary()
|
||||
for key, wr in self.data.items():
|
||||
o = wr()
|
||||
if o is not None:
|
||||
new[key] = o
|
||||
return new
|
||||
|
||||
__copy__ = copy
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
from copy import deepcopy
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
new = self.__class__()
|
||||
for key, wr in self.data.items():
|
||||
o = wr()
|
||||
if o is not None:
|
||||
new[deepcopy(key, memo)] = o
|
||||
return new
|
||||
|
||||
def get(self, key, default=None):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
try:
|
||||
wr = self.data[key]
|
||||
except KeyError:
|
||||
return default
|
||||
else:
|
||||
o = wr()
|
||||
if o is None:
|
||||
# This should only happen
|
||||
return default
|
||||
else:
|
||||
return o
|
||||
|
||||
def items(self):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
with _IterationGuard(self):
|
||||
for k, wr in self.data.items():
|
||||
v = wr()
|
||||
if v is not None:
|
||||
yield k, v
|
||||
|
||||
def keys(self):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
with _IterationGuard(self):
|
||||
for k, wr in self.data.items():
|
||||
if wr() is not None:
|
||||
yield k
|
||||
|
||||
__iter__ = keys
|
||||
|
||||
def itervaluerefs(self):
|
||||
"""Return an iterator that yields the weak references to the values.
|
||||
|
||||
The references are not guaranteed to be 'live' at the time
|
||||
they are used, so the result of calling the references needs
|
||||
to be checked before being used. This can be used to avoid
|
||||
creating references that will cause the garbage collector to
|
||||
keep the values around longer than needed.
|
||||
|
||||
"""
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
with _IterationGuard(self):
|
||||
yield from self.data.values()
|
||||
|
||||
def values(self):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
with _IterationGuard(self):
|
||||
for wr in self.data.values():
|
||||
obj = wr()
|
||||
if obj is not None:
|
||||
yield obj
|
||||
|
||||
def popitem(self):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
while True:
|
||||
key, wr = self.data.popitem()
|
||||
o = wr()
|
||||
if o is not None:
|
||||
return key, o
|
||||
|
||||
def pop(self, key, *args):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
try:
|
||||
o = self.data.pop(key)()
|
||||
except KeyError:
|
||||
o = None
|
||||
if o is None:
|
||||
if args:
|
||||
return args[0]
|
||||
else:
|
||||
raise KeyError(key)
|
||||
else:
|
||||
return o
|
||||
|
||||
def setdefault(self, key, default=None):
|
||||
try:
|
||||
o = self.data[key]()
|
||||
except KeyError:
|
||||
o = None
|
||||
if o is None:
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
self.data[key] = KeyedRef(default, self._remove, key)
|
||||
return default
|
||||
else:
|
||||
return o
|
||||
|
||||
def update(*args, **kwargs):
|
||||
if not args:
|
||||
raise TypeError("descriptor 'update' of 'WeakValueDictionary' "
|
||||
"object needs an argument")
|
||||
self, *args = args
|
||||
if len(args) > 1:
|
||||
raise TypeError('expected at most 1 arguments, got %d' % len(args))
|
||||
dict = args[0] if args else None
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
d = self.data
|
||||
if dict is not None:
|
||||
if not hasattr(dict, "items"):
|
||||
dict = type({})(dict)
|
||||
for key, o in dict.items():
|
||||
d[key] = KeyedRef(o, self._remove, key)
|
||||
if len(kwargs):
|
||||
self.update(kwargs)
|
||||
|
||||
def valuerefs(self):
|
||||
"""Return a list of weak references to the values.
|
||||
|
||||
The references are not guaranteed to be 'live' at the time
|
||||
they are used, so the result of calling the references needs
|
||||
to be checked before being used. This can be used to avoid
|
||||
creating references that will cause the garbage collector to
|
||||
keep the values around longer than needed.
|
||||
|
||||
"""
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
return list(self.data.values())
|
||||
|
||||
|
||||
class KeyedRef(ref):
|
||||
"""Specialized reference that includes a key corresponding to the value.
|
||||
|
||||
This is used in the WeakValueDictionary to avoid having to create
|
||||
a function object for each key stored in the mapping. A shared
|
||||
callback object can use the 'key' attribute of a KeyedRef instead
|
||||
of getting a reference to the key from an enclosing scope.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = "key",
|
||||
|
||||
def __new__(type, ob, callback, key):
|
||||
self = ref.__new__(type, ob, callback)
|
||||
self.key = key
|
||||
return self
|
||||
|
||||
def __init__(self, ob, callback, key):
|
||||
super().__init__(ob, callback)
|
||||
|
||||
|
||||
class WeakKeyDictionary(_collections_abc.MutableMapping):
|
||||
""" Mapping class that references keys weakly.
|
||||
|
||||
Entries in the dictionary will be discarded when there is no
|
||||
longer a strong reference to the key. This can be used to
|
||||
associate additional data with an object owned by other parts of
|
||||
an application without adding attributes to those objects. This
|
||||
can be especially useful with objects that override attribute
|
||||
accesses.
|
||||
"""
|
||||
|
||||
def __init__(self, dict=None):
|
||||
self.data = {}
|
||||
def remove(k, selfref=ref(self)):
|
||||
self = selfref()
|
||||
if self is not None:
|
||||
if self._iterating:
|
||||
self._pending_removals.append(k)
|
||||
else:
|
||||
del self.data[k]
|
||||
self._remove = remove
|
||||
# A list of dead weakrefs (keys to be removed)
|
||||
self._pending_removals = []
|
||||
self._iterating = set()
|
||||
self._dirty_len = False
|
||||
if dict is not None:
|
||||
self.update(dict)
|
||||
|
||||
def _commit_removals(self):
|
||||
# NOTE: We don't need to call this method before mutating the dict,
|
||||
# because a dead weakref never compares equal to a live weakref,
|
||||
# even if they happened to refer to equal objects.
|
||||
# However, it means keys may already have been removed.
|
||||
l = self._pending_removals
|
||||
d = self.data
|
||||
while l:
|
||||
try:
|
||||
del d[l.pop()]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def _scrub_removals(self):
|
||||
d = self.data
|
||||
self._pending_removals = [k for k in self._pending_removals if k in d]
|
||||
self._dirty_len = False
|
||||
|
||||
def __delitem__(self, key):
|
||||
self._dirty_len = True
|
||||
del self.data[ref(key)]
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.data[ref(key)]
|
||||
|
||||
def __len__(self):
|
||||
if self._dirty_len and self._pending_removals:
|
||||
# self._pending_removals may still contain keys which were
|
||||
# explicitly removed, we have to scrub them (see issue #21173).
|
||||
self._scrub_removals()
|
||||
return len(self.data) - len(self._pending_removals)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s at %#x>" % (self.__class__.__name__, id(self))
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.data[ref(key, self._remove)] = value
|
||||
|
||||
def copy(self):
|
||||
new = WeakKeyDictionary()
|
||||
for key, value in self.data.items():
|
||||
o = key()
|
||||
if o is not None:
|
||||
new[o] = value
|
||||
return new
|
||||
|
||||
__copy__ = copy
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
from copy import deepcopy
|
||||
new = self.__class__()
|
||||
for key, value in self.data.items():
|
||||
o = key()
|
||||
if o is not None:
|
||||
new[o] = deepcopy(value, memo)
|
||||
return new
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self.data.get(ref(key),default)
|
||||
|
||||
def __contains__(self, key):
|
||||
try:
|
||||
wr = ref(key)
|
||||
except TypeError:
|
||||
return False
|
||||
return wr in self.data
|
||||
|
||||
def items(self):
|
||||
with _IterationGuard(self):
|
||||
for wr, value in self.data.items():
|
||||
key = wr()
|
||||
if key is not None:
|
||||
yield key, value
|
||||
|
||||
def keys(self):
|
||||
with _IterationGuard(self):
|
||||
for wr in self.data:
|
||||
obj = wr()
|
||||
if obj is not None:
|
||||
yield obj
|
||||
|
||||
__iter__ = keys
|
||||
|
||||
def values(self):
|
||||
with _IterationGuard(self):
|
||||
for wr, value in self.data.items():
|
||||
if wr() is not None:
|
||||
yield value
|
||||
|
||||
def keyrefs(self):
|
||||
"""Return a list of weak references to the keys.
|
||||
|
||||
The references are not guaranteed to be 'live' at the time
|
||||
they are used, so the result of calling the references needs
|
||||
to be checked before being used. This can be used to avoid
|
||||
creating references that will cause the garbage collector to
|
||||
keep the keys around longer than needed.
|
||||
|
||||
"""
|
||||
return list(self.data)
|
||||
|
||||
def popitem(self):
|
||||
self._dirty_len = True
|
||||
while True:
|
||||
key, value = self.data.popitem()
|
||||
o = key()
|
||||
if o is not None:
|
||||
return o, value
|
||||
|
||||
def pop(self, key, *args):
|
||||
self._dirty_len = True
|
||||
return self.data.pop(ref(key), *args)
|
||||
|
||||
def setdefault(self, key, default=None):
|
||||
return self.data.setdefault(ref(key, self._remove),default)
|
||||
|
||||
def update(self, dict=None, **kwargs):
|
||||
d = self.data
|
||||
if dict is not None:
|
||||
if not hasattr(dict, "items"):
|
||||
dict = type({})(dict)
|
||||
for key, value in dict.items():
|
||||
d[ref(key, self._remove)] = value
|
||||
if len(kwargs):
|
||||
self.update(kwargs)
|
||||
|
||||
|
||||
class finalize:
|
||||
"""Class for finalization of weakrefable objects
|
||||
|
||||
finalize(obj, func, *args, **kwargs) returns a callable finalizer
|
||||
object which will be called when obj is garbage collected. The
|
||||
first time the finalizer is called it evaluates func(*arg, **kwargs)
|
||||
and returns the result. After this the finalizer is dead, and
|
||||
calling it just returns None.
|
||||
|
||||
When the program exits any remaining finalizers for which the
|
||||
atexit attribute is true will be run in reverse order of creation.
|
||||
By default atexit is true.
|
||||
"""
|
||||
|
||||
# Finalizer objects don't have any state of their own. They are
|
||||
# just used as keys to lookup _Info objects in the registry. This
|
||||
# ensures that they cannot be part of a ref-cycle.
|
||||
|
||||
__slots__ = ()
|
||||
_registry = {}
|
||||
_shutdown = False
|
||||
_index_iter = itertools.count()
|
||||
_dirty = False
|
||||
_registered_with_atexit = False
|
||||
|
||||
class _Info:
|
||||
__slots__ = ("weakref", "func", "args", "kwargs", "atexit", "index")
|
||||
|
||||
def __init__(self, obj, func, *args, **kwargs):
|
||||
if not self._registered_with_atexit:
|
||||
# We may register the exit function more than once because
|
||||
# of a thread race, but that is harmless
|
||||
import atexit
|
||||
atexit.register(self._exitfunc)
|
||||
finalize._registered_with_atexit = True
|
||||
info = self._Info()
|
||||
info.weakref = ref(obj, self)
|
||||
info.func = func
|
||||
info.args = args
|
||||
info.kwargs = kwargs or None
|
||||
info.atexit = True
|
||||
info.index = next(self._index_iter)
|
||||
self._registry[self] = info
|
||||
finalize._dirty = True
|
||||
|
||||
def __call__(self, _=None):
|
||||
"""If alive then mark as dead and return func(*args, **kwargs);
|
||||
otherwise return None"""
|
||||
info = self._registry.pop(self, None)
|
||||
if info and not self._shutdown:
|
||||
return info.func(*info.args, **(info.kwargs or {}))
|
||||
|
||||
def detach(self):
|
||||
"""If alive then mark as dead and return (obj, func, args, kwargs);
|
||||
otherwise return None"""
|
||||
info = self._registry.get(self)
|
||||
obj = info and info.weakref()
|
||||
if obj is not None and self._registry.pop(self, None):
|
||||
return (obj, info.func, info.args, info.kwargs or {})
|
||||
|
||||
def peek(self):
|
||||
"""If alive then return (obj, func, args, kwargs);
|
||||
otherwise return None"""
|
||||
info = self._registry.get(self)
|
||||
obj = info and info.weakref()
|
||||
if obj is not None:
|
||||
return (obj, info.func, info.args, info.kwargs or {})
|
||||
|
||||
@property
|
||||
def alive(self):
|
||||
"""Whether finalizer is alive"""
|
||||
return self in self._registry
|
||||
|
||||
@property
|
||||
def atexit(self):
|
||||
"""Whether finalizer should be called at exit"""
|
||||
info = self._registry.get(self)
|
||||
return bool(info) and info.atexit
|
||||
|
||||
@atexit.setter
|
||||
def atexit(self, value):
|
||||
info = self._registry.get(self)
|
||||
if info:
|
||||
info.atexit = bool(value)
|
||||
|
||||
def __repr__(self):
|
||||
info = self._registry.get(self)
|
||||
obj = info and info.weakref()
|
||||
if obj is None:
|
||||
return '<%s object at %#x; dead>' % (type(self).__name__, id(self))
|
||||
else:
|
||||
return '<%s object at %#x; for %r at %#x>' % \
|
||||
(type(self).__name__, id(self), type(obj).__name__, id(obj))
|
||||
|
||||
@classmethod
|
||||
def _select_for_exit(cls):
|
||||
# Return live finalizers marked for exit, oldest first
|
||||
L = [(f,i) for (f,i) in cls._registry.items() if i.atexit]
|
||||
L.sort(key=lambda item:item[1].index)
|
||||
return [f for (f,i) in L]
|
||||
|
||||
@classmethod
|
||||
def _exitfunc(cls):
|
||||
# At shutdown invoke finalizers for which atexit is true.
|
||||
# This is called once all other non-daemonic threads have been
|
||||
# joined.
|
||||
reenable_gc = False
|
||||
try:
|
||||
if cls._registry:
|
||||
import gc
|
||||
if gc.isenabled():
|
||||
reenable_gc = True
|
||||
gc.disable()
|
||||
pending = None
|
||||
while True:
|
||||
if pending is None or finalize._dirty:
|
||||
pending = cls._select_for_exit()
|
||||
finalize._dirty = False
|
||||
if not pending:
|
||||
break
|
||||
f = pending.pop()
|
||||
try:
|
||||
# gc is disabled, so (assuming no daemonic
|
||||
# threads) the following is the only line in
|
||||
# this function which might trigger creation
|
||||
# of a new finalizer
|
||||
f()
|
||||
except Exception:
|
||||
sys.excepthook(*sys.exc_info())
|
||||
assert f not in cls._registry
|
||||
finally:
|
||||
# prevent any more finalizers from executing during shutdown
|
||||
finalize._shutdown = True
|
||||
if reenable_gc:
|
||||
gc.enable()
|
||||
@@ -5,8 +5,22 @@
|
||||
//! - [rust weak struct](https://doc.rust-lang.org/std/rc/struct.Weak.html)
|
||||
//!
|
||||
|
||||
use super::super::pyobject::PyObjectRef;
|
||||
use crate::pyobject::PyObjectRef;
|
||||
use crate::vm::VirtualMachine;
|
||||
use std::rc::Rc;
|
||||
|
||||
fn weakref_getweakrefcount(obj: PyObjectRef, _vm: &VirtualMachine) -> usize {
|
||||
Rc::weak_count(&obj)
|
||||
}
|
||||
|
||||
fn weakref_getweakrefs(_obj: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
|
||||
// TODO: implement this, may require a different gc
|
||||
vm.ctx.new_list(vec![])
|
||||
}
|
||||
|
||||
fn weakref_remove_dead_weakref(_obj: PyObjectRef, _key: PyObjectRef, _vm: &VirtualMachine) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
|
||||
let ctx = &vm.ctx;
|
||||
@@ -14,5 +28,11 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
|
||||
py_module!(vm, "_weakref", {
|
||||
"ref" => ctx.weakref_type(),
|
||||
"proxy" => ctx.weakproxy_type(),
|
||||
"getweakrefcount" => ctx.new_rustfunc(weakref_getweakrefcount),
|
||||
"getweakrefs" => ctx.new_rustfunc(weakref_getweakrefs),
|
||||
"ReferenceType" => ctx.weakref_type(),
|
||||
"ProxyType" => ctx.weakproxy_type(),
|
||||
"CallableProxyType" => ctx.weakproxy_type(),
|
||||
"_remove_dead_weakref" => ctx.new_rustfunc(weakref_remove_dead_weakref),
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user