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

38
discord/guild.py

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

2
discord/role.py

@ -347,7 +347,7 @@ class Role(Hashable):
@property @property
def members(self) -> List[Member]: def members(self) -> List[Member]:
"""List[:class:`Member`]: Returns all the members with this role.""" """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(): if self.is_default():
return all_members return all_members

16
discord/state.py

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

29
discord/utils.py

@ -31,6 +31,7 @@ from typing import (
AsyncIterator, AsyncIterator,
Awaitable, Awaitable,
Callable, Callable,
Collection,
Coroutine, Coroutine,
Dict, Dict,
ForwardRef, ForwardRef,
@ -205,31 +206,41 @@ def cached_slot_property(name: str) -> Callable[[Callable[[T], T_co]], CachedSlo
class SequenceProxy(Sequence[T_co]): 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]): def __init__(self, proxied: Collection[T_co], *, sorted: bool = False):
self.__proxied = proxied 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: def __getitem__(self, idx: int) -> T_co:
return self.__proxied[idx] return self.__copied[idx]
def __len__(self) -> int: def __len__(self) -> int:
return len(self.__proxied) return len(self.__proxied)
def __contains__(self, item: Any) -> bool: def __contains__(self, item: Any) -> bool:
return item in self.__proxied return item in self.__copied
def __iter__(self) -> Iterator[T_co]: def __iter__(self) -> Iterator[T_co]:
return iter(self.__proxied) return iter(self.__copied)
def __reversed__(self) -> Iterator[T_co]: def __reversed__(self) -> Iterator[T_co]:
return reversed(self.__proxied) return reversed(self.__copied)
def index(self, value: Any, *args: Any, **kwargs: Any) -> int: 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: def count(self, value: Any) -> int:
return self.__proxied.count(value) return self.__copied.count(value)
@overload @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 The ``NoMoreItems`` exception was removed as calling :func:`anext` or :meth:`~object.__anext__` on an
:term:`asynchronous iterator` will now raise :class:`StopAsyncIteration`. :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`` Removal of ``Embed.Empty``
--------------------------- ---------------------------

Loading…
Cancel
Save