diff --git a/sc2reader/decoders.py b/sc2reader/decoders.py index 8520e259..56674a1c 100644 --- a/sc2reader/decoders.py +++ b/sc2reader/decoders.py @@ -31,7 +31,8 @@ class ByteDecoder(object): _contents = "" def __init__(self, contents, endian): - """ Accepts both strings and files implementing ``read()`` and + """ + Accepts both strings and files implementing ``read()`` and decodes them in the specified endian format. """ if hasattr(contents, "read"): @@ -66,40 +67,58 @@ def __init__(self, contents, endian): self._unpack_bytes = lambda bytes: bytes if self.endian == ">" else bytes[::-1] def done(self): - """ Returns true when all bytes have been decoded """ + """ + Returns true when all bytes have been decoded + """ return self.tell() == self.length def read_range(self, start, end): - """ Returns the raw byte string from the indicated address range """ + """ + Returns the raw byte string from the indicated address range + """ return self._contents[start:end] def peek(self, count): - """ Returns the raw byte string for the next ``count`` bytes """ + """ + Returns the raw byte string for the next ``count`` bytes + """ start = self.tell() return self._contents[start : start + count] def read_uint8(self): - """ Returns the next byte as an unsigned integer """ + """ + Returns the next byte as an unsigned integer + """ return ord(self.read(1)) def read_uint16(self): - """ Returns the next two bytes as an unsigned integer """ + """ + Returns the next two bytes as an unsigned integer + """ return self._unpack_short(self.read(2))[0] def read_uint32(self): - """ Returns the next four bytes as an unsigned integer """ + """ + Returns the next four bytes as an unsigned integer + """ return self._unpack_int(self.read(4))[0] def read_uint64(self): - """ Returns the next eight bytes as an unsigned integer """ + """ + Returns the next eight bytes as an unsigned integer + """ return self._unpack_longlong(self.read(8))[0] def read_bytes(self, count): - """ Returns the next ``count`` bytes as a byte string """ + """ + Returns the next ``count`` bytes as a byte string + """ return self._unpack_bytes(self.read(count)) def read_uint(self, count): - """ Returns the next ``count`` bytes as an unsigned integer """ + """ + Returns the next ``count`` bytes as an unsigned integer + """ unpack = struct.Struct(str(self.endian + "B" * count)).unpack uint = 0 for byte in unpack(self.read(count)): @@ -107,11 +126,15 @@ def read_uint(self, count): return uint def read_string(self, count, encoding="utf8"): - """ Read a string in given encoding (default utf8) that is ``count`` bytes long """ + """ + Read a string in given encoding (default utf8) that is ``count`` bytes long + """ return self.read_bytes(count).decode(encoding) def read_cstring(self, encoding="utf8"): - """ Read a NULL byte terminated character string decoded with given encoding (default utf8). Ignores endian. """ + """ + Read a NULL byte terminated character string decoded with given encoding (default utf8). Ignores endian. + """ cstring = BytesIO() while True: c = self.read(1) @@ -170,16 +193,22 @@ def __init__(self, contents): self.read_bool = functools.partial(self.read_bits, 1) def done(self): - """ Returns true when all bytes in the buffer have been used""" + """ + Returns true when all bytes in the buffer have been used + """ return self.tell() == self.length def byte_align(self): - """ Moves cursor to the beginning of the next byte """ + """ + Moves cursor to the beginning of the next byte + """ self._next_byte = None self._bit_shift = 0 def read_uint8(self): - """ Returns the next 8 bits as an unsigned integer """ + """ + Returns the next 8 bits as an unsigned integer + """ data = ord(self._buffer.read(1)) if self._bit_shift != 0: @@ -192,7 +221,9 @@ def read_uint8(self): return data def read_uint16(self): - """ Returns the next 16 bits as an unsigned integer """ + """ + Returns the next 16 bits as an unsigned integer + """ data = self._buffer.read_uint16() if self._bit_shift != 0: @@ -206,7 +237,9 @@ def read_uint16(self): return data def read_uint32(self): - """ Returns the next 32 bits as an unsigned integer """ + """ + Returns the next 32 bits as an unsigned integer + """ data = self._buffer.read_uint32() if self._bit_shift != 0: @@ -220,7 +253,9 @@ def read_uint32(self): return data def read_uint64(self): - """ Returns the next 64 bits as an unsigned integer """ + """Returns + the next 64 bits as an unsigned integer + """ data = self._buffer.read_uint64() if self._bit_shift != 0: @@ -234,7 +269,9 @@ def read_uint64(self): return data def read_vint(self): - """ Reads a signed integer of variable length """ + """ + Reads a signed integer of variable length + """ byte = ord(self._buffer.read(1)) negative = byte & 0x01 result = (byte & 0x7F) >> 1 @@ -246,17 +283,23 @@ def read_vint(self): return -result if negative else result def read_aligned_bytes(self, count): - """ Skips to the beginning of the next byte and returns the next ``count`` bytes as a byte string """ + """ + Skips to the beginning of the next byte and returns the next ``count`` bytes as a byte string + """ self.byte_align() return self._buffer.read_bytes(count) def read_aligned_string(self, count, encoding="utf8"): - """ Skips to the beginning of the next byte and returns the next ``count`` bytes decoded with encoding (default utf8) """ + """ + Skips to the beginning of the next byte and returns the next ``count`` bytes decoded with encoding (default utf8) + """ self.byte_align() return self._buffer.read_string(count, encoding) def read_bytes(self, count): - """ Returns the next ``count*8`` bits as a byte string """ + """ + Returns the next ``count*8`` bits as a byte string + """ data = self._buffer.read_bytes(count) if self._bit_shift != 0: @@ -276,7 +319,9 @@ def read_bytes(self, count): return data def read_bits(self, count): - """ Returns the next ``count`` bits as an unsigned integer """ + """Returns + the next ``count`` bits as an unsigned integer + """ result = 0 bits = count bit_shift = self._bit_shift @@ -325,7 +370,9 @@ def read_bits(self, count): return result def read_frames(self): - """ Reads a frame count as an unsigned integer """ + """ + Reads a frame count as an unsigned integer + """ byte = self.read_uint8() time, additional_bytes = byte >> 2, byte & 0x03 if additional_bytes == 0: @@ -338,8 +385,9 @@ def read_frames(self): return time << 24 | self.read_uint16() << 8 | self.read_uint8() def read_struct(self, datatype=None): - """ Reads a nested data structure. If the type is not specified the - first byte is used as the type identifier. + """ + Reads a nested data structure. If the type is not specified + the first byte is used as the type identifier. """ self.byte_align() datatype = ord(self._buffer.read(1)) if datatype is None else datatype diff --git a/sc2reader/engine/engine.py b/sc2reader/engine/engine.py index e091fa3a..479872df 100644 --- a/sc2reader/engine/engine.py +++ b/sc2reader/engine/engine.py @@ -7,110 +7,111 @@ class GameEngine(object): - """ GameEngine Specification - -------------------------- + """ + GameEngine Specification + -------------------------- - The game engine runs through all the events for a given replay in - chronological order. For each event, event handlers from registered - plugins are executed in order of plugin registration from most general - to most specific. + The game engine runs through all the events for a given replay in + chronological order. For each event, event handlers from registered + plugins are executed in order of plugin registration from most general + to most specific. - Example Usage:: + Example Usage:: - class Plugin1(): - def handleCommandEvent(self, event, replay): - pass + class Plugin1(): + def handleCommandEvent(self, event, replay): + pass - class Plugin2(): - def handleEvent(self, event, replay): - pass + class Plugin2(): + def handleEvent(self, event, replay): + pass - def handleTargetUnitCommandEvent(self, event, replay): - pass + def handleTargetUnitCommandEvent(self, event, replay): + pass - ... + ... - engine = GameEngine(plugins=[Plugin1(), Plugin2()], **options) - engine.register_plugins(Plugin3(), Plugin(4)) - engine.reigster_plugin(Plugin(5)) - engine.run(replay) + engine = GameEngine(plugins=[Plugin1(), Plugin2()], **options) + engine.register_plugins(Plugin3(), Plugin(4)) + engine.reigster_plugin(Plugin(5)) + engine.run(replay) - Calls functions in the following order for a ``TargetUnitCommandEvent``:: + Calls functions in the following order for a ``TargetUnitCommandEvent``:: - Plugin1.handleCommandEvent(event, replay) - Plugin2.handleEvent(event, replay) - Plugin2.handleTargetUnitCommandEvent(event, replay) + Plugin1.handleCommandEvent(event, replay) + Plugin2.handleEvent(event, replay) + Plugin2.handleTargetUnitCommandEvent(event, replay) - Plugin Specification - ------------------------- + Plugin Specification + ------------------------- - Plugins can opt in to handle events with methods in the format: + Plugins can opt in to handle events with methods in the format: - def handleEventName(self, event, replay) + def handleEventName(self, event, replay) - In addition to handling specific event types, plugins can also - handle events more generally by handling built-in parent classes - from the list below:: + In addition to handling specific event types, plugins can also + handle events more generally by handling built-in parent classes + from the list below:: - * handleEvent - called for every single event of all types - * handleMessageEvent - called for events in replay.message.events - * handleGameEvent - called for events in replay.game.events - * handleTrackerEvent - called for events in replay.tracker.events - * handleCommandEvent - called for all types of command events - * handleControlGroupEvent - called for all player control group events + * handleEvent - called for every single event of all types + * handleMessageEvent - called for events in replay.message.events + * handleGameEvent - called for events in replay.game.events + * handleTrackerEvent - called for events in replay.tracker.events + * handleCommandEvent - called for all types of command events + * handleControlGroupEvent - called for all player control group events - Plugins may also handle optional ``InitGame`` and ``EndGame`` events generated - by the GameEngine before and after processing all the events: + Plugins may also handle optional ``InitGame`` and ``EndGame`` events generated + by the GameEngine before and after processing all the events: - * handleInitGame - is called prior to processing a new replay to provide - an opportunity for the plugin to clear internal state and set up any - replay state necessary. + * handleInitGame - is called prior to processing a new replay to provide + an opportunity for the plugin to clear internal state and set up any + replay state necessary. - * handleEndGame - is called after all events have been processed and - can be used to perform post processing on aggrated data or clean up - intermediate data caches. + * handleEndGame - is called after all events have been processed and + can be used to perform post processing on aggrated data or clean up + intermediate data caches. - Event handlers can choose to ``yield`` additional events which will be injected - into the event stream directly after the event currently being processed. This - feature allows for message passing between plugins. An ExpansionTracker plugin - could notify all other plugins of a new ExpansionEvent that they could opt to - process:: + Event handlers can choose to ``yield`` additional events which will be injected + into the event stream directly after the event currently being processed. This + feature allows for message passing between plugins. An ExpansionTracker plugin + could notify all other plugins of a new ExpansionEvent that they could opt to + process:: - def handleUnitDoneEvent(self, event, replay): - if event.unit.name == 'Nexus': - yield ExpansionEvent(event.frame, event.unit) - .... + def handleUnitDoneEvent(self, event, replay): + if event.unit.name == 'Nexus': + yield ExpansionEvent(event.frame, event.unit) + .... - If a plugin wishes to stop processing a replay it can yield a PluginExit event before returning:: + If a plugin wishes to stop processing a replay it can yield a PluginExit event before returning:: - def handleEvent(self, event, replay): - if len(replay.tracker_events) == 0: - yield PluginExit(self, code=0, details=dict(msg="tracker events required")) - return - ... + def handleEvent(self, event, replay): + if len(replay.tracker_events) == 0: + yield PluginExit(self, code=0, details=dict(msg="tracker events required")) + return + ... - def handleCommandEvent(self, event, replay): - try: - possibly_throwing_error() - catch Error as e: - logger.error(e) - yield PluginExit(self, code=0, details=dict(msg="Unexpected exception")) + def handleCommandEvent(self, event, replay): + try: + possibly_throwing_error() + catch Error as e: + logger.error(e) + yield PluginExit(self, code=0, details=dict(msg="Unexpected exception")) - The GameEngine will intercept this event and remove the plugin from the list of - active plugins for this replay. The exit code and details will be available from the - replay:: + The GameEngine will intercept this event and remove the plugin from the list of + active plugins for this replay. The exit code and details will be available from the + replay:: - code, details = replay.plugins['MyPlugin'] + code, details = replay.plugins['MyPlugin'] - If your plugin depends on another plugin, it is a good idea to implement handlePluginExit - and be alerted if the plugin that you require fails. This way you can exit gracefully. You - can also check to see if the plugin name is in ``replay.plugin_failures``:: + If your plugin depends on another plugin, it is a good idea to implement handlePluginExit + and be alerted if the plugin that you require fails. This way you can exit gracefully. You + can also check to see if the plugin name is in ``replay.plugin_failures``:: - if 'RequiredPlugin' in replay.plugin_failures: - code, details = replay.plugins['RequiredPlugin'] - message = "RequiredPlugin failed with code: {0}. Cannot continue.".format(code) - yield PluginExit(self, code=1, details=dict(msg=message)) + if 'RequiredPlugin' in replay.plugin_failures: + code, details = replay.plugins['RequiredPlugin'] + message = "RequiredPlugin failed with code: {0}. Cannot continue.".format(code) + yield PluginExit(self, code=1, details=dict(msg=message)) """ def __init__(self, plugins=[]): diff --git a/sc2reader/engine/plugins/creeptracker.py b/sc2reader/engine/plugins/creeptracker.py index db5e6ac8..8dde77c9 100644 --- a/sc2reader/engine/plugins/creeptracker.py +++ b/sc2reader/engine/plugins/creeptracker.py @@ -86,8 +86,8 @@ def handleEndGame(self, event, replay): for player in replay.players: if player.play_race[0] == "Z": self.creepTracker.reduce_cgu_per_minute(player.pid) - player.creep_spread_by_minute = self.creepTracker.get_creep_spread_area( - player.pid + player.creep_spread_by_minute = ( + self.creepTracker.get_creep_spread_area(player.pid) ) # note that player.max_creep_spread may be a tuple or an int if player.creep_spread_by_minute: diff --git a/sc2reader/engine/plugins/selection.py b/sc2reader/engine/plugins/selection.py index 59d4ee02..52f50c65 100644 --- a/sc2reader/engine/plugins/selection.py +++ b/sc2reader/engine/plugins/selection.py @@ -3,25 +3,26 @@ class SelectionTracker(object): - """ Tracks a player's active selection as an input into other plugins. + """ + Tracks a player's active selection as an input into other plugins. - In some situations selection tracking isn't perfect. The plugin will - detect these situations and report errors. For a player will a high - level of selection errors, it may be best to ignore the selection - results as they could have been severely compromised. + In some situations selection tracking isn't perfect. The plugin will + detect these situations and report errors. For a player will a high + level of selection errors, it may be best to ignore the selection + results as they could have been severely compromised. - Exposes the following interface, directly integrated into the player: + Exposes the following interface, directly integrated into the player: - for person in replay.entities: - total_errors = person.selection_errors + for person in replay.entities: + total_errors = person.selection_errors - selection = person.selection - control_group_0 = selection[0] - ... - control_group_9 = selection[9] - active_selection = selection[10] + selection = person.selection + control_group_0 = selection[0] + ... + control_group_9 = selection[9] + active_selection = selection[10] - # TODO: list a few error inducing situations + # TODO: list a few error inducing situations """ name = "SelectionTracker" @@ -69,7 +70,9 @@ def _select(self, selection, units): return sorted(set(selection + units)) def _deselect(self, selection, mode, data): - """Returns false if there was a data error when deselecting""" + """ + Returns false if there was a data error when deselecting + """ if mode == "None": return selection, False diff --git a/sc2reader/events/game.py b/sc2reader/events/game.py index 346adb96..02a054dd 100644 --- a/sc2reader/events/game.py +++ b/sc2reader/events/game.py @@ -685,8 +685,11 @@ def __init__(self, frame, pid, data): self.custom_resource = self.resources[3] if len(self.resources) >= 4 else None def __str__(self): - return self._str_prefix() + " requests {0} minerals, {1} gas, {2} terrazine, and {3} custom".format( - self.minerals, self.vespene, self.terrazine, self.custom + return ( + self._str_prefix() + + " requests {0} minerals, {1} gas, {2} terrazine, and {3} custom".format( + self.minerals, self.vespene, self.terrazine, self.custom + ) ) diff --git a/sc2reader/events/message.py b/sc2reader/events/message.py index 2fdb9cfd..57da5dce 100644 --- a/sc2reader/events/message.py +++ b/sc2reader/events/message.py @@ -9,7 +9,7 @@ @loggable class MessageEvent(Event): """ - Parent class for all message events. + Parent class for all message events. """ def __init__(self, frame, pid): @@ -36,7 +36,7 @@ def __str__(self): @loggable class ChatEvent(MessageEvent): """ - Records in-game chat events. + Records in-game chat events. """ def __init__(self, frame, pid, target, text): @@ -60,7 +60,7 @@ def __init__(self, frame, pid, target, text): @loggable class ProgressEvent(MessageEvent): """ - Sent during the load screen to update load process for other clients. + Sent during the load screen to update load process for other clients. """ def __init__(self, frame, pid, progress): @@ -73,7 +73,7 @@ def __init__(self, frame, pid, progress): @loggable class PingEvent(MessageEvent): """ - Records pings made by players in game. + Records pings made by players in game. """ def __init__(self, frame, pid, target, x, y): diff --git a/sc2reader/factories/sc2factory.py b/sc2reader/factories/sc2factory.py index 690797a3..1bde3ac1 100644 --- a/sc2reader/factories/sc2factory.py +++ b/sc2reader/factories/sc2factory.py @@ -28,7 +28,8 @@ @log_utils.loggable class SC2Factory(object): - """The SC2Factory class acts as a generic loader interface for all + """ + The SC2Factory class acts as a generic loader interface for all available to sc2reader resources. At current time this includes :class:`~sc2reader.resources.Replay` and :class:`~sc2reader.resources.Map` resources. These resources can be loaded in both singular and plural contexts with: @@ -81,57 +82,79 @@ def __init__(self, **options): # Primary Interface def load_replay(self, source, options=None, **new_options): - """Loads a single sc2replay file. Accepts file path, url, or file object.""" + """ + Loads a single sc2replay file. Accepts file path, url, or file object. + """ return self.load(Replay, source, options, **new_options) def load_replays(self, sources, options=None, **new_options): - """Loads a collection of sc2replay files, returns a generator.""" + """ + Loads a collection of sc2replay files, returns a generator. + """ return self.load_all( Replay, sources, options, extension="SC2Replay", **new_options ) def load_localization(self, source, options=None, **new_options): - """Loads a single s2ml file. Accepts file path, url, or file object.""" + """ + Loads a single s2ml file. Accepts file path, url, or file object. + """ return self.load(Localization, source, options, **new_options) def load_localizations(self, sources, options=None, **new_options): - """Loads a collection of s2ml files, returns a generator.""" + """ + Loads a collection of s2ml files, returns a generator. + """ return self.load_all( Localization, sources, options, extension="s2ml", **new_options ) def load_map(self, source, options=None, **new_options): - """Loads a single s2ma file. Accepts file path, url, or file object.""" + """ + Loads a single s2ma file. Accepts file path, url, or file object. + """ return self.load(Map, source, options, **new_options) def load_maps(self, sources, options=None, **new_options): - """Loads a collection of s2ma files, returns a generator.""" + """ + Loads a collection of s2ma files, returns a generator. + """ return self.load_all(Map, sources, options, extension="s2ma", **new_options) def load_game_summary(self, source, options=None, **new_options): - """Loads a single s2gs file. Accepts file path, url, or file object.""" + """ + Loads a single s2gs file. Accepts file path, url, or file object. + """ return self.load(GameSummary, source, options, **new_options) def load_game_summaries(self, sources, options=None, **new_options): - """Loads a collection of s2gs files, returns a generator.""" + """ + Loads a collection of s2gs files, returns a generator. + """ return self.load_all( GameSummary, sources, options, extension="s2gs", **new_options ) def configure(self, cls=None, **options): - """ Configures the factory to use the supplied options. If cls is specified - the options will only be applied when loading that class""" + """ + Configures the factory to use the supplied options. If cls is specified + the options will only be applied when loading that class + """ if isinstance(cls, basestring): cls = self._resource_name_map.get[cls.lower()] cls = cls or Resource self.options[cls].update(options) def reset(self): - "Resets the options to factory defaults" + """ + Resets the options to factory defaults + """ self.options = defaultdict(dict) def register_plugin(self, cls, plugin): - "Registers the given Plugin to be run on classes of the supplied name." + """ + Registers the given Plugin to be run on classes of the supplied name. + """ if isinstance(cls, basestring): cls = self._resource_name_map.get(cls.lower(), Resource) self.plugins.append((cls, plugin)) @@ -170,7 +193,9 @@ def _get_options(self, cls, **new_options): return options def _load_resources(self, resources, options=None, **new_options): - """Collections of resources or a path to a directory""" + """ + Collections of resources or a path to a directory + """ options = options or self._get_options(Resource, **new_options) # Path to a folder, retrieve all relevant files as the collection @@ -190,7 +215,9 @@ def load_local_resource_contents(self, location, **options): return resource_file.read() def _load_resource(self, resource, options=None, **new_options): - """http links, filesystem locations, and file-like objects""" + """ + http links, filesystem locations, and file-like objects + """ options = options or self._get_options(Resource, **new_options) if isinstance(resource, utils.DepotFile): diff --git a/sc2reader/log_utils.py b/sc2reader/log_utils.py index a53656c8..c4288820 100644 --- a/sc2reader/log_utils.py +++ b/sc2reader/log_utils.py @@ -67,12 +67,12 @@ def add_log_handler(handler, level="WARN", format=None, datefmt=None): def get_logger(entity): """ - Retrieves loggers from the enties fully scoped name. + Retrieves loggers from the enties fully scoped name. - get_logger(Replay) -> sc2reader.replay.Replay - get_logger(get_logger) -> sc2reader.utils.get_logger + get_logger(Replay) -> sc2reader.replay.Replay + get_logger(get_logger) -> sc2reader.utils.get_logger - :param entity: The entity for which we want a logger. + :param entity: The entity for which we want a logger. """ try: return logging.getLogger(entity.__module__ + "." + entity.__name__) diff --git a/sc2reader/objects.py b/sc2reader/objects.py index 5d37f62a..d9e53035 100644 --- a/sc2reader/objects.py +++ b/sc2reader/objects.py @@ -283,14 +283,17 @@ def __init__(self, uid, init_data): @property def url(self): - """The player's formatted Battle.net profile url""" + """ + The player's formatted Battle.net profile url + """ return self.URL_TEMPLATE.format( **self.__dict__ ) # region=self.region, toon_id=self.toon_id, subregion=self.subregion, name=self.name.('utf8')) class Observer(Entity, User): - """ Extends :class:`Entity` and :class:`User`. + """ + Extends :class:`Entity` and :class:`User`. :param integer sid: The entity's unique slot id. :param dict slot_data: The slot data associated with this entity @@ -314,7 +317,8 @@ def __repr__(self): class Computer(Entity, Player): - """ Extends :class:`Entity` and :class:`Player` + """ + Extends :class:`Entity` and :class:`Player` :param integer sid: The entity's unique slot id. :param dict slot_data: The slot data associated with this entity @@ -338,7 +342,8 @@ def __repr__(self): class Participant(Entity, User, Player): - """ Extends :class:`Entity`, :class:`User`, and :class:`Player` + """ + Extends :class:`Entity`, :class:`User`, and :class:`Player` :param integer sid: The entity's unique slot id. :param dict slot_data: The slot data associated with this entity diff --git a/sc2reader/utils.py b/sc2reader/utils.py index 41e4ed09..c1cfd437 100644 --- a/sc2reader/utils.py +++ b/sc2reader/utils.py @@ -93,12 +93,16 @@ def __init__(self, name=None, r=0, g=0, b=0, a=255): @property def rgba(self): - """ Returns a tuple containing the color's (r,g,b,a) """ + """ + Returns a tuple containing the color's (r,g,b,a) + """ return (self.r, self.g, self.b, self.a) @property def hex(self): - """The hexadecimal representation of the color""" + """ + The hexadecimal representation of the color + """ return "{0.r:02X}{0.g:02X}{0.b:02X}".format(self) def __str__(self): @@ -197,23 +201,30 @@ def get_files( class Length(timedelta): - """ Extends the builtin timedelta class. See python docs for more info on - what capabilities this gives you. + """ + Extends the builtin timedelta class. See python docs for more info on + what capabilities this gives you. """ @property def hours(self): - """ The number of hours in represented. """ + """ + The number of hours in represented. + """ return self.seconds // 3600 @property def mins(self): - """ The number of minutes in excess of the hours. """ + """ + The number of minutes in excess of the hours. + """ return self.seconds // 60 % 60 @property def secs(self): - """ The number of seconds in excess of the minutes. """ + """ + The number of seconds in excess of the minutes. + """ return self.seconds % 60 def __str__(self):