Browse Source

Change certain sequences to use a special proxy type instead of list

This is to speed up cases where someone is just querying the length
of the underlying sequence. If anything else is done to the sequence
then it is copied from the original iterator.

This change should be mostly transparent.
pull/8258/head
Rapptz 3 years ago
parent
commit
87bc79e6e3
  1. 16
      discord/client.py
  2. 38
      discord/guild.py
  3. 2
      discord/role.py
  4. 16
      discord/state.py
  5. 29
      discord/utils.py
  6. 19
      docs/migrating.rst

16
discord/client.py

@ -370,18 +370,18 @@ class Client:
return self._connection.user
@property
def guilds(self) -> List[Guild]:
"""List[:class:`.Guild`]: The guilds that the connected client is a member of."""
def guilds(self) -> Sequence[Guild]:
"""Sequence[:class:`.Guild`]: The guilds that the connected client is a member of."""
return self._connection.guilds
@property
def emojis(self) -> List[Emoji]:
"""List[:class:`.Emoji`]: The emojis that the connected client has."""
def emojis(self) -> Sequence[Emoji]:
"""Sequence[:class:`.Emoji`]: The emojis that the connected client has."""
return self._connection.emojis
@property
def stickers(self) -> List[GuildSticker]:
"""List[:class:`.GuildSticker`]: The stickers that the connected client has.
def stickers(self) -> Sequence[GuildSticker]:
"""Sequence[:class:`.GuildSticker`]: The stickers that the connected client has.
.. versionadded:: 2.0
"""
@ -396,8 +396,8 @@ class Client:
return utils.SequenceProxy(self._connection._messages or [])
@property
def private_channels(self) -> List[PrivateChannel]:
"""List[:class:`.abc.PrivateChannel`]: The private channels that the connected client is participating on.
def private_channels(self) -> Sequence[PrivateChannel]:
"""Sequence[:class:`.abc.PrivateChannel`]: The private channels that the connected client is participating on.
.. note::

38
discord/guild.py

@ -562,17 +562,17 @@ class Guild(Hashable):
self._add_thread(Thread(guild=self, state=self._state, data=thread))
@property
def channels(self) -> List[GuildChannel]:
"""List[:class:`abc.GuildChannel`]: A list of channels that belongs to this guild."""
return list(self._channels.values())
def channels(self) -> Sequence[GuildChannel]:
"""Sequence[:class:`abc.GuildChannel`]: A list of channels that belongs to this guild."""
return utils.SequenceProxy(self._channels.values())
@property
def threads(self) -> List[Thread]:
"""List[:class:`Thread`]: A list of threads that you have permission to view.
def threads(self) -> Sequence[Thread]:
"""Sequence[:class:`Thread`]: A list of threads that you have permission to view.
.. versionadded:: 2.0
"""
return list(self._threads.values())
return utils.SequenceProxy(self._threads.values())
@property
def large(self) -> bool:
@ -817,9 +817,9 @@ class Guild(Hashable):
return self._PREMIUM_GUILD_LIMITS[self.premium_tier].filesize
@property
def members(self) -> List[Member]:
"""List[:class:`Member`]: A list of members that belong to this guild."""
return list(self._members.values())
def members(self) -> Sequence[Member]:
"""Sequence[:class:`Member`]: A list of members that belong to this guild."""
return utils.SequenceProxy(self._members.values())
def get_member(self, user_id: int, /) -> Optional[Member]:
"""Returns a member with the given ID.
@ -846,13 +846,13 @@ class Guild(Hashable):
return [member for member in self.members if member.premium_since is not None]
@property
def roles(self) -> List[Role]:
"""List[:class:`Role`]: Returns a :class:`list` of the guild's roles in hierarchy order.
def roles(self) -> Sequence[Role]:
"""Sequence[:class:`Role`]: Returns a sequence of the guild's roles in hierarchy order.
The first element of this list will be the lowest role in the
The first element of this sequence will be the lowest role in the
hierarchy.
"""
return sorted(self._roles.values())
return utils.SequenceProxy(self._roles.values(), sorted=True)
def get_role(self, role_id: int, /) -> Optional[Role]:
"""Returns a role with the given ID.
@ -904,13 +904,13 @@ class Guild(Hashable):
return None
@property
def stage_instances(self) -> List[StageInstance]:
"""List[:class:`StageInstance`]: Returns a :class:`list` of the guild's stage instances that
def stage_instances(self) -> Sequence[StageInstance]:
"""Sequence[:class:`StageInstance`]: Returns a sequence of the guild's stage instances that
are currently running.
.. versionadded:: 2.0
"""
return list(self._stage_instances.values())
return utils.SequenceProxy(self._stage_instances.values())
def get_stage_instance(self, stage_instance_id: int, /) -> Optional[StageInstance]:
"""Returns a stage instance with the given ID.
@ -930,12 +930,12 @@ class Guild(Hashable):
return self._stage_instances.get(stage_instance_id)
@property
def scheduled_events(self) -> List[ScheduledEvent]:
"""List[:class:`ScheduledEvent`]: Returns a :class:`list` of the guild's scheduled events.
def scheduled_events(self) -> Sequence[ScheduledEvent]:
"""Sequence[:class:`ScheduledEvent`]: Returns a sequence of the guild's scheduled events.
.. versionadded:: 2.0
"""
return list(self._scheduled_events.values())
return utils.SequenceProxy(self._scheduled_events.values())
def get_scheduled_event(self, scheduled_event_id: int, /) -> Optional[ScheduledEvent]:
"""Returns a scheduled event with the given ID.

2
discord/role.py

@ -347,7 +347,7 @@ class Role(Hashable):
@property
def members(self) -> List[Member]:
"""List[:class:`Member`]: Returns all the members with this role."""
all_members = self.guild.members
all_members = list(self.guild._members.values())
if self.is_default():
return all_members

16
discord/state.py

@ -378,8 +378,8 @@ class ConnectionState:
return self._view_store.persistent_views
@property
def guilds(self) -> List[Guild]:
return list(self._guilds.values())
def guilds(self) -> Sequence[Guild]:
return utils.SequenceProxy(self._guilds.values())
def _get_guild(self, guild_id: Optional[int]) -> Optional[Guild]:
# the keys of self._guilds are ints
@ -403,12 +403,12 @@ class ConnectionState:
del guild
@property
def emojis(self) -> List[Emoji]:
return list(self._emojis.values())
def emojis(self) -> Sequence[Emoji]:
return utils.SequenceProxy(self._emojis.values())
@property
def stickers(self) -> List[GuildSticker]:
return list(self._stickers.values())
def stickers(self) -> Sequence[GuildSticker]:
return utils.SequenceProxy(self._stickers.values())
def get_emoji(self, emoji_id: Optional[int]) -> Optional[Emoji]:
# the keys of self._emojis are ints
@ -419,8 +419,8 @@ class ConnectionState:
return self._stickers.get(sticker_id) # type: ignore
@property
def private_channels(self) -> List[PrivateChannel]:
return list(self._private_channels.values())
def private_channels(self) -> Sequence[PrivateChannel]:
return utils.SequenceProxy(self._private_channels.values())
def _get_private_channel(self, channel_id: Optional[int]) -> Optional[PrivateChannel]:
try:

29
discord/utils.py

@ -31,6 +31,7 @@ from typing import (
AsyncIterator,
Awaitable,
Callable,
Collection,
Coroutine,
Dict,
ForwardRef,
@ -205,31 +206,41 @@ def cached_slot_property(name: str) -> Callable[[Callable[[T], T_co]], CachedSlo
class SequenceProxy(Sequence[T_co]):
"""Read-only proxy of a Sequence."""
"""A proxy of a sequence that only creates a copy when necessary."""
def __init__(self, proxied: Sequence[T_co]):
self.__proxied = proxied
def __init__(self, proxied: Collection[T_co], *, sorted: bool = False):
self.__proxied: Collection[T_co] = proxied
self.__sorted: bool = sorted
@cached_property
def __copied(self) -> List[T_co]:
if self.__sorted:
# The type checker thinks the variance is wrong, probably due to the comparison requirements
self.__proxied = sorted(self.__proxied) # type: ignore
else:
self.__proxied = list(self.__proxied)
return self.__proxied
def __getitem__(self, idx: int) -> T_co:
return self.__proxied[idx]
return self.__copied[idx]
def __len__(self) -> int:
return len(self.__proxied)
def __contains__(self, item: Any) -> bool:
return item in self.__proxied
return item in self.__copied
def __iter__(self) -> Iterator[T_co]:
return iter(self.__proxied)
return iter(self.__copied)
def __reversed__(self) -> Iterator[T_co]:
return reversed(self.__proxied)
return reversed(self.__copied)
def index(self, value: Any, *args: Any, **kwargs: Any) -> int:
return self.__proxied.index(value, *args, **kwargs)
return self.__copied.index(value, *args, **kwargs)
def count(self, value: Any) -> int:
return self.__proxied.count(value)
return self.__copied.count(value)
@overload

19
docs/migrating.rst

@ -864,6 +864,25 @@ The return type of the following methods has been changed to an :term:`asynchron
The ``NoMoreItems`` exception was removed as calling :func:`anext` or :meth:`~object.__anext__` on an
:term:`asynchronous iterator` will now raise :class:`StopAsyncIteration`.
Changing certain lists to be lazy sequences instead
-----------------------------------------------------
In order to improve performance when calculating the length of certain lists, certain attributes were changed to return a sequence rather than a :class:`list`.
A sequence is similar to a :class:`list` except it is read-only. In order to get a list again you can call :class:`list` on the resulting sequence.
The following properties were changed to return a sequence instead of a list:
- :attr:`Client.guilds`
- :attr:`Client.emojis`
- :attr:`Client.private_channels`
- :attr:`Guild.roles`
- :attr:`Guild.channels`
- :attr:`Guild.members`
This change should be transparent, unless you are modifying the sequence by doing things such as ``list.append``.
Removal of ``Embed.Empty``
---------------------------

Loading…
Cancel
Save