自动初始化实例变量?

122 投票
17 回答
55101 浏览
提问于 2025-04-15 14:07

我有一个看起来像这样的Python类:

class Process:
    def __init__(self, PID, PPID, cmd, FDs, reachable, user):

接下来是:

        self.PID=PID
        self.PPID=PPID
        self.cmd=cmd
        ...

有没有什么方法可以像C++的初始化列表那样自动初始化这些实例变量?这样可以省去很多重复的代码。

17 个回答

37

如果你使用的是Python 2.6或更高版本,可以使用collections.namedtuple这个功能:

>>> from collections import namedtuple
>>> Process = namedtuple('Process', 'PID PPID cmd')
>>> proc = Process(1, 2, 3)
>>> proc.PID
1
>>> proc.PPID
2

这个功能特别适合用在你的类只是用来存放一堆值的时候。

67

对于Python 3.7及以上版本,你可以使用数据类(Data Class),这是一种非常符合Python风格且易于维护的方法,可以帮助你实现想要的功能。

数据类允许你为你的类定义字段,这些字段就是你自动初始化的实例变量。

它的样子大概是这样的:

@dataclass
class Process:
    PID: int
    PPID: int
    cmd: str
    ...

你的类中会自动包含__init__方法。

需要注意的是,这里需要类型提示,所以我在例子中使用了intstr。如果你不知道字段的类型,可以使用来自typing模块的Any

与其他提议的解决方案相比,数据类有很多优点:

  • 它是明确的:所有字段都是可见的,这符合Python的哲学,使代码更易读和易于维护。可以和使用**kwargs进行比较。
  • 它可以有方法。就像其他任何类一样。
  • 它允许你在自动生成的__init__方法之外进行扩展,使用__post_init__方法。

编辑:避免使用命名元组(NamedTuples)的原因

有些人建议在这种情况下使用namedtuple,但命名元组有一些与Python类不同的行为,这些行为一开始并不明显,需要了解:

1. 命名元组是不可变的

不可变性可能有用,但这可能不是你想要的实例特性。如果你使用@dataclass装饰器中的frozen=True参数,数据类也可以在某种程度上是不可变的。

2. 命名元组的__eq__行为像元组一样

在Python中,SomeNamedTuple(a=1, b=2) == AnotherNamedTuple(c=1, d=2)的结果是True!命名元组的__eq__函数在比较时只考虑值和这些值在被比较实例中的位置,而不考虑它们的类或字段名称。

127

你可以使用一个装饰器:

from functools import wraps
import inspect

def initializer(func):
    """
    Automatically assigns the parameters.

    >>> class process:
    ...     @initializer
    ...     def __init__(self, cmd, reachable=False, user='root'):
    ...         pass
    >>> p = process('halt', True)
    >>> p.cmd, p.reachable, p.user
    ('halt', True, 'root')
    """
    names, varargs, keywords, defaults = inspect.getargspec(func)

    @wraps(func)
    def wrapper(self, *args, **kargs):
        for name, arg in list(zip(names[1:], args)) + list(kargs.items()):
            setattr(self, name, arg)

        for name, default in zip(reversed(names), reversed(defaults)):
            if not hasattr(self, name):
                setattr(self, name, default)

        func(self, *args, **kargs)

    return wrapper

用它来装饰 __init__ 方法:

class process:
    @initializer
    def __init__(self, PID, PPID, cmd, FDs, reachable, user):
        pass

输出结果:

>>> c = process(1, 2, 3, 4, 5, 6)
>>> c.PID
1
>>> dir(c)
['FDs', 'PID', 'PPID', '__doc__', '__init__', '__module__', 'cmd', 'reachable', 'user'

撰写回答