2626from typing import TYPE_CHECKING , List , Optional , Tuple , Type , TypeVar
2727
2828from telegram .utils .types import JSONDict
29+ from telegram .utils .deprecate import set_new_attribute_deprecated
2930
3031if TYPE_CHECKING :
3132 from telegram import Bot
@@ -38,11 +39,19 @@ class TelegramObject:
3839
3940 _id_attrs : Tuple [object , ...] = ()
4041
42+ # Adding slots reduces memory usage & allows for faster attribute access.
43+ # Only instance variables should be added to __slots__.
44+ # We add __dict__ here for backward compatibility & also to avoid repetition for subclasses.
45+ __slots__ = ('__dict__' ,)
46+
4147 def __str__ (self ) -> str :
4248 return str (self .to_dict ())
4349
4450 def __getitem__ (self , item : str ) -> object :
45- return self .__dict__ [item ]
51+ return getattr (self , item , None )
52+
53+ def __setattr__ (self , key : str , value : object ) -> None :
54+ set_new_attribute_deprecated (self , key , value )
4655
4756 @staticmethod
4857 def _parse_data (data : Optional [JSONDict ]) -> Optional [JSONDict ]:
@@ -102,11 +111,16 @@ def to_dict(self) -> JSONDict:
102111 """
103112 data = {}
104113
105- for key in iter (self .__dict__ ):
114+ # We want to get all attributes for the class, using self.__slots__ only includes the
115+ # attributes used by that class itself, and not its superclass(es). Hence we get its MRO
116+ # and then get their attributes. The `[:-2]` slice excludes the `object` class & the
117+ # TelegramObject class itself.
118+ attrs = {attr for cls in self .__class__ .__mro__ [:- 2 ] for attr in cls .__slots__ }
119+ for key in attrs :
106120 if key == 'bot' or key .startswith ('_' ):
107121 continue
108122
109- value = self . __dict__ [ key ]
123+ value = getattr ( self , key , None )
110124 if value is not None :
111125 if hasattr (value , 'to_dict' ):
112126 data [key ] = value .to_dict ()
0 commit comments