Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Lib/_py_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,12 @@ def _abc_caches_clear(cls):
def __instancecheck__(cls, instance):
"""Override for isinstance(instance, cls)."""
# Inline the cache checking
subclass = instance.__class__
try:
subclass = instance.__class__
except AttributeError:
# Fall back to the type when the instance has no __class__,
# matching the behaviour of the built-in isinstance() (gh-153772).
subclass = type(instance)
if subclass in cls._abc_cache:
return True
subtype = type(instance)
Expand Down
63 changes: 53 additions & 10 deletions Lib/functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
# import weakref # Deferred to single_dispatch()
from operator import itemgetter
from reprlib import recursive_repr
from types import GenericAlias, MethodType, MappingProxyType, UnionType
from types import FunctionType, GenericAlias, MethodType, MappingProxyType, UnionType
from _thread import RLock

################################################################################
Expand Down Expand Up @@ -170,7 +170,7 @@ def _lt_from_ge(self, other):
return op_result
return not op_result

_convert = {
_convert = frozendict({
'__lt__': [('__gt__', _gt_from_lt),
('__le__', _le_from_lt),
('__ge__', _ge_from_lt)],
Expand All @@ -183,7 +183,7 @@ def _lt_from_ge(self, other):
'__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"""
Expand Down Expand Up @@ -232,7 +232,7 @@ def __ge__(self, other):
### reduce() sequence to a single item
################################################################################

_initial_missing = object()
_initial_missing = sentinel('_initial_missing')

def reduce(function, sequence, initial=_initial_missing):
"""
Expand Down Expand Up @@ -517,7 +517,7 @@ def _unwrap_partialmethod(func):
### LRU Cache function decorator
################################################################################

_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])
_CacheInfo = namedtuple("CacheInfo", ("hits", "misses", "maxsize", "currsize"))

def _make_key(args, kwds, typed,
kwd_mark = (object(),),
Expand All @@ -539,13 +539,15 @@ def _make_key(args, kwds, typed,
# distinct call from f(y=2, x=1) which will be cached separately.
key = args
if kwds:
key = list(key)
key += kwd_mark
for item in kwds.items():
key += item
key = tuple(key)
if typed:
key += tuple(type(v) for v in args)
key += tuple([type(v) for v in args])
if kwds:
key += tuple(type(v) for v in kwds.values())
key += tuple([type(v) for v in kwds.values()])
elif len(key) == 1 and type(key[0]) in fasttypes:
return key[0]
return key
Expand Down Expand Up @@ -580,12 +582,14 @@ def lru_cache(maxsize=128, typed=False):
# Negative maxsize is treated as 0
if maxsize < 0:
maxsize = 0

elif callable(maxsize) and isinstance(typed, bool):
# The user_function was passed in directly via the maxsize argument
user_function, maxsize = maxsize, 128
wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
return update_wrapper(wrapper, user_function)

elif maxsize is not None:
raise TypeError(
'Expected first argument to be an integer, a callable, or None')
Expand All @@ -598,6 +602,9 @@ def decorating_function(user_function):
return decorating_function

def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo):
if not callable(user_function):
raise TypeError("the first argument must be callable")

# 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
Expand All @@ -617,6 +624,7 @@ def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo):
def wrapper(*args, **kwds):
# No caching -- just a statistics update
nonlocal misses

misses += 1
result = user_function(*args, **kwds)
return result
Expand All @@ -626,6 +634,7 @@ def wrapper(*args, **kwds):
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:
Expand All @@ -641,7 +650,9 @@ def wrapper(*args, **kwds):
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:
Expand All @@ -656,19 +667,23 @@ def wrapper(*args, **kwds):
hits += 1
return result
misses += 1

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
Expand All @@ -677,22 +692,27 @@ def wrapper(*args, **kwds):
# still adjusting the links.
root = oldroot[NEXT]
oldkey = root[KEY]
oldresult = root[RESULT]
oldresult = root[RESULT] # noqa: F841
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)

return result

def cache_info():
Expand All @@ -703,6 +723,7 @@ def cache_info():
def cache_clear():
"""Clear the cache and cache statistics"""
nonlocal hits, misses, full

with lock:
cache.clear()
root[:] = [root, root, None, None]
Expand Down Expand Up @@ -995,7 +1016,8 @@ def wrapper(*args, **kw):
class singledispatchmethod:
"""Single-dispatch generic method descriptor.

Supports wrapping existing descriptors.
Supports wrapping existing descriptors and handles non-descriptor
callables as instance methods.
"""

def __init__(self, func):
Expand Down Expand Up @@ -1038,6 +1060,11 @@ def __init__(self, unbound, obj, cls):
# Set instance attributes which cannot be handled in __getattr__()
# because they conflict with type descriptors.
func = unbound.func

# Dispatch on the second argument if a generic method turns into
# a bound method on instance-level access. See GH-143535.
self._dispatch_arg_index = 1 if obj is None and isinstance(func, FunctionType) else 0

try:
self.__module__ = func.__module__
except AttributeError:
Expand Down Expand Up @@ -1066,7 +1093,23 @@ def __call__(self, /, *args, **kwargs):
'singledispatchmethod method')
raise TypeError(f'{funcname} requires at least '
'1 positional argument')
return self._dispatch(args[0].__class__).__get__(self._obj, self._cls)(*args, **kwargs)
method = self._dispatch(args[self._dispatch_arg_index].__class__)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard the second singledispatch argument before indexing

When a plain-function singledispatchmethod is accessed on the class and called with only its explicit instance, such as C.m(C()) for def m(self, value), _dispatch_arg_index is 1 while the existing guard only rejects an empty tuple. This line consequently raises IndexError: tuple index out of range instead of the expected missing-argument TypeError; check that the selected dispatch index exists before indexing.

Useful? React with 👍 / 👎.


if hasattr(method, "__get__"):
# If the method is a descriptor, it might be necessary
# to drop the first argument before calling
# as it can be no longer expected after descriptor access.
skip_bound_arg = False
if isinstance(method, staticmethod):
skip_bound_arg = self._dispatch_arg_index == 1

method = method.__get__(self._obj, self._cls)
if isinstance(method, MethodType):
skip_bound_arg = self._dispatch_arg_index == 1

if skip_bound_arg:
return method(*args[1:], **kwargs)
return method(*args, **kwargs)

def __getattr__(self, name):
# Resolve these attributes lazily to speed up creation of
Expand Down
38 changes: 29 additions & 9 deletions Lib/genericpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

__all__ = ['commonprefix', 'exists', 'getatime', 'getctime', 'getmtime',
'getsize', 'isdevdrive', 'isdir', 'isfile', 'isjunction', 'islink',
'lexists', 'samefile', 'sameopenfile', 'samestat', 'ALLOW_MISSING']
'lexists', 'samefile', 'sameopenfile', 'samestat',
'ALL_BUT_LAST', 'ALLOW_MISSING']


# Does a path exist?
Expand Down Expand Up @@ -81,29 +82,38 @@ def isdevdrive(path):
return False


def getsize(filename):
def getsize(filename, /):
"""Return the size of a file, reported by os.stat()."""
return os.stat(filename).st_size


def getmtime(filename):
def getmtime(filename, /):
"""Return the last modification time of a file, reported by os.stat()."""
return os.stat(filename).st_mtime


def getatime(filename):
def getatime(filename, /):
"""Return the last access time of a file, reported by os.stat()."""
return os.stat(filename).st_atime


def getctime(filename):
def getctime(filename, /):
"""Return the metadata change time of a file, reported by os.stat()."""
return os.stat(filename).st_ctime


# Return the longest prefix of all list elements.
def commonprefix(m):
def commonprefix(m, /):
"Given a list of pathnames, returns the longest common leading component"
import warnings
warnings.warn('os.path.commonprefix() is deprecated. Use '
'os.path.commonpath() for longest path prefix.',
category=DeprecationWarning,
stacklevel=2)
return _commonprefix(m)

def _commonprefix(m, /):
"Internal implementation of commonprefix()"
if not m: return ''
# Some people pass in a list of pathname parts to operate in an OS-agnostic
# fashion; don't try to translate in that case as that's an abuse of the
Expand All @@ -120,14 +130,14 @@ def commonprefix(m):

# Are two stat buffers (obtained from stat, fstat or lstat)
# describing the same file?
def samestat(s1, s2):
def samestat(s1, s2, /):
"""Test whether two stat buffers reference the same file"""
return (s1.st_ino == s2.st_ino and
s1.st_dev == s2.st_dev)


# Are two filenames really pointing to the same file?
def samefile(f1, f2):
def samefile(f1, f2, /):
"""Test whether two pathnames reference the same actual file or directory

This is determined by the device number and i-node number and
Expand Down Expand Up @@ -190,7 +200,17 @@ def _check_arg_types(funcname, *args):
if hasstr and hasbytes:
raise TypeError("Can't mix strings and bytes in path components") from None

# A singleton with a true boolean value.

# Singletons with a true boolean value.

@object.__new__
class ALL_BUT_LAST:
"""Special value for use in realpath()."""
def __repr__(self):
return 'os.path.ALL_BUT_LAST'
def __reduce__(self):
return self.__class__.__name__

@object.__new__
class ALLOW_MISSING:
"""Special value for use in realpath()."""
Expand Down
5 changes: 3 additions & 2 deletions Lib/gettext.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,9 @@ def _error(value):
('+', '-'),
('*', '/', '%'),
)
_binary_ops = {op: i for i, ops in enumerate(_binary_ops, 1) for op in ops}
_c2py_ops = {'||': 'or', '&&': 'and', '/': '//'}
_binary_ops = frozendict({op: i for i, ops in enumerate(_binary_ops, 1)
for op in ops})
_c2py_ops = frozendict({'||': 'or', '&&': 'and', '/': '//'})


def _parse(tokens, priority=-1):
Expand Down
Loading
Loading