Подтвердить что ты не робот

Назначить аргументы функции `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, которая создаст для вас шаблонный код.

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

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

>>> 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 "кусков" ). Таким образом, в этом случае ваш код уже немного сложно читать, и это только сделает его более сложным.

Ответ 3

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

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

Ответ 4

Мод для ответа @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

Ответ 5

В вашем конкретном случае можно также обрабатывать 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

Ответ 6

Вы можете сделать это через 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']

Ответ 7

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

Я написал рецепт для него по адресу 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

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

Ответ 8

Я решил это для себя, используя 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
>>> 

Ответ 9

Отказ

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

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

Решение

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

Ответ 10

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