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']))
или же
Class A(object):
def __init__(self, length, width, x):
self.__dict__.update(locals())
del self.__dict__['self']
Не так уж плохо..
8 ответов
Ну вот. Да, это безобразный злой взлом. Да, объекту нужна переменная __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']
Это также будет включать в себя self, но вы можете легко удалить это или сделать свою собственную функцию обновления, которая игнорирует self.
Вы можете использовать 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)
Нет хорошего способа получить аргументы в виде списка, если они указаны отдельно в сигнатуре функции. Вы, вероятно, можете что-то сделать с проверкой или взломом фреймов, но это будет уродливее, чем просто изложить, как вы это сделали.
Попробуйте inspect.getargspec:
In [31]: inspect.getargspec(C.__init__)
Out[31]: ArgSpec(args=['self', 'ivar1', 'ivar2', 'ivar3', 'optional'],
varargs=None, keywords=None, defaults=(False,))
Посмотрите, подойдет ли вам новый namedtuple (новый в Python 2.6) из модуля коллекций.
Вы можете сделать это, используя самоанализ аргументов, но код будет длиннее, чем код, который вы пытаетесь заменить. Особенно, если вы работаете с KW, что вам, возможно, придется сделать.
Этот короткий код работает в большинстве случаев (улучшено из примера 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 параметров инициализации?
Если вы хотите объявлять переменные класса динамически из заданного параметра, есть простой и простой способ сделать это при инициализации класса:
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'
Мне больше всего нравится эта форма, не слишком длинная, с возможностью копирования и подкласса:
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)
Как насчет получения из специального класса? Я думаю, что это более явно и более гибко:
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)