-1

I have a class something like the following that works in Python 3. How can I get it to work also in Python 2?

class Palette(list):

    def __init__(
        self,
        name        = None, # string name
        description = None, # string description
        colors      = None, # list of colors
        *args
        ):
        super().__init__(self, *args)   
        self._name          = name
        self._description   = description
        self.extend(colors)

    def name(
        self
        ):
        return self._name

    def set_name(
        self,
        name = None
        ):
        self._name = name

    def description(
        self
        ):
        return self._description

    def set_description(
        self,
        description = None
        ):
        self._description = description

palette1 = Palette(
    name   = "palette 1",
    colors = [
        "#F1E1BD",
        "#EEBA85",
        "#E18D76",
        "#9C837E",
        "#5B7887"
    ]
)

palette1.set_description("This is palette 1.")

print(palette1.name())
print(palette1.description())
print(palette1)
d3pd
  • 7,935
  • 24
  • 76
  • 127
  • 3
    Not about the issue: man, that style of argument formatting is next to unreadable; at least follow PEP8 and indent those arguments one level further. – Martijn Pieters Sep 08 '15 at 12:52
  • 1
    Could you narrow it down a bit: **what does it currently do on Python 2?** Also, what's with the getters and setters? Are you unaware of `@property`? – jonrsharpe Sep 08 '15 at 12:54

1 Answers1

2

You only have to change your super() call to use explicit arguments:

super(Palette, self).__init__(*args)   

and your code will work just fine in both Python 2 and Python 3. See Why is Python 3.x's super() magic? for background information on why the above is equivalent to super().__init__(*args). Also, do not pass in self again, or you'll create a circular reference as you include self in the contents of the list.

Note that it is more pythonic use property objects instead of explicit getters and setters:

class Palette(list):
    def __init__(self, name=None, description=None, colors=None, *args):
        super(Palette, self).__init__(args)   
        self.name = name
        self.description = description
        self.extend(colors)

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        self._name = name

    @name.deleter
    def name(self):
        self.name = None

    @property
    def description(self):
        return self._description

    @description.setter
    def description(self, description):
        self._description = description

    @description.deleter
    def description(self):
        self.description = None

then use

palette1.description = "This is palette 1."

I also took the liberty of reducing the amount of whitespace in the function definitions; putting each and every argument on a new line makes it very hard to get an overview of the class as function bodies are needlessly pushed down.

As these properties don't actually do anything other than wrap an attribute by the same name with an underscore, you may as well just leave them out altogether. Unlike Java, in Python you can freely switch between using attributes directly, and later on swapping attributes out for a property object; you are not tied into one or the other.

Note that in both Python 2 and Python 3, you cannot pass in positional arguments; the following doesn't work:

Palette('#F1E1BD', '#EEBA85', name='palette2')

because the first positional argument will be assigned to the name argument:

>>> Palette('#F1E1BD', '#EEBA85', name='palette2')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init__() got multiple values for argument 'name'

To support that use case, you need to not name the keyword arguments in the signature, and only use **kwargs, then retrieve your keyword arguments from that. Pass in any positional arguments as one argument so that list() takes any number of positional arguments as the contents for the new list:

class Palette(list):
    def __init__(self, *args, **kwargs):
        super(Palette, self).__init__(args)
        self.name = kwargs.pop('name', None)
        self.description = kwargs.pop('description', None)
        self.extend(kwargs.pop('colors', []))
        if kwargs:
            raise TypeError('{} does not take {} as argument(s)'.format(
                type(self).__name__, ', '.join(kwargs)))

Demo:

>>> class Palette(list):
...     def __init__(self, *args, **kwargs):
...         super(Palette, self).__init__(args)
...         self.name = kwargs.pop('name', None)
...         self.description = kwargs.pop('description', None)
...         self.extend(kwargs.pop('colors', []))
...         if kwargs:
...             raise TypeError('{} does not take {} as argument(s)'.format(
...                 type(self).__name__, ', '.join(kwargs)))
... 

>>> palette1 = Palette(
...     name   = "palette 1",
...     colors = [
...         "#F1E1BD",
...         "#EEBA85",
...         "#E18D76",
...         "#9C837E",
...         "#5B7887"
...     ]
... )
>>> palette2 = Palette("#F1E1BD", "#EEBA85", "#E18D76", "#9C837E", "#5B7887",
...                    name="palette 2")
>>> palette1
['#F1E1BD', '#EEBA85', '#E18D76', '#9C837E', '#5B7887']
>>> palette2
['#F1E1BD', '#EEBA85', '#E18D76', '#9C837E', '#5B7887']
>>> palette1.name
'palette 1'
>>> palette2.name
'palette 2'
>>> palette1.description = 'This is palette 1.'
>>> palette2.description = 'This is palette 2.'
>>> Palette(foo='bar', spam='eggs')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 9, in __init__
TypeError: Palette does not take foo, spam as argument(s)
Community
  • 1
  • 1
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • I've removed my objections. I do appreciate your taking the time to explain correct but "just leave them out altogether" methods; thanks. – msw Sep 08 '15 at 13:26