【问题标题】:Automatically initialize instance variables?自动初始化实例变量?
【发布时间】:2010-11-26 05:27:06
【问题描述】:

我有一个如下所示的 python 类:

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

接着是:

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

有没有办法自动初始化这些实例变量,比如 C++ 的初始化列表?它将节省大量冗余代码。

【问题讨论】:

标签: python class initialization-list


【解决方案1】:

你可以使用装饰器:

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'

【讨论】:

  • 这有效并回答了这个问题,所以我投了赞成票。但我保留了 Ferdidand Beyer 的回答:“显式胜于隐式”
  • +1 获得解决我问题的好答案。但它不应该是语言的核心功能吗?你认为写 PEP 值得吗?
  • 这是一个非常好的答案 - 这已直接进入我的工具箱。
  • @NadiaAlramli 我认为代码中有一个小错误。看这里gist.github.com/pmav99/137dbf4681be9a58de74 (original.py)
  • 当前示例有一个错误,如果签名不包含默认参数,它将无法工作。您需要检查以确保名称和默认值不是无。例如:如果名称和默认值:
【解决方案2】:

对于 Python 3.7+,您可以使用 Data Class,这是一种非常 Python 且可维护的方式来做您想做的事。

它允许您为您的类定义字段,它们是您自动初始化的实例变量。

它看起来像这样:

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

__init__方法已经在你的课堂上了。

注意这里需要类型提示,这就是我在示例中使用intstr 的原因。如果您不知道您的字段类型,可以使用Any from the typing module

与建议的解决方案相比,Data Class 具有许多优点:

  • 显式:所有字段都是可见的,尊重Python的禅意,使其可读可维护。将其与**kwargs 的使用进行比较。
  • 它可以有方法。就像任何其他课程一样。
  • 它允许您使用__post_init__ 方法超越自动__init__

编辑:避免使用 NamedTuples 的原因

有些人建议在这种情况下使用namedtuple,但namedtuples 有一些与Python 类不同的行为,这些行为一开始并不明显,应该广为人知:

1. NamedTuples 是不可变的

不变性可能很有用,但也许它不是您想要的实例。如果您在 @dataclass 装饰器上使用参数 frozen=True,DataClasses 也可以是不可变的。

2。 NamedTuples __eq__ 的行为类似于 Tuple 的

在 Python 中,SomeNamedTuple(a=1, b=2) == AnotherNamedTuple(c=1, d=2)True!在比较中使用的 NamedTuple 的 __eq__ 函数只考虑比较实例上的值和这些值的位置,而不考虑它们的类或字段的名称。

【讨论】:

  • 只有在类的目的是存储数据时才应该使用它。
  • 或者围绕存储数据进行开发。
  • 你能解释一下为什么 dataclass 应该只用于存储数据的类,而不是其他行为吗?我可能会为此创建一个新的 SO 帖子,以更好地了解其适当的用例。谢谢。
  • Data Classes can be thought of as "mutable namedtuples with defaults". -- PEP557
  • 你需要导入它。 from dataclasses import dataclass
【解决方案3】:

如果您使用的是 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

这是合适的,尤其是当你的类实际上只是一大袋值时。

【讨论】:

【解决方案4】:

引用Zen of Python

显式优于隐式。

【讨论】:

  • 初始化列表声明不够明确吗?
  • 我猜。但我认为没有理由在语言中添加类似的东西。我显然更喜欢多个赋值语句而不是幕后的某种装饰器魔法。
  • @Ferdinand,我同意在语言中拥有可以完全在标准库中的东西是愚蠢的,但是,它应该在标准库中,因为“美丽胜于丑陋”需要优先级,并且许多重复的分配是丑陋的(任何形式的重复也是如此)。
  • 好吧,反击:DWIM > DWIS
  • 我同意装饰器比作业列表更漂亮,但 PyCharm 不理解它使它变得更丑:-(
【解决方案5】:

你可以做的另一件事:

class X(object):
    def __init__(self, a,b,c,d):
        vars = locals() # dict of local names
        self.__dict__.update(vars) # __dict__ holds and object's attributes
        del self.__dict__["self"] # don't need `self`

但我推荐的唯一解决方案,除了拼写出来之外,是“在你的编辑器中创建一个宏”;-p

【讨论】:

  • 很好地删除“自我”。
【解决方案6】:

您可以使用关键字参数轻松做到这一点,例如像这样:

>>> class D:
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

>>> D(test='d').test
'd'

位置参数的类似实现是:

>> class C:
    def __init__(self, *args):
        self.t, self.d = args


>>> C('abc', 'def').t
'abc'
>>> C('abc', 'def').d
'def'

在我看来这并不能解决你的问题。

【讨论】:

  • 我喜欢的另一个变体是self.__dict__.update( **kwargs )
  • 还不如使用 locals() 并输入普通参数。
【解决方案7】:

Nadia 的解决方案更好更强大,但我觉得这也很有趣:

def constructor(*arg_names):
  def __init__(self, *args):
    for name, val in zip(arg_names, args):
      self.__setattr__(name, val)
  return __init__


class MyClass(object):
  __init__ = constructor("var1", "var2", "var3")


>>> c = MyClass("fish", "cheese", "beans")
>>> c.var2
"cheese"

【讨论】:

    【解决方案8】:

    出于同样的目的,我需要一些东西,但现有的答案都没有涵盖我测试的所有案例。 Nadia 的答案与我所寻找的最接近,因此我以她的代码为基础开始。

    下面的装饰器适用于所有有效的参数组合:

    Positional                                          __init__(self, a, b                )
    Keyword                                             __init__(self, a=None, b=None      )
    Positional + Keyword                                __init__(self, a, b, c=None, d=None)
    Variable Positional                                 __init__(self, *a                  )
    Variable Positional + Keyword                       __init__(self, *a, b=None          )
    Variable Positional + Variable Keyword              __init__(self, *a, **kwargs        )
    Positional + Variable Positional + Keyword          __init__(self, a, *b, c=None       )
    Positional + Variable Positional + Variable Keyword __init__(self, a, *b, **kwargs     )
    Keyword Only                                        __init__(self, *, a=None           )
    Positional + Keyword Only                           __init__(self, a, *, b=None        )
    

    它还实现了标准的_-prefix 约定,以允许__init__-private 变量不会分配给类实例。


    ###  StdLib  ###
    from functools import wraps
    import inspect
    
    
    ###########################################################################################################################
    #//////|   Decorator   |//////////////////////////////////////////////////////////////////////////////////////////////////#
    ###########################################################################################################################
    
    def auto_assign_arguments(function):
    
      @wraps(function)
      def wrapped(self, *args, **kwargs):
        _assign_args(self, list(args), kwargs, function)
        function(self, *args, **kwargs)
    
      return wrapped
    
    
    ###########################################################################################################################
    #//////|   Utils   |//////////////////////////////////////////////////////////////////////////////////////////////////////#
    ###########################################################################################################################
    
    def _assign_args(instance, args, kwargs, function):
    
      def set_attribute(instance, parameter, default_arg):
        if not(parameter.startswith("_")):
          setattr(instance, parameter, default_arg)
    
      def assign_keyword_defaults(parameters, defaults):
        for parameter, default_arg in zip(reversed(parameters), reversed(defaults)):
          set_attribute(instance, parameter, default_arg)
    
      def assign_positional_args(parameters, args):
        for parameter, arg in zip(parameters, args.copy()):
          set_attribute(instance, parameter, arg)
          args.remove(arg)
    
      def assign_keyword_args(kwargs):
        for parameter, arg in kwargs.items():
          set_attribute(instance, parameter, arg)
      def assign_keyword_only_defaults(defaults):
        return assign_keyword_args(defaults)
    
      def assign_variable_args(parameter, args):
        set_attribute(instance, parameter, args)
    
      POSITIONAL_PARAMS, VARIABLE_PARAM, _, KEYWORD_DEFAULTS, _, KEYWORD_ONLY_DEFAULTS, _ = inspect.getfullargspec(function)
      POSITIONAL_PARAMS = POSITIONAL_PARAMS[1:] # remove 'self'
    
      if(KEYWORD_DEFAULTS     ): assign_keyword_defaults     (parameters=POSITIONAL_PARAMS,  defaults=KEYWORD_DEFAULTS)
      if(KEYWORD_ONLY_DEFAULTS): assign_keyword_only_defaults(defaults=KEYWORD_ONLY_DEFAULTS                          )
      if(args                 ): assign_positional_args      (parameters=POSITIONAL_PARAMS,  args=args                )
      if(kwargs               ): assign_keyword_args         (kwargs=kwargs                                           )
      if(VARIABLE_PARAM       ): assign_variable_args        (parameter=VARIABLE_PARAM,      args=args                )
    
    
    ###########################################################################################################################$#//////|   Tests   |//////////////////////////////////////////////////////////////////////////////////////////////////////#$###########################################################################################################################$$if __name__ == "__main__":$$#######|   Positional   |##################################################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a, b):$      pass$$  t = T(1, 2)$  assert (t.a == 1) and (t.b == 2)$$#######|   Keyword   |#####################################################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a="KW_DEFAULT_1", b="KW_DEFAULT_2"):$      pass$$  t = T(a="kw_arg_1", b="kw_arg_2")$  assert (t.a == "kw_arg_1") and (t.b == "kw_arg_2")$$#######|   Positional + Keyword   |########################################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a, b, c="KW_DEFAULT_1", d="KW_DEFAULT_2"):$      pass$$  t = T(1, 2)$  assert (t.a == 1) and (t.b == 2) and (t.c == "KW_DEFAULT_1") and (t.d == "KW_DEFAULT_2")$$  t = T(1, 2, c="kw_arg_1")$  assert (t.a == 1) and (t.b == 2) and (t.c == "kw_arg_1") and (t.d == "KW_DEFAULT_2")$$  t = T(1, 2, d="kw_arg_2")$  assert (t.a == 1) and (t.b == 2) and (t.c == "KW_DEFAULT_1") and (t.d == "kw_arg_2")$$#######|   Variable Positional   |#########################################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, *a):$      pass$$  t = T(1, 2, 3)$  assert (t.a == [1, 2, 3])$$#######|   Variable Positional + Keyword   |###############################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, *a, b="KW_DEFAULT_1"):$      pass$$  t = T(1, 2, 3)$  assert (t.a == [1, 2, 3]) and (t.b == "KW_DEFAULT_1")$$  t = T(1, 2, 3, b="kw_arg_1")$  assert (t.a == [1, 2, 3]) and (t.b == "kw_arg_1")$$#######|   Variable Positional + Variable Keyword   |######################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, *a, **kwargs):$      pass$$  t = T(1, 2, 3, b="kw_arg_1", c="kw_arg_2")$  assert (t.a == [1, 2, 3]) and (t.b == "kw_arg_1") and (t.c == "kw_arg_2")$$#######|   Positional + Variable Positional + Keyword   |##################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a, *b, c="KW_DEFAULT_1"):$      pass$$  t = T(1, 2, 3, c="kw_arg_1")$  assert (t.a == 1) and (t.b == [2, 3]) and (t.c == "kw_arg_1")$$#######|   Positional + Variable Positional + Variable Keyword   |#########################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a, *b, **kwargs):$      pass$$  t = T(1, 2, 3, c="kw_arg_1", d="kw_arg_2")$  assert (t.a == 1) and (t.b == [2, 3]) and (t.c == "kw_arg_1") and (t.d == "kw_arg_2")$$#######|   Keyword Only   |################################################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, *, a="KW_DEFAULT_1"):$      pass$$  t = T(a="kw_arg_1")$  assert (t.a == "kw_arg_1")$$#######|   Positional + Keyword Only   |###################################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a, *, b="KW_DEFAULT_1"):$      pass$$  t = T(1)$  assert (t.a == 1) and (t.b == "KW_DEFAULT_1")$$  t = T(1, b="kw_arg_1")$  assert (t.a == 1) and (t.b == "kw_arg_1")$$#######|   Private __init__ Variables (underscored)   |####################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a, b, _c):$      pass$$  t = T(1, 2, 3)$  assert hasattr(t, "a") and hasattr(t, "b") and not(hasattr(t, "_c"))
    

    注意:

    我包含了测试,但为了简洁起见,将它们折叠到最后一行 (58)。您可以通过find/replace-将所有$ 字符换行来扩展测试,详细说明所有潜在用例。

    【讨论】:

      【解决方案9】:

      可能不需要初始化变量,因为 locals() 已经包含值!

      类虚拟(对象):

      def __init__(self, a, b, default='Fred'):
          self.params = {k:v for k,v in locals().items() if k != 'self'}
      

      d = Dummy(2, 3)

      d.params

      {'a': 2, 'b': 3, 'default': 'Fred'}

      d.params['b']

      3

      当然,在一个类中可以使用 self.params

      【讨论】:

      • 这是一个不错的原创方法,但是d['b'] 是用 Python 的 lingua franca 编写的,而d.params['b'] 会引起代码阅读者的困惑。
      【解决方案10】:

      getargspec 自 Python 3.5 起已被弃用,以下是使用 inspect.signature 的解决方案:

      from inspect import signature, Parameter
      import functools
      
      
      def auto_assign(func):
          # Signature:
          sig = signature(func)
          for name, param in sig.parameters.items():
              if param.kind in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD):
                  raise RuntimeError('Unable to auto assign if *args or **kwargs in signature.')
          # Wrapper:
          @functools.wraps(func)
          def wrapper(self, *args, **kwargs):
              for i, (name, param) in enumerate(sig.parameters.items()):
                  # Skip 'self' param:
                  if i == 0: continue
                  # Search value in args, kwargs or defaults:
                  if i - 1 < len(args):
                      val = args[i - 1]
                  elif name in kwargs:
                      val = kwargs[name]
                  else:
                      val = param.default
                  setattr(self, name, val)
              func(self, *args, **kwargs)
          return wrapper
      

      检查是否有效:

      class Foo(object):
          @auto_assign
          def __init__(self, a, b, c=None, d=None, e=3):
              pass
      
      f = Foo(1, 2, d="a")
      assert f.a == 1
      assert f.b == 2
      assert f.c is None
      assert f.d == "a"
      assert f.e == 3
      
      print("Ok")
      

      【讨论】:

        【解决方案11】:

        对于 Python 3.3+:

        from functools import wraps
        from inspect import Parameter, signature
        
        
        def instance_variables(f):
            sig = signature(f)
            @wraps(f)
            def wrapper(self, *args, **kwargs):
                values = sig.bind(self, *args, **kwargs)
                for k, p in sig.parameters.items():
                    if k != 'self':
                        if k in values.arguments:
                            val = values.arguments[k]
                            if p.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY):
                                setattr(self, k, val)
                            elif p.kind == Parameter.VAR_KEYWORD:
                                for k, v in values.arguments[k].items():
                                    setattr(self, k, v) 
                        else:
                            setattr(self, k, p.default) 
            return wrapper
        
        class Point(object):
            @instance_variables 
            def __init__(self, x, y, z=1, *, m='meh', **kwargs):
                pass
        

        演示:

        >>> p = Point('foo', 'bar', r=100, u=200)
        >>> p.x, p.y, p.z, p.m, p.r, p.u
        ('foo', 'bar', 1, 'meh', 100, 200)
        

        使用框架的 Python 2 和 3 的非装饰器方法:

        import inspect
        
        
        def populate_self(self):
            frame = inspect.getouterframes(inspect.currentframe())[1][0]
            for k, v in frame.f_locals.items():
                if k != 'self':
                    setattr(self, k, v)
        
        
        class Point(object):
            def __init__(self, x, y):
                populate_self(self)
        

        演示:

        >>> p = Point('foo', 'bar')
        >>> p.x
        'foo'
        >>> p.y
        'bar'
        

        【讨论】:

          【解决方案12】:

          init 函数的末尾:

          for var in list(locals().keys()):
              setattr(self,var,locals()[var])
          

          有关setattr()的更多信息,请参阅here

          【讨论】:

            【解决方案13】:

            nu11ptr 制作了一个小模块PyInstanceVars,其中包含此功能作为函数装饰器。在模块的 README 中声明“[...] 性能现在仅比 CPython 下的显式初始化差 30-40%”。

            用法示例,直接取自模块的documentation

            >>> from instancevars import *
            >>> class TestMe(object):
            ...     @instancevars(omit=['arg2_'])
            ...     def __init__(self, _arg1, arg2_, arg3='test'):
            ...             self.arg2 = arg2_ + 1
            ...
            >>> testme = TestMe(1, 2)
            >>> testme._arg1
            1
            >>> testme.arg2_
            Traceback (most recent call last):
              File "<stdin>", line 1, in <module>
            AttributeError: 'TestMe' object has no attribute 'arg2_'
            >>> testme.arg2
            3
            >>> testme.arg3
            'test'
            

            【讨论】:

              【解决方案14】:

              fastcore 库 https://fastcore.fast.ai/utils.html#store_attr 中有一个辅助函数可以执行此操作。

              from fastcore.utils import store_attr
              
              class Process:
                  def __init__(self, PID, PPID, cmd, FDs, reachable, user):
                      store_attr() # this will do the same as self.PID = PID etc.
              

              【讨论】:

                【解决方案15】:

                也许这是一个封闭的问题,但我想提出我的解决方案,以便了解您的想法。我使用了一个元类,它将装饰器应用于 init 方法

                import inspect
                
                class AutoInit(type):
                    def __new__(meta, classname, supers, classdict):
                        classdict['__init__'] = wrapper(classdict['__init__'])
                        return type.__new__(meta, classname, supers, classdict)
                
                def wrapper(old_init):
                    def autoinit(*args):
                        formals = inspect.getfullargspec(old_init).args
                        for name, value in zip(formals[1:], args[1:]):
                            setattr(args[0], name, value)
                    return autoinit
                

                【讨论】:

                  【解决方案16】:

                  attrs 库做了类似的事情。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2017-04-05
                    • 2011-03-18
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2014-09-14
                    • 2013-07-04
                    • 1970-01-01
                    相关资源
                    最近更新 更多