如何使从列表继承并使用关键字参数的 class 同时在 Python 2 和 Python 3 中工作?

How can a class that inherits from list and uses keyword arguments be made to work in both Python 2 and Python 3?

我有一个 class 在 Python 3 中工作的类似下面的东西。我怎样才能让它在 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)

您只需将 super() 调用更改为使用显式参数:

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

并且您的代码在 Python 2 和 Python 3 中都可以正常工作。请参阅 Why is Python 3.x's super() magic? 了解为什么以上等同于 super().__init__(*args) 的背景信息。此外,不要 再次传入 self,否则您将在列表内容中包含 self 时创建循环引用。

请注意,使用 property 对象而不是显式的 getter 和 setter 更符合 pythonic:

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

然后使用

palette1.description = "This is palette 1."

我还冒昧地减少了函数定义中的空格数量;将每个参数都放在一个新行上使得很难获得 class 的概述,因为函数体被不必要地向下推。

由于这些属性实际上 除了用下划线包裹同名属性之外的任何东西,你最好 把它们放在外面一共。与 Java 不同,在 Python 中,您可以在直接使用属性和稍后为 属性 对象交换属性之间自由切换;你不属于其中之一。

请注意,在Python 2 和Python 3 中,您不能传入位置参数;以下不起作用:

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

因为第一个位置参数将分配给 name 参数:

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

要支持该用例,您需要 命名签名中的关键字参数,并且仅使用**kwargs,然后从中检索您的关键字参数。将任何位置参数作为 一个参数 传递,以便 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)))

演示:

>>> 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)