代码之家  ›  专栏  ›  技术社区  ›  FMc TLP

在Python中进行自动属性赋值的最佳方法是什么?这是一个好主意吗?

  •  28
  • FMc TLP  · 技术社区  · 14 年前

    而不是每次定义类时都这样编写代码:

    class Foo(object): 
         def __init__(self, a, b, c, d, e, f, g):
            self.a = a
            self.b = b
            self.c = c
            self.d = d
            self.e = e
            self.f = f
            self.g = g
    

    我可以用 this recipe for automatic attribute assignment

    class Foo(object):
         @autoassign
         def __init__(self, a, b, c, d, e, f, g):
            pass
    

    两个问题

    1. 这种捷径有缺点或缺陷吗?
    9 回复  |  直到 14 年前
        1
  •  29
  •   unutbu    7 年前

    autoassign代码有一些地方让我感到困扰(主要是风格上的问题,但还有一个更严重的问题):

    1. autoassign 不分配 “args”属性:

      class Foo(object):
          @autoassign
          def __init__(self,a,b,c=False,*args):
              pass
      a=Foo('IBM','/tmp',True, 100, 101)
      print(a.args)
      # AttributeError: 'Foo' object has no attribute 'args'
      
    2. 像个装饰师。 但是 autoassign(*argnames) 返回装饰器的函数。 为了实现这个魔法, 自动分配 需要测试其第一个 争论。如果有选择,我 首选函数而不是测试

    3. 用于设置的代码量 sieve ,lambdas中的lambdas, 过滤器和很多条件。

      if kwargs:
          exclude, f = set(kwargs['exclude']), None
          sieve = lambda l:itertools.ifilter(lambda nv: nv[0] not in exclude, l)
      elif len(names) == 1 and inspect.isfunction(names[0]):
          f = names[0]
          sieve = lambda l:l
      else:
          names, f = set(names), None
          sieve = lambda l: itertools.ifilter(lambda nv: nv[0] in names, l)
      

      我想可能有更简单的方法。(见

    4. for _ in itertools.starmap(assigned.setdefault, defaults): pass . 我不认为 map starmap 副作用。可能是的

      for key,value in defaults.iteritems():
          assigned.setdefault(key,value)
      

    下面是另一个更简单的实现,它与autoassign具有相同的功能(例如,can do includes和excludes),并解决了上述问题:

    import inspect
    import functools
    
    def autoargs(*include, **kwargs):
        def _autoargs(func):
            attrs, varargs, varkw, defaults = inspect.getargspec(func)
    
            def sieve(attr):
                if kwargs and attr in kwargs['exclude']:
                    return False
                if not include or attr in include:
                    return True
                else:
                    return False
    
            @functools.wraps(func)
            def wrapper(self, *args, **kwargs):
                # handle default values
                if defaults:
                    for attr, val in zip(reversed(attrs), reversed(defaults)):
                        if sieve(attr):
                            setattr(self, attr, val)
                # handle positional arguments
                positional_attrs = attrs[1:]
                for attr, val in zip(positional_attrs, args):
                    if sieve(attr):
                        setattr(self, attr, val)
                # handle varargs
                if varargs:
                    remaining_args = args[len(positional_attrs):]
                    if sieve(varargs):
                        setattr(self, varargs, remaining_args)
                # handle varkw
                if kwargs:
                    for attr, val in kwargs.items():
                        if sieve(attr):
                            setattr(self, attr, val)
                return func(self, *args, **kwargs)
            return wrapper
        return _autoargs
    

    import sys
    import unittest
    import utils_method as um
    
    class Test(unittest.TestCase):
        def test_autoargs(self):
            class A(object):
                @um.autoargs()
                def __init__(self,foo,path,debug=False):
                    pass
            a=A('rhubarb','pie',debug=True)
            self.assertTrue(a.foo=='rhubarb')
            self.assertTrue(a.path=='pie')
            self.assertTrue(a.debug==True)
    
            class B(object):
                @um.autoargs()
                def __init__(self,foo,path,debug=False,*args):
                    pass
            a=B('rhubarb','pie',True, 100, 101)
            self.assertTrue(a.foo=='rhubarb')
            self.assertTrue(a.path=='pie')
            self.assertTrue(a.debug==True)
            self.assertTrue(a.args==(100,101))        
    
            class C(object):
                @um.autoargs()
                def __init__(self,foo,path,debug=False,*args,**kw):
                    pass
            a=C('rhubarb','pie',True, 100, 101,verbose=True)
            self.assertTrue(a.foo=='rhubarb')
            self.assertTrue(a.path=='pie')
            self.assertTrue(a.debug==True)
            self.assertTrue(a.verbose==True)        
            self.assertTrue(a.args==(100,101))        
    
        def test_autoargs_names(self):
            class C(object):
                @um.autoargs('bar','baz','verbose')
                def __init__(self,foo,bar,baz,verbose=False):
                    pass
            a=C('rhubarb','pie',1)
            self.assertTrue(a.bar=='pie')
            self.assertTrue(a.baz==1)
            self.assertTrue(a.verbose==False)
            self.assertRaises(AttributeError,getattr,a,'foo')
    
        def test_autoargs_exclude(self):
            class C(object):
                @um.autoargs(exclude=('bar','baz','verbose'))
                def __init__(self,foo,bar,baz,verbose=False):
                    pass
            a=C('rhubarb','pie',1)
            self.assertTrue(a.foo=='rhubarb')
            self.assertRaises(AttributeError,getattr,a,'bar')
    
        def test_defaults_none(self):
            class A(object):
                @um.autoargs()
                def __init__(self,foo,path,debug):
                    pass
            a=A('rhubarb','pie',debug=True)
            self.assertTrue(a.foo=='rhubarb')
            self.assertTrue(a.path=='pie')
            self.assertTrue(a.debug==True)
    
    
    if __name__ == '__main__':
        unittest.main(argv = sys.argv + ['--verbose'])
    

    PS.使用 自动分配 autoargs 与IPython代码完成兼容。

        2
  •  16
  •   Jundiaius    7 年前

    Python 3.7版+ 你可以使用 Data Class

    它允许你定义 领域 对于类,这些属性是自动指定的。

    它看起来是这样的:

    @dataclass
    class Foo:
        a: str
        b: int
        c: str
        ...
    

    这个 __init__ 方法将在类中自动创建,并将实例创建的参数分配给这些属性(并验证参数)。

    注意这里 类型提示是必需的 int str 在这个例子中。如果不知道字段的类型,可以使用 Any from the typing module .

        3
  •  9
  •   Manoj Govindan    14 年前

    class Foo(object):
        def __init__(self, **kwargs):
            self.__dict__.update(kwargs)
    
    
    >>> foo = Foo(a = 1, b = 'bar', c = [1, 2])
    >>> foo.a
    1
    >>> foo.b
    'bar'
    >>> foo.c
    [1, 2]
    >>> 
    

    由Peter Norvig提供 Python: Infrequently Answered Questions .

        4
  •  7
  •   Max    14 年前

    __init__.py 发现对象的属性。如果您希望IDE中的自动代码完成更具功能性,那么最好用老式的方式来拼写。

        5
  •  2
  •   leoluk    14 年前

    如果有很多变量,可以传递一个配置dict或object。

        6
  •  2
  •   onno    10 年前

    与上述类似,但不尽相同。。。以下是非常简短的,涉及 args kwargs

    def autoassign(lcls):
        for key in lcls.keys():
            if key!="self":
                lcls["self"].__dict__[key]=lcls[key]
    

    class Test(object):
        def __init__(self, a, b, *args, **kwargs):
            autoassign(locals())
    
        7
  •  2
  •   Apalala    8 年前

    这是一个简单的实现 judy2k :

    from inspect import signature
    
    def auto_args(f):
        sig = signature(f)  # Get a signature object for the target:
        def replacement(self, *args, **kwargs):
            # Parse the provided arguments using the target's signature:
            bound_args = sig.bind(self, *args, **kwargs)
            # Save away the arguments on `self`:
            for k, v in bound_args.arguments.items():
                if k != 'self':
                    setattr(self, k, v)
            # Call the actual constructor for anything else:
            f(self, *args, **kwargs)
        return replacement
    
    
    class MyClass:
        @auto_args
        def __init__(self, a, b, c=None):
            pass
    
    m = MyClass('A', 'B', 'C')
    print(m.__dict__)
    # {'a': 'A', 'b': 'B', 'c': 'C'}
    
        8
  •  2
  •   smarie    7 年前

    this package

        9
  •  0
  •   Rafael Sierra    14 年前
    class MyClass(object):
        def __init__(self, **kwargs):
            for key, value in kwargs.iteritems():
                setattr(self, key, value)
    

        10
  •  0
  •   SEDaradji    5 年前

    在很多情况下,我确实需要这个功能,所以我决定实现我的,我的解决方案可能不是最适合每种情况,但它有一些很酷的特性,使它可以无缝地执行继承和默认参数,用法非常简单:

    1. 从EasyObj继承。
    2. 以super()的形式从\uuu init \uuuu调用super

    例子:

    class A(EasyObj):
       EasyObj_KWARGS  = OrderedDict((
                ('name'     , {'default': 'Sal' , 'adapter': lambda x: 'My name is '+x  }),
                ('age'      , {'default : 20    }                                        ),
                ('degree'   , {}                                                         ),
                ('degree'   , {'adapter': lambda x: x.strip()}                           )))
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
    

    from    collections     import  OrderedDict
    from    enum            import  Enum
    from    inspect         import  getmro
    
    class   InfoExceptionType(Enum):
        PROVIDED_TWICE  = 1
        MISSING         = 2
        EXTRA           = 3
    
    class   ExceptionKwargs(Exception):
        '''
            Raised on kwargs setting errors by EasyObj.
            Args    :
                kwargs  (list)                  : List of kwargs.
                error   (InfoExceptionType)     : Error to print to the user
        '''
    
        def __init__(
            self        , 
            kwargs      ,
            error       ,
            all_kwargs  ):
            self.kwargs     = kwargs
            self.error      = error
            self.all_kwargs = '\nPossible kwargs:\n\t'+ '\n\t'.join(
                [ '{}{}'.format(x, (': '+ str(all_kwargs[x]['default']) if 'default' in all_kwargs[x] else ''))
                    for x in all_kwargs])
    
        def __str__(self):
            return 'The following kwargs/args were {}: {}'.format(
                self.error.name.lower().replace('_',' ')    ,
                ', '.join(self.kwargs)                      )+ self.all_kwargs
    
        def __repr__(self):
            return str(self)
    
    class   EasyObj():
        '''
            Allows automatic attribute setting from within __init__.
            All derived classes must call super with the provided kwargs 
                when implementing __init__ :
                super().__init__(**kwargs)
            EasyObj_KWARGS dict must be overridden.
            If args are supplied to init, they will be assigned automatically 
                using the order specified in EasyObj_KWARGS.
            Kwarg dict keys are the name of the kwargs, values are dict 
                containing a default value and an adapter, both are optional.
            If no value was given to a kwarg, default value is used, if no default value
                is found, ExceptionKwargs is raised.
            Adapters are applied to parameters even to default values.
    
            Support for kwargs inheritance:
                If a class B is derived from A and both A and B are EasyObj then 
                    B.EasyObj_KWARGS will be A.EasyObj_KWARGS + B.EasyObj_KWARGS
                In this case, the EasyObj_KWARGS order will be dependent on the order of 
                types returned by inspect.getmro in reverse.
    
            Examples:
                >>> class A(EasyObj):
                        EasyObj_KWARGS  = OrderedDict((
                            ('name'     , {'default': 'Sal' , 'adapter': lambda x: 'My name is '+x  }),
                            ('age'      , {'default': 20    }                                        ),
                            ('degree'   , {}                                                         ),
                            ('degree'   , {'adapter': lambda x: x.strip()}                           )))
                        def __init__(self, *args, **kwargs):
                            super().__init__(*args, **kwargs)
    
                >>> #Class be doesn't have to implement __init__ since A already does that
                >>> class B(A):
                        EasyObj_KWARGS  = OrderedDict((
                            ('male' , {'default': True  }   ),))
    
                >>> A(degree= ' bachelor ').__dict__
                >>> {'degree': 'bachelor', 'name': 'My name is Sal', 'age': 20}
                >>> B(degree= ' bachelor ').__dict__
                >>> {'degree': 'bachelor', 'name': 'My name is Sal', 'age': 20, 'male': True}
        '''
        #Contains kwargs and validators for creating the object, must be overridden
        #Must be an ordered dict.
        EasyObj_KWARGS  = OrderedDict()
    
        def __init__(self, *args, **kwargs):
            all_kwargs = OrderedDict()
            for _type in reversed(getmro(type(self))):
                if hasattr(_type, 'EasyObj_KWARGS'):
                    all_kwargs.update(_type.EasyObj_KWARGS)
    
            if len(args) > len(all_kwargs):
                extra_args = ['Arg at postition '+ str(i+1) for i in range(len(all_kwargs), len(args))]
                raise ExceptionKwargs(extra_args, InfoExceptionType.EXTRA, all_kwargs)
    
            args_kwargs     = {
                list(all_kwargs.keys())[i] : args[i] for i in range(len(args))}
            twice_kwargs    = [kwarg for kwarg in kwargs if kwarg in args_kwargs]
    
            if twice_kwargs:
                raise ExceptionKwargs(twice_kwargs, InfoExceptionType.PROVIDED_TWICE, all_kwargs)
    
            kwargs.update(args_kwargs)
            default_kwargs = {
                x:all_kwargs[x]['default'] for x in all_kwargs \
                    if 'default' in all_kwargs[x] and x not in kwargs}
            kwargs.update(default_kwargs)
    
            extra_kwargs    = [k for k in kwargs if k not in all_kwargs] 
            if extra_kwargs     :
                raise ExceptionKwargs(extra_kwargs, InfoExceptionType.EXTRA, all_kwargs)
    
            missing_kwargs  = [k for k in all_kwargs if k not in kwargs] 
            if missing_kwargs   :
                raise ExceptionKwargs(missing_kwargs, InfoExceptionType.MISSING, all_kwargs)
    
            for k in kwargs :
                if 'adapter' in all_kwargs[k]:
                    setattr(self, k, all_kwargs[k]['adapter'](kwargs[k]))
                else :
                    setattr(self, k, kwargs[k])