__new__() does not return anything, on singletong pattern

Steven D'Aprano steve+comp.lang.python at pearwood.info
Thu Mar 12 07:29:24 EDT 2015


Mario Figueiredo wrote:

> On Thu, 12 Mar 2015 16:31:12 +1100, Steven D'Aprano
> <steve+comp.lang.python at pearwood.info> wrote:
> 
>>Mario Figueiredo wrote:
>>
>>
>>If this is supposed to be a singleton, you can't create more instances.
>>The point of a singleton that there is only one instance (or perhaps a
>>small number, two or three say). Why do you need two different ways to
>>create instances if you only have one instance?
>>
> 
> The map is instantiated from the contents of a saved map file on the
> main application.
> 
> A different application, a map editor, needs to also instantiate an
> object of the class Map. But in this case the map needs to either be
> empty (if the user wants to create a new map), or loaded from the
> saved map file (if the user wants to edit an existing map).

An empty map is just a special case of an existing map, one with no features
added.

I would have a loadfile() method which takes a filename on disk, opens the
file and passes the contents (or the open file object) to another method,
load() to do the actual work:


class Map:
    def __new__(cls, width, height, fill, treasure=None):
        # Include additional args as needed.
        map = super().__new__.(cls)
        map.width = width
        map.height = height
        map.fill = fill
        map.treasure = treasure
        return map

    @classmethod
    def loadfile(cls, filename):
        with open(filename, 'r') as f:
            map = cls.load(f)
        return map

    @classmethod
    def load(cls, f):
        # validate and parse file contents
        # extract width, height, fill, etc.
        map = cls(width, height, fill, treasure)
        return map


To get an empty map, you pass the load method a file-like object that
contains whatever an empty map data file will contain. That could literally
be an external file, if you so choose, or you could simply read it from a
global constant or even an embedded string/bytes object:

    @classmethod
    def new(cls):
        """Return a new empty map."""
        emptymap = b"blah blah blah width=100 height=100 fill=1"
        f = io.StringIO(emptymap)
        return cls.load(f)


This, naturally, assumes that your map format is simple enough and small
enough that an empty map can be embedded into your code. If an empty map is
4MB on disk, you probably don't want to do this :-)

Alternatively, set the default arguments to the __new__ constructor to be
whatever values an empty map uses:

    def __new__(cls, width=100, height=100, fill=1, treasure=None):
        ...


and now your "create empty map" command just calls Map() with no arguments.
    

>>> I added the following method to the class definition, above:
>>> 
>>>     @classmethod
>>>     def generate(cls, width, height, fill=terrain[6]):
>>>         if Map._instance is None:
>>>             Map._instance = super(Map, cls).__new__(cls)
>>>         else:
>>>             raise Exception('Cannot generate an instance of Map.')
>>> 
>>>         Map._instance.author = None
>>>         Map._instance.name = None
>>
>>Since this method modifies the singleton instance in place, it doesn't
>>generate a new instance. It shouldn't be called generate().
> 
> No sure what you mean. That method either creates a new instance or
> raises an exception. It doesn't modify an instance in-place.

My mistake, I was confused. You're right, if the instance already exists, it
raises an exception.



>>>         Map._instance.description = None
>>>              # etc...
>>>              self.cells = [Cell(fill)] * width * height
>>>         return Map._instance
>>
>>That's not your actual code, since the indentation is wrong.
> 
>      Map._instance.description = None
>      # etc...
>      self.cells = [Cell(fill)] * width * height
>      return Map._instance



What's "self"? That's a classmethod, it's not usual to have a self local
variable.



-- 
Steven




More information about the Python-list mailing list