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

FMc*_*FMc 31 python attributes decorator

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

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
Run Code Online (Sandbox Code Playgroud)

我可以使用此配方进行自动属性分配.

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

两个问题:

  1. 这条捷径有缺点或陷阱吗?
  2. 有没有更好的方法来实现类似的便利?

unu*_*tbu 26

关于自动分配代码的一些事情让我感到困扰(主要是风格,但更严重的问题):

  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'
    
    Run Code Online (Sandbox Code Playgroud)
  2. autoassign像装饰家一样.但是autoassign(*argnames)调用一个返回装饰器的函数.要实现这个魔力,autoassign 需要测试其第一个参数的类型.如果给出了选择,我更喜欢函数不测试其参数的类型.

  3. 似乎有相当多的代码专门用于设置 sievelambda,lambdas,ifilters和许多条件.

    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)
    
    Run Code Online (Sandbox Code Playgroud)

    我认为可能有一种更简单的方法.(见下文).

  4. for _ in itertools.starmap(assigned.setdefault, defaults): pass.我不认为 mapstarmap意图调用函数,其唯一目的是它们的副作用.它可以用世俗的写得更清楚:

    for key,value in defaults.iteritems():
        assigned.setdefault(key,value)
    
    Run Code Online (Sandbox Code Playgroud)

这是一个替代的更简单的实现,它具有与自动分配相同的功能(例如,可以包含和排除),并解决上述问题:

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
Run Code Online (Sandbox Code Playgroud)

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

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'])
Run Code Online (Sandbox Code Playgroud)

PS.使用autoassignautoargs兼容IPython代码完成.


Jun*_*ius 10

Python 3.7+开始,您可以使用数据类,它可以实现您想要的更多功能.

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

看起来像这样:

@dataclass
class Foo:
    a: str
    b: int
    c: str
    ...
Run Code Online (Sandbox Code Playgroud)

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

注意,这里类型提示是必需的,这就是为什么我已经使用intstr在本例中.如果您不知道字段的类型,可以使用模块中的Anytyping.


Man*_*dan 7

有没有更好的方法来实现类似的便利?

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

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]
>>> 
Run Code Online (Sandbox Code Playgroud)

礼貌Peter Norvig的Python:不经常回答的问题.

  • 这是该问题的快速解决方案,但它有两个缺点:1)如果要确保将某些参数传递给函数,则必须添加更多代码; 2)函数的签名完全不透明,不能用于理解如何调用函数; 一个人需要依赖docstring (6认同)

Max*_*Max 7

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