Назначить аргументы функции `self`

Я заметил, что общий шаблон, который я использую, заключается в назначении SomeClass.__init__() аргументов self атрибутам с тем же именем. Пример:

class SomeClass():
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c

На самом деле это должно быть общей задачей для других, а в PyDev есть ярлык для этого - если вы поместите курсор в список параметров и щелкните Ctrl+1 вам предоставляется возможность Assign parameters to attributes создать для вас этот шаблонный код.

Есть ли другой, короткий и элегантный способ выполнить это задание?


person Jonathan    schedule 30.12.2011    source источник
comment
личное мнение: шаблонный код, подобный этому, является распространенным признаком отсутствующей языковой конструкции...   -  person Jonathan    schedule 30.12.2011
comment
хм.... можно ли это сделать с помощью декоратора?   -  person Jonathan    schedule 30.12.2011
comment
почти любая альтернатива не позволит Ctrl+Space PyDev автоматически заполнять эти атрибуты... такой позор...   -  person Jonathan    schedule 30.12.2011
comment
Да, вы можете сделать это с помощью декоратора, но делать это явно будет лучше для вас в долгосрочной перспективе. Также это не испортит автозаполнение PyDevs, если вы сделаете это явно   -  person ojii    schedule 30.12.2011
comment
Выполнение этого вручную строит характер.   -  person derekerdmann    schedule 30.12.2011
comment
python.org/dev/peps/pep-0020 (в частности, явное лучше, чем неявное, простое лучше, чем сложное, читабельность имеет значение).   -  person    schedule 30.12.2011
comment
Большинство параметров конструктора, которые мои классы принимают, не следует копировать в self под тем же именем без преобразования. Я делаю вещи приватными, проверяю значения, нормализую их, заполняю изменяемые объекты, которые были бы значениями по умолчанию, если бы аргументы по умолчанию работали по-другому, и т. д.   -  person    schedule 30.12.2011
comment
+1 @derekerdmann Спасибо, папа.   -  person Droogans    schedule 31.12.2011
comment
Если Eclipse уже делает это за вас, то зачем пытаться исправить то, что не сломано?   -  person Droogans    schedule 31.12.2011
comment
@Droogans - есть те, кто считает, что зависимость от IDE вместо возможности легко кодировать с помощью текстового редактора связана с темной стороной :)   -  person Jonathan    schedule 01.01.2012
comment
Я согласен с ответом @senderle (особенно о необходимости рефакторинга, если у вас много параметров). Однако бывают редкие случаи, когда наличие большого количества параметров является правильным подходом (рассмотрите init оценщика sklearn). В этом случае я предлагаю использовать редактор с возможностями вертикального редактирования и автозаполнением. Я использую Intellij с привязками vim, без проблем!   -  person Chris    schedule 20.10.2017


Ответы (10)


Я разделяю ваше мнение о том, что шаблонный код — это плохо. Но в этом случае я не уверен, что может быть лучшая альтернатива. Рассмотрим возможности.

Если вы говорите всего о нескольких переменных, то легко читается последовательность из self.x = x строк. Фактически, я думаю, что его явность делает этот подход предпочтительным с точки зрения удобочитаемости. И хотя печатать может быть немного неудобно, одного этого недостаточно, чтобы оправдать новую языковую конструкцию, которая может скрыть то, что на самом деле происходит. Конечно, использование vars(self).update() махинаций в этом случае может привести к большей путанице, чем оно того стоит.

С другой стороны, если вы передаете девять, десять или более параметров в __init__, вам, вероятно, все равно придется провести рефакторинг. Таким образом, эта проблема действительно относится только к случаям, когда требуется передать, скажем, 5-8 параметров. Теперь я вижу, как восемь строк self.x = x будут раздражать как при вводе, так и при чтении; но я не уверен, что случай с 5-8 параметрами достаточно распространен или достаточно проблематичен, чтобы оправдать использование другого метода. Так что я думаю, что хотя проблема, о которой вы говорите, в принципе хорошая, на практике существуют другие ограничивающие факторы, которые делают ее неактуальной.

Чтобы конкретизировать этот момент, давайте рассмотрим функцию, которая принимает объект, словарь и список имен и присваивает значения из словаря именам из списка. Это гарантирует, что вы по-прежнему четко указываете, какие переменные присваиваются self. (Я бы никогда не предложил решение этой проблемы, которое не требовало бы явного перечисления присваиваемых переменных; это было бы магнитом для редкоземельных ошибок):

>>> def assign_attributes(obj, localdict, names):
...     for name in names:
...         setattr(obj, name, localdict[name])
...
>>> class SomeClass():
...     def __init__(self, a, b, c):
...         assign_attributes(self, vars(), ['a', 'b', 'c'])

Теперь, хотя это и не является ужасно непривлекательным, это все же сложнее понять, чем простую серию из self.x = x строк. И это также дольше и сложнее набирать, чем одну, две и, может быть, даже три или четыре строки, в зависимости от обстоятельств. Таким образом, вы получаете определенный выигрыш только начиная с пятипараметрического случая. Но это также точный момент, когда вы начинаете приближаться к пределу человеческого объема кратковременной памяти (= 7 +/- 2 "куска"). Итак, в этом случае ваш код уже немного сложен для чтения, и это только усложнит его.

person senderle    schedule 30.12.2011
comment
Поскольку мы говорим о произвольном количестве атрибутов, в python3 использование locals().keys() намного эффективнее, чем перечисление имен атрибутов вручную. пример: assignAttributes(self, vars(), locals().keys()) - person Skyler; 13.06.2018
comment
@Skyler, я думаю, вы, возможно, пропустили мою основную посылку. В частности, я считаю плохой идеей неявное имя атрибутов класса. Имена атрибутов должны быть указаны явно. Это обеспечивает минимальный стандарт самодокументирования. Вы когда-нибудь пробовали читать код, который делает что-то подобное? Не смешно! - person senderle; 13.06.2018

Вы можете сделать это, что имеет достоинство простоты:

>>>  class C(object):
    def __init__(self, **kwargs):
        self.__dict__ = dict(kwargs)

Это оставляет за любым кодом, создающим экземпляр C, решать, какие атрибуты экземпляра будут после построения, например:

>>> c = C(a='a', b='b', c='c')
>>> c.a, c.b, c.c
('a', 'b', 'c')

Если вы хотите, чтобы все объекты C имели атрибуты a, b и c, этот подход не будет полезен.

(Кстати, этот паттерн исходит от Гвидо, его собственного плохого «я», как общее решение проблемы определения перечислений в Python. Создайте класс, подобный приведенному выше, с именем Enum, а затем вы можете написать код, подобный Colors = Enum(Red=0, Green=1, Blue=2), и впредь использовать Colors.Red, Colors.Green и Colors.Blue.)

Это полезное упражнение, чтобы выяснить, какие проблемы могут возникнуть, если вы установите self.__dict__ на kwargs вместо dict(kwargs).

person Robert Rossney    schedule 30.12.2011
comment
Можете ли вы добавить ссылку на обсуждение Гвидо этого шаблона и перечислений? - person Air; 12.04.2016
comment
Это должно быть топовым решением именно за его элегантность и высокую вероятность успешной реализации. Он работает правильно, маловероятно, что он будет вести себя неожиданно, и занимает только одну, максимум две строки. - person TheLoneDeranger; 15.10.2018

Мод для ответа @pcperini:

>>> class SomeClass():
        def __init__(self, a, b=1, c=2):
            for name,value in vars().items():
                if name != 'self':
                    setattr(self,name,value)

>>> s = SomeClass(7,8)
>>> print s.a,s.b,s.c
7 8 2
person Jonathan    schedule 30.12.2011
comment
@ThiefMaster — удалено понимание списка. (Вы могли бы дать людям шанс исправить свои ошибки, прежде чем действовать, думаю, это верно здесь, как и в жизни) - person Jonathan; 02.01.2012
comment
Голоса могут быть изменены на неопределенный срок после того, как соответствующий ответ / вопрос был отредактирован;) Таким образом, нет ничего, что можно было бы возразить против голосования против + комментирования и последующего удаления голосования против и / или голосования. - person ThiefMaster; 02.01.2012
comment
Перечислите версию понимания выше: [ setattr(self, var[0], var[1]) for var in vars().items() if var[0] != "self" ] - person Seth Robertson; 01.07.2020

Ваш конкретный случай также может быть обработан с помощью namedtuple:

>>> from collections import namedtuple
>>> SomeClass = namedtuple("SomeClass", "a b c")
>>> sc = SomeClass(1, "x", 200)
>>> print sc
SomeClass(a=1, b='x', c=200)
>>> print sc.a, sc.b, sc.c
1 x 200
person PaulMcG    schedule 30.12.2011

Вы можете сделать это через setattr(), например:

[setattr(self, key, value) for key, value in kwargs.items()]

Не очень красиво, но может сэкономить место :)

Итак, вы получите:

  kwargs = { 'd':1, 'e': 2, 'z': 3, }

  class P():
     def __init__(self, **kwargs):
        [setattr(self, key, value) for key, value in kwargs.items()]

  x = P(**kwargs)
  dir(x)
  ['__doc__', '__init__', '__module__', 'd', 'e', 'z']
person Pavel Shvedov    schedule 30.12.2011
comment
Верно, но тогда я не могу определить список аргументов функции, что в большинстве случаев предпочтительнее - person Jonathan; 30.12.2011
comment
Вы можете определить список атрибутов класса, используя класс нового стиля с __slots__. - person Karl Knechtel; 30.12.2011
comment
Зачем использовать понимание списка, а не цикл? for key, value in kwargs.items(): setattr(self, key, value) немного короче и в то же время более прямолинеен. Я бы использовал iteritems(), а не items() в Py‹3. - person ; 30.12.2011
comment
И для краткости for a in kwargs.items(): setattr(self, *a) - person ; 30.12.2011
comment
-1 за использование понимания списка в качестве ярлыка для цикла for, это не очень хороший стиль, и его не следует предлагать новым ученикам. (Создает одноразовый список None, которые являются значениями, возвращаемыми из всех вызовов setattr, без всякой цели.) - person PaulMcG; 31.12.2011
comment
-1 тоже. Для меня это уродливее, чем простое назначение, и вы также теряете, какие атрибуты являются принятыми (добавлен ответ, объясняющий, как использовать класс Bunch, который, я думаю, лучше). - person Fabio Zadrozny; 01.01.2012

Для этого простого варианта использования я должен сказать, что мне нравится указывать вещи явно (используя Ctrl+1 из PyDev), но иногда я также заканчиваю тем, что использую групповую реализацию, но с классом, в котором принятые атрибуты создаются из предварительно объявленных атрибутов. в классе, чтобы я знал, что ожидается (и мне это нравится больше, чем именованный кортеж, поскольку я нахожу его более читабельным - и это не будет путать статический анализ кода или завершение кода).

Я разместил рецепт по адресу: http://code.activestate.com/recipes/577999-bunch-class-created-from-attributes-in-class/

Основная идея заключается в том, что вы объявляете свой класс как подкласс Bunch, и он создаст эти атрибуты в экземпляре (либо из значений по умолчанию, либо из значений, переданных в конструкторе):

class Point(Bunch):
    x = 0
    y = 0

p0 = Point()
assert p0.x == 0
assert p0.y == 0

p1 = Point(x=10, y=20)
assert p1.x == 10
assert p1.y == 20

Кроме того, Алекс Мартелли также предоставил реализацию связки: http://code.activestate.com/recipes/52308-the-simple-but-handy-collector-of-a-bunch-of-named/ с идеей обновления instance из аргументов, но это запутает статический анализ кода (и IMO может затруднить отслеживание), поэтому я бы использовал этот подход только для экземпляра, созданного локально и выброшенного внутри той же области, не передавая его куда-либо еще).

person Fabio Zadrozny    schedule 31.12.2011
comment
Хорошо, хотя это специально решает случай __init__, тогда как декоратор в этом смысле более общий. - person Jonathan; 01.01.2012
comment
Верно, но я думаю, что шаблон распространен только в методах конструктора и установщика (но поскольку методы установщика обычно имеют только 1 аргумент, изменение строки назначения для строки декоратора только делает для меня более нечитаемым). - person Fabio Zadrozny; 01.01.2012

Магия декоратора!!

>>> class SomeClass():
        @ArgsToSelf
        def __init__(a, b=1, c=2, d=4, e=5):
            pass

>>> s=SomeClass(6,b=7,d=8)
>>> print s.a,s.b,s.c,s.d,s.e
6 7 2 8 5

при определении:

>>> import inspect
>>> def ArgsToSelf(f):
    def act(self, *args, **kwargs):
        arg_names,_,_,defaults = inspect.getargspec(f)
        defaults=list(defaults)
        for arg in args:
            setattr(self, arg_names.pop(0),arg)
        for arg_name,arg in kwargs.iteritems():
            setattr(self, arg_name,arg)
            defaults.pop(arg_names.index(arg_name))
            arg_names.remove(arg_name)
        for arg_name,arg in zip(arg_names,defaults):
            setattr(self, arg_name,arg)
        return f(*args, **kwargs)
    return act

Конечно, вы можете определить этот декоратор один раз и использовать его в своем проекте.
Кроме того, этот декоратор работает с любой функцией объекта, а не только с __init__.

person Jonathan    schedule 31.12.2011
comment
python, вероятно, где-то уже имеет код, который соответствует args,kwargs,defaults в locals() или vars(), поэтому я мог бы сэкономить на его повторной реализации в этом декораторе - я открыт для предложений... - person Jonathan; 31.12.2011
comment
Кстати, я считаю, что в большинстве случаев @senderle прав, однако при определенных обстоятельствах такой подход может быть очень удобным. Языковая конструкция может быть даже более элегантной, чем этот декоратор, а также PyDev может поддерживать его, не теряя при этом Ctrl+space функциональности. Увы... - person Jonathan; 31.12.2011

Я решил это для себя, используя locals() и __dict__:

>>> class Test:
...     def __init__(self, a, b, c):
...         l = locals()
...         for key in l:
...             self.__dict__[key] = l[key]
... 
>>> t = Test(1, 2, 3)
>>> t.a
1
>>> 
person user3638162    schedule 19.05.2016

Отказ от ответственности

Не используйте это: я просто пытался создать ответ, наиболее близкий к первоначальным намерениям ОП. Как указано в комментариях, это зависит от полностью неопределенного поведения и явно запрещенных модификаций таблицы символов.

Однако он работает и был протестирован в чрезвычайно простых условиях.

Решение

class SomeClass():
    def __init__(self, a, b, c):
        vars(self).update(dict((k,v) for k,v in vars().iteritems() if (k != 'self')))

sc = SomeClass(1, 2, 3)
# sc.a == 1
# sc.b == 2
# sc.c == 3

Используя встроенную функцию vars(), этот фрагмент перебирает все переменные, доступные в методе __init__ (которые на данный момент должны быть просто self, a, b и c), и, очевидно, устанавливает переменные self равными одному и тому же. игнорируя аргумент-ссылку на self (потому что self.self кажется плохим решением.)

person Patrick Perini    schedule 30.12.2011
comment
docs.python.org/library/functions.html#vars явно запрещает изменение варс(). - person ; 30.12.2011
comment
отказ от ответственности обновлен. я не решаюсь снять его, просто потому, что я чувствую, что это довольно крутой ответ :) - person Patrick Perini; 30.12.2011

Одна из проблем с ответом @ user3638162 заключается в том, что locals() содержит переменную 'self'. Следовательно, вы получаете дополнительный self.self. Если человек не возражает против дополнительного «я», это решение может быть просто

class X:
    def __init__(self, a, b, c):
        self.__dict__.update(locals())

x = X(1, 2, 3)
print(x.a, x.__dict__)

self может быть удален после строительства с помощью del self.__dict__['self']

В качестве альтернативы, можно удалить self во время построения, используя понимание словаря, введенное в Python3.

class X:
    def __init__(self, a, b, c):
        self.__dict__.update(l for l in locals().items() if l[0] != 'self')

x = X(1, 2, 3)
print(x.a, x.__dict__)
person rahul    schedule 05.12.2017