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

What is the best way to do automatic attribute assignment in Python, and is it a good idea?

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

1
2
3
4
5
6
7
8
9
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

我可以使用这个方法来自动分配属性。

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

两个问题:

  • 这个快捷方式是否存在缺陷或陷阱?
  • 有没有更好的方法来实现类似的便利?

  • 关于autoassign代码,有一些事情让我很烦(主要是风格上的,但还有一个更严重的问题):

  • autoassign不分配"args"属性:

    1
    2
    3
    4
    5
    6
    7
    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'

  • autoassign起着装饰的作用。但autoassign(*argnames)称返回修饰符的函数。为了达到这个魔法,autoassign。需要先测试它的类型参数。如果有选择,我首选功能而不是测试其参数的类型。

  • 似乎有一个相当大的用于设置的代码量以东十一〔四〕羊羔中的羊羔,i过滤器和许多条件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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)

    我想可能有一个更简单的方法。(见以下)。

  • for _ in
    itertools.starmap(assigned.setdefault,
    defaults): pass
    。我不认为mapstarmap是用来呼叫功能,其唯一目的是副作用。可能是用俗语写得更清楚:

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

  • 这里是一个替代的更简单的实现,它具有与autoassign相同的功能(例如,can do include和excludes),并解决了上述问题:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    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

    下面是我用来检查其行为的单元测试:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    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.使用autoassignautoargs与ipython代码完成兼容。


    一个缺点是:许多IDE解析__init__.py以发现对象的属性。如果您希望IDE中的自动代码完成功能更强大,那么最好还是用老式的方式来拼写。


    Is there a better way to achieve similar convenience?

    我不知道这是否一定更好,但你可以这样做:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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]
    >>>

    彼得·诺维格的Python:不常回答的问题。


    从Python3.7+中,您可以使用一个数据类,它可以实现您想要的以及更多的功能。

    它允许您为类定义字段,这些字段是自动分配的属性。

    看起来是这样的:

    1
    2
    3
    4
    5
    6
    @dataclass
    class Foo:
        a: str
        b: int
        c: str
        ...

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

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


    在这个包裹里你现在可以找到

    • @autoargs灵感来源于答案3653049
    • @autoprops@autoargs生成的字段转换为@property字段,用于与验证库(如强制执行或PyContracts)结合使用。

    请注意,这已经针对Python3.5进行了验证。+


    这是Judy2K的一个简单实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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'}


    类似于上述,尽管不同…以下内容非常简短,涉及argskwargs

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

    使用方法如下:

    1
    2
    3
    class Test(object):
        def __init__(self, a, b, *args, **kwargs):
            autoassign(locals())


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


    1
    2
    3
    4
    class MyClass(object):
        def __init__(self, **kwargs):
            for key, value in kwargs.iteritems():
                setattr(self, key, value)

    您不能使用*参数,但可以存储在一些实例列表中(如self.args,不知道)。