Как эффективно передавать аргументы (**kwargs в python)
У меня есть класс, который наследует от 2 других классов. Это базовые классы:
class FirstBase(object):
def __init__(self, detail_text=desc, backed_object=backed_object,
window=window, droppable_zone_obj=droppable_zone_obj,
bound_zone_obj=bound_zone_object,
on_drag_opacity=on_drag_opacity):
# bla bla bla
class SecondBase(object):
def __init__(self, size, texture, desc, backed_object, window):
# bla bla bla
А это ребенок
class Child(FirstBase, SecondBase):
""" this contructor doesnt work
def __init__(self, **kwargs):
# PROBLEM HERE
#super(Child, self).__init__(**kwargs)
"""
#have to do it this TERRIBLE WAY
def __init__(self, size=(0,0), texture=None, desc="", backed_object=None,
window=None, droppable_zone_obj=[], bound_zone_object=[],
on_drag_opacity=1.0):
FirstBase.__init__(self, detail_text=desc, backed_object=backed_object,
window=window, droppable_zone_obj=droppable_zone_obj,
bound_zone_obj=bound_zone_object,
on_drag_opacity=on_drag_opacity)
SecondBase.__init__(self, size, texture, desc, backed_object, window)
Я хотел решить все это с **kwargs
но когда я вызываю первый закомментированный конструктор, я получаю TypeError: __init__() got an unexpected keyword argument 'size'
,
Любые идеи, как я могу заставить его работать с **kwargs?
7 ответов
Ваша проблема в том, что вы пытались использовать только super
в детском классе.
Если вы используете super
в базовых классах тоже, тогда это будет работать. Каждый конструктор "съест" аргументы ключевого слова, которые он принимает, и не передаст их следующему конструктору. Когда конструктор для object
вызывается, если есть какие-либо ключевые аргументы, оставленные над ним, вызовет исключение.
class FirstBase(object):
def __init__(self, detail_text=None, backed_object=None,
window=None, droppable_zone_obj=None,
bound_zone_obj=None, on_drag_opacity=None, **kwargs):
super(FirstBase, self).__init__(**kwargs)
class SecondBase(object):
def __init__(self, size=(0,0), texture=None, desc="",
backed_object=None, window=None, **kwargs):
super(SecondBase, self).__init__(**kwargs)
class Child(FirstBase, SecondBase):
def __init__(self, **kwargs):
super(Child, self).__init__(**kwargs)
Как видите, это работает, кроме случаев, когда вы передаете фиктивный аргумент:
>>> Child()
<__main__.Child object at 0x7f4aef413bd0>
>>> Child(detail_text="abc")
<__main__.Child object at 0x7f4aef413cd0>
>>> Child(bogus_kw=123)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "test.py", line 14, in __init__
super(Child, self).__init__(**kwargs)
File "test.py", line 5, in __init__
super(FirstBase, self).__init__(**kwargs)
File "test.py", line 10, in __init__
super(SecondBase, self).__init__(**kwargs)
TypeError: object.__init__() takes no parameters
Мне не нравится это решение, но вы можете сделать это:
class FirstBase(object):
def __init__(self, *args, **kargs):
kargs.get('karg name', 'default value')
# bla bla bla
class SecondBase(object):
def __init__(self, *args, **kargs):
kargs.get('karg name', 'default value')
# bla bla bla
class Child(FirstBase, SecondBase):
def __init__(self, *args, **kargs):
FirstBase.__init__(self, *args, **kargs)
SecondBase.__init__(self, *args, **kargs)
Так как FirstBase.__init__
не имеет size
аргумент. Вы можете добавить size
аргумент или добавить **kwargs
в FirstBase.__init__
,
Попробуйте что-то вроде этого.
class FirstBase(object):
def __init__(self, detail_text, backed_object, window, droppable_zone_obj, bound_zone_obj, on_drag_opacity):
# bla bla bla
class SecondBase(object):
def __init__(self, texture, desc, backed_object, window, size=None):
# bla bla bla
Возможный взлом (но взлом это плохо, помните), это обратиться к модулю проверки, чтобы получить подпись функции, которую вы собираетесь использовать:
import inspect
#define a test function with two parameters function
def foo(a,b):
return a+b
#obtain the list of the named arguments
acceptable = inspect.getargspec(f).args
print acceptable
#['a', 'b']
#test dictionary of kwargs
kwargs=dict(a=3,b=4,c=5)
#keep only the arguments that are both in the signature and in the dictionary
new_kwargs = {k:kwargs[k] for k in acceptable if k in kwargs}
#launch with the sanitized dictionary
f(**new_kwargs)
Это не код, который вы должны использовать в производственном коде. Если функция отклоняет некоторые аргументы, это означает, что вы можете получить подпись, читая код и передавая только необходимые ключи. Взлом с inspect гораздо менее стабилен и читабелен, поэтому используйте его только тогда, когда у вас нет другого пути!
Поскольку у вас есть контроль над кодом, простой способ позволить себе просто обойти ** kwargs так, как вам нужно, - это добавить параметр ** kwargs в __init__ обоих базовых классов следующим образом:
class FirstBase(object):
def __init__(self, detail_text=desc, backed_object=backed_object,
window=window, droppable_zone_obj=droppable_zone_obj,
bound_zone_obj=bound_zone_object,
on_drag_opacity=on_drag_opacity, **kwargs):
# bla bla bla
class SecondBase(object):
def __init__(self, size, texture, desc, backed_object, window, **kwargs):
# bla bla bla
Ошибка, с которой вы столкнулись, потому что ваши базовые классы имели только именованные параметры, поэтому, когда вы передаете неизвестное имя, вышибала говорит, что вас нет в моем списке. Добавляя параметры ** kwargs к функциям __init__ базовых классов, они внезапно позволяют просто передавать любое произвольное именованное значение, что понятно, поскольку, в конце концов, ** kwargs - это открытый конец для всех, ничего из того, что по имени, так как бы он знал, что исключить? Он просто заканчивает словарь именованных значений в глазах вызываемого кода, который использует их или нет. Вы можете полностью игнорировать ** kwargs.
Ответ, который на момент написания в настоящее время Дитрих принял как правильный, запутал существенную проблему - он вообще не имеет ничего общего с super(), и его исправление работает просто потому, что были добавлены ** kwargs и что открыл дверь для любого произвольного имени.
Если у вас нет контроля над кодом, вы можете:
- передать аргументы явно, чтобы не распаковывать слишком длинные аргументы или ** kwargs с неопределенными именами,
- извлекать посторонние параметры из kwargs, используя функцию словаря pop,
или вы можете использовать эту вспомогательную мета-функцию:
import inspect def anykwarg(function, *args, **kwargs): validargs = inspect.getargspec(function).args removals = [key for key in kwargs.keys() if key not in validargs] for removal in removals: del kwargs[removal] return function(*args, **kwargs)
Этот помощник anykwarg может быть улучшен для проверки параметра kwargs и, если он присутствует, не удаляет никакие значения из переданных kwargs, а inspect.getargspec, использованный выше, возвращает эту информацию. Заметьте, что это нормально для удаления из kwargs в anykwarg, потому что это не ссылка на некоторый переданный словарь, это локальный словарь, сгенерированный из параметров.
Вам не нужно звонить super()
в базовых классах, как в принятом ответе @Dietrich Epp. Все, что вы делаете, передаете любые оставшиеся аргументы в kwargs
вверх к object
базовый класс, где их выбрасывают; это небрежно
Когда вы делаете super(ChildClass, self).__init__(foo, bar, **kwargs)
суперкласс (ы) ChildClass
должны ожидать все аргументы foo
, bar
, а также **kwargs
, Так что в вашем случае, получая ошибку TypeError: __init__() got an unexpected keyword argument 'size'
подразумевает ваши родительские классы Child
либо не имеют size
или нет **kwargs
в их __init__()
методы.
Когда ваш super()
звонок идет к каждому из Firstbase
а также Secondbase
, их конструкторы попробуют и распакуют **kwargs
в их ключевые аргументы. Все в **kwargs
нужно куда-то идти. Все, что осталось (т.е. не указано аргументом ключевого слова), например size
, поднимет ваш TypeError, если нет другого **kwargs
для остатков, чтобы войти в.