Python __init__ setattr для аргументов?

Кажется, что часто __init__ методы похожи на это:

def __init__(self, ivar1, ivar2, ivar3):
    self.ivar1 = ivar1
    self.ivar2 = ivar2
    self.ivar3 = ivar3

Есть ли способ превратить аргументы в список (не прибегая к *args или **kwargs), а затем использовать setattr для установки переменных экземпляра с именем параметра и переданным аргументом? И, возможно, нарезать список, например. вам нужно по крайней мере нарезать его до [1:], потому что вы не хотите self.self.

(на самом деле я думаю, что это должен быть словарь для хранения имени и значения)

нравится:

def __init__(self, ivar1, ivar2, ivar3, optional=False):
    for k, v in makedict(self.__class__.__init__.__args__): # made up __args__
        setattr(self, k, v)

Спасибо!

Отвечая на ответ Неизвестного, я обнаружил, что это работает:

Class A(object):
    def __init__(self, length, width, x):
        self.__dict__.update(dict([(k, v) for k, v in locals().iteritems() if k != 'self']))

or

Class A(object):
    def __init__(self, length, width, x):
        self.__dict__.update(locals())
        del self.__dict__['self']

Не так уж плохо..


person mk12    schedule 14.09.2009    source источник
comment
dupe stackoverflow.com/questions/1389180/   -  person SilentGhost    schedule 14.09.2009
comment
см. также: stackoverflow.com/questions/739625 /   -  person Imran    schedule 14.09.2009


Ответы (9)


Ну вот. Да, это уродливый злой хак. Да, объекту нужна переменная __dict__. Но эй, это аккуратный маленький вкладыш!

def __init__(self):
    self.__dict__.update(locals())

Конструктор может принимать аргументы любого типа.

class test(object):
    def __init__(self, a, b, foo, bar=5)...

a = test(1,2,3)
dir(a)

['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'a', 'b', 'foo', 'bar', 'self']

Он также будет включать себя, но вы можете легко удалить его или создать свою собственную функцию обновления, которая игнорирует себя.

person Unknown    schedule 14.09.2009
comment
Это не делает то, что хотел ОП: он вообще не требует аргументов! - person Ned Batchelder; 14.09.2009
comment
@ Нед, это так ... Вы можете указать любой тип аргумента, который хотите. - person Unknown; 14.09.2009
comment
Это установит все локальные переменные в качестве атрибутов, особенно self, что довольно абсурдно. :) Вы, конечно, можете сначала отфильтровать self из locals(), но тогда это уже становится менее аккуратно. - person Lennart Regebro; 14.09.2009
comment
@ Леннарт, это предполагаемое поведение. За исключением себя, который легко удаляется, о чем свидетельствует ваша версия этого ответа. - person Unknown; 14.09.2009
comment
А, я вижу: ваш первый пример просто говорит, __init__(self). - person Ned Batchelder; 14.09.2009

Вы можете использовать inspect.getargspec и инкапсулировать его как декоратор. Поиск необязательных и ключевых аргументов немного сложен, но это должно сделать это:

def inits_args(func):
    """Initializes object attributes by the initializer signature"""
    argspec = inspect.getargspec(func)
    argnames = argspec.args[1:]
    defaults = dict(zip(argnames[-len(argspec.defaults):], argspec.defaults))
    @functools.wraps(func)
    def __init__(self, *args, **kwargs):
        args_it = iter(args)
        for key in argnames:
            if key in kwargs:
                value = kwargs[key]
            else:
                try:
                    value = args_it.next()
                except StopIteration:
                    value = defaults[key]
            setattr(self, key, value)
        func(self, *args, **kwargs)
    return __init__

Затем вы можете использовать его следующим образом:

class Foo(object):
    @inits_args
    def __init__(self, spam, eggs=4, ham=5):
        print "Foo(%r, %r, %r)" % (self.spam, self.eggs, self.ham)
person Ants Aasma    schedule 14.09.2009

Нет хорошего способа получить аргументы в виде списка, если они указаны индивидуально в сигнатуре функции. Вы, вероятно, можете что-то сделать с хаками inspect или frame, но это будет более уродливо, чем простое изложение того, что вы сделали.

person Ned Batchelder    schedule 14.09.2009
comment
Это невозможно сделать, даже взглянув на функцию или объекты кода. Хотя вы можете сделать что-то неприятное, например предположить, что первые co_argcount из co_varnames являются аргументами, это не будет правдой перед лицом распаковки последовательности или *args/**kwargs. - person bobince; 14.09.2009
comment
Нед, мой метод работает для любой сигнатуры метода, и мне не нужно прибегать к проверкам или фрейм-хакам. - person Unknown; 14.09.2009

Попробуйте inspect.getargspec:

In [31]: inspect.getargspec(C.__init__)

Out[31]: ArgSpec(args=['self', 'ivar1', 'ivar2', 'ivar3', 'optional'],

                 varargs=None, keywords=None, defaults=(False,))
person Cristian Ciupitu    schedule 14.09.2009

Посмотрите, подойдет ли вам новый namedtuple (новый в Python 2.6) из модуля collections.

person PaulMcG    schedule 14.09.2009

Вы можете сделать это, используя самоанализ аргументов, но код будет длиннее, чем код, который вы пытаетесь заменить. Особенно, если вы работаете с кВт, что вам, возможно, придется сделать.

Этот короткий код работает в большинстве случаев (улучшен из примера Unknowns):

>>> class Foo:
...   def __init__(self, labamba, **kw):
...       params = locals().copy()
...       del params['self']
...       if 'kw' in params:
...           params.update(params['kw'])
...           del params['kw']
...       self.__dict__.update(params)

Но это уродливый хак, делающий код менее читабельным ни по какой причине, кроме лени, так что не делайте этого. А также, как часто у вас на самом деле есть классы с более чем 5-6 параметрами инициализации?

person Lennart Regebro    schedule 14.09.2009

Мне больше всего нравится эта форма, не слишком длинная, и ее можно копировать и вставлять, а также подклассы:

class DynamicInitClass(object):
      __init_defargs=('x',)
      def __init__(self,*args,**attrs):
        for idx,val in enumerate(args): attrs[self.__init_defargs[idx]]=val
        for key,val in attrs.iteritems(): setattr(self,key,val)
person Community    schedule 13.11.2009

Как насчет происхождения из особого класса? Я думаю, что это будет более явным и гибким следующим образом:

class InitMe:
    def __init__(self, data):
        if 'self' in data:
             data = data.copy()
             del data['self']
        self.__dict__.update(data)


class MyClassA(InitMe):
    def __init__(self, ivar1, ivar2, ivar3 = 'default value'):
        super().__init__(locals())


class MyClassB(InitMe):
    def __init__(self, foo):
        super().__init__({'xxx': foo, 'yyy': foo, 'zzz': None})
# or    super().__init__(dict(xxx=foo, yyy=foo, zzz=None))

class MyClassC(InitMe):
    def __init__(self, foo, **keywords):
        super().__init__(keywords)
person ilya n.    schedule 14.09.2009

Если вы хотите динамически объявлять переменные класса из заданного параметра, есть короткий и понятный способ сделать это при инициализации класса:

class Foo():

    def __init__(self, **content):
        if isinstance(content, dict):
            self.hello = 'world' # normal declaration instance variable
            for key, value in content.items():
                self.__setattr__(key, value)

    test = "test" # class variable


>>> content = {'foo':'bar'}
>>> instance = Foo(**content)
>>> instance.foo 
>>> 'bar'
person Björn    schedule 15.02.2020