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

2022-01-13 00:00:00 python decorator attributes

问题描述

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

Instead of writing code like this every time I define a class:

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

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

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

两个问题:

  1. 是否存在与此快捷方式相关的缺点或陷阱?
  2. 有没有更好的方法来实现类似的便利?


解决方案

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

There are some things about the autoassign code that bug me (mostly stylistic, but one more serious problem):

  1. autoassign 不分配'args' 属性:

  1. autoassign does not assign an 'args' attribute:

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需要先测试它的类型争论.如果可以选择,我喜欢功能而不是测试其参数的类型.

  • autoassign acts like a decorator. But autoassign(*argnames) calls a function which returns a decorator. To achieve this magic, autoassign needs to test the type of its first argument. If given a choice, I prefer functions not test the type of its arguments.

    好像有很多用于设置的代码量筛子,lambdas中的lambdas,ifilters,还有很多条件.

    There seems to be a considerable amount of code devoted to setting up sieve, lambdas within lambdas, ifilters, and lots of conditions.

    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)
    

    我认为可能有更简单的方法.(看下面).

    I think there might be a simpler way. (See below).

    for _ initertools.starmap(assigned.setdefault,默认值):通过.我不认为mapstarmap 旨在调用函数,其唯一目的是它们的副作用.本来可以用平凡写得更清楚:

    for _ in itertools.starmap(assigned.setdefault, defaults): pass. I don't think map or starmap was meant to call functions, whose only purpose is their side effects. It could have been written more clearly with the mundane:

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

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

    Here is an alternative simpler implementation which has the same functionality as autoassign (e.g. can do includes and excludes), and which addresses the above points:

    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
    

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

    And here is the unit test I used to check its behavior:

    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 代码完成兼容.

    PS. Using autoassign or autoargs is compatible with IPython code completion.

    相关文章