Подтвердить что ты не робот

Каков наилучший способ сделать автоматическое назначение атрибутов в Python, и это хорошая идея?

Вместо написания такого кода каждый раз, когда я определяю класс:

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

Два вопроса:

  • Есть ли недостатки или ошибки, связанные с этим ярлыком?
  • Есть ли лучший способ добиться аналогичного удобства?
4b9b3361

Ответ 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'
    
  • autoassign действует как декоратор. Но autoassign(*argnames) вызывает функция, которая возвращает декоратор. Чтобы достичь этой магии, autoassign необходимо проверить тип первого аргумент. Если будет дан выбор, я предпочитают функции не тестировать тип его аргументов.

  • Похоже, что существует значительная количество кода, предназначенного для настройки sieve, лямбда внутри лямбда, 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)
    

    Я думаю, что может быть более простой способ. (Видеть ниже).

  • for _ in itertools.starmap(assigned.setdefault, defaults): pass. Я не думаю map или starmap предназначалось для вызова функций, единственной целью которых является их побочные эффекты. Это могло бы быть написанное более четко с мирским:

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

Вот альтернативная более простая реализация, которая имеет ту же функциональность, что и функция autoassign (например, может включать и отключает), и которая адресована указанным выше пунктам:

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

И вот unit test я использовал для проверки его поведения:

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. Использование autoassign или autoargs совместимо с завершением кода IPython.

Ответ 2

Есть ли лучший способ добиться аналогичного удобства?

Я не знаю, будет ли это лучше, но вы можете это сделать:

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: вопросы, не связанные с ответом.

Ответ 3

Один недостаток: многие IDE анализируют __init__.py, чтобы обнаружить атрибуты объекта. Если вы хотите, чтобы автоматическое завершение кода в вашей среде IDE было более функциональным, вам может быть лучше написано это старомодно.

Ответ 4

Если у вас много переменных, вы можете передать один единственный файл конфигурации или объект.

Ответ 5

Как и выше, хотя и не одно и то же... следующее очень короткое, имеет дело с 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())

Ответ 6

Это простая реализация 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 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'}

Ответ 7

В этот пакет вы можете найти

  • @autoargs вдохновлен answer-3653049
  • @autoprops, чтобы преобразовать поля, сгенерированные с помощью @autoargs, в @property, для использования в сочетании с библиотекой валидации, такой как enforce или pycontracts.

Обратите внимание, что это подтверждено для python 3.5 +

Ответ 8

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

Вы просто не можете использовать * args, но можете хранить в некотором списке экземпляров (например, self.args, не знаете)