Присвойте аргументы функции `self` - PullRequest
24 голосов
/ 30 декабря 2011

Я заметил, что я обычно использую шаблон для присвоения 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, который создаст для вас этот шаблонный код .

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

Ответы [ 10 ]

9 голосов
/ 30 декабря 2011

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

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

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

Чтобы сделать этот пункт более конкретным, давайте рассмотрим функцию, которая принимает объект, dict и список имен и присваивает значения из dict именам из списка. Это гарантирует, что вы по-прежнему четко указываете, какие переменные присваиваются 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 "порции"). Так что в этом случае ваш код уже немного сложен для чтения, и это только сделает его более сложным.

7 голосов
/ 31 декабря 2011

Вы могли бы сделать это, благодаря простоте:

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

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

>>> 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).

3 голосов
/ 31 декабря 2011

Ваш конкретный случай также может быть обработан с помощью 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
3 голосов
/ 30 декабря 2011

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

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

>>> s = SomeClass(7,8)
>>> print s.a,s.b,s.c
7 8 2
1 голос
/ 19 мая 2016

Я решил это для себя, используя 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
>>> 
1 голос
/ 31 декабря 2011

В этом простом сценарии использования я должен сказать, что мне нравится ставить вещи явно (используя 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/ с идеей обновления экземпляра из аргументов, но это спутает статическийанализ кода (а IMO может усложнить отслеживание), поэтому я бы использовал этот подход только для экземпляра, который создан локально и отброшен внутриту же область видимости, не передавая ее куда-либо еще).

1 голос
/ 31 декабря 2011

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

>>> 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__.

1 голос
/ 30 декабря 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']
0 голосов
/ 05 декабря 2017

Одна из проблем с ответом @ 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']

Кроме того, можно удалить себя во время конструирования, используя словарные понимания, представленные в 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__)
0 голосов
/ 30 декабря 2011

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

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

Это работает, хотя и было протестировано при чрезвычайно основных обстоятельствах.

Решение

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казалось плохим решением.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...