LINUX.ORG.RU

Вопрос по Python


0

1

Я хочу сделать что-то наподобие такого:

class A(object):
    def __init__(self, **kwargs):
        self.a = 0
        self.b = ""
        for key in kwargs:
            setattr(self, key, kwargs[key])

a1 = A(a=1, b="a")
d = {'a': 2, 'b': "b"}
a2 = A(**d)
То есть у полей есть какие-нибудь значения по умолчанию, и при создании нового объекта в конструктор передаются либо значения, либо словарь с ними.

Но класс A у меня абстрактный, по-сути нигде не будет его экземпляров. Зато есть производный класс B, в котором добавляются свои поля. Если написать так:

class B(A):
    def __init__(self, **kwargs):
        A.__init__(self, kwargs)
        self.c = []
        # тут ещё куча полей
        for key in kwargs:
            setattr(self, key, kwargs[key])
,то получится, что полям значения присваиваются по 2 раза.

У меня есть 2 варианта, как это обойти: либо убрать цикл из базового класса, либо во всех производных классах писать

class B(A):
    def __init__(self, **kwargs):
        A.__init__(self, kwargs)
        self.c = kwargs.get('c', [])
        # тут ещё куча полей

Так вот, как это всё лучше организовать?



Последнее исправление: memnek (всего исправлений: 1)
class A:
    def __init__(self, **kwargs):
        self.a = 0
        self.b = ""

        for key in kwargs:
            setattr(self, key, kwargs[key])

class B(A):
    def __init__(self, **kwargs):
        kwargs.update(c=[])
        A.__init__(self, **kwargs)

не?

theNamelessOne ★★★★★
()

Так вот, как это всё лучше организовать?

Это лучше не организовывать.

tailgunner ★★★★★
()
class B(A):
    def __init__(self, **kwargs):
        self.c = []
        # тут ещё куча полей
        A.__init__(self, **kwargs)

//Ваш К.О.

AIv ★★★★★
()
Ответ на: комментарий от memnek

а чем плох первый способ через setattr?

тем, что так ты присваиваешь два раза. - К.О.
"... как это всё лучше организовать" - бери решение theNamelessOne, он всё правильно написал.
</thread>

iSlava
()
Ответ на: комментарий от iSlava
self.__dict__.setdefault(key, kwargs.get(key, None))

не сработает, так как ключ a уже есть. будет только старое значение. нужно update тогда делать. всё равно будет 2 присваивания

memnek
() автор топика
Ответ на: комментарий от buddhist

Но ТС хочет чего-то странного.

ТС скорее всего хочет быстрое прототипирование.

class Employee(object):
    def __init__(self, *initial_data, **kwargs):       
        attrs = {'salary':1000, 'has_minetchica':False} 
        attrs.update(kwargs)       
                
        for key in attrs:
            setattr(self, key, attrs[key])
        
        for dictionary in initial_data:
            for key in dictionary:
                setattr(self, key, dictionary[key])

class Boss(Employee):
    def __init__(self, *initial_data, **kwargs):
        attrs = {'salary':5000, 'has_minetchica':True, 'can_fire_me':'yes'} 
        attrs.update(kwargs)
        
        super(Boss, self).__init__(*initial_data, **attrs)


standard_programmer={'language':'C#', 'is_monkey':True}
marginal_programmer={'language':'coq', 'has_minetchica':True, 'is_ilita':True}

boss = Boss(name="John", age=66)
...
programmer = Employee(standard_programmer, name="Kurt", age=19) 
...
programmer2 = Employee(marginal_programmer, name="Jack", age=33, salary=9999) 
beka
()
Ответ на: комментарий от beka

Так можно же просто все в словаре хранить. Динамически менюяющийся список полей у класса — это какой-то апофеоз быдлокода, как непостоянный стековый эффект у слов в Форте.

buddhist ★★★★★
()
Ответ на: комментарий от memnek

можно немного пошевелить мозгами, и сработает

self.__dict__.update({key:kwargs[key]})
но это не оптимальное решение в твоем случае, как уже выяснилось. думаю, далее обсуждение бессмысленно.

iSlava
()
Ответ на: комментарий от buddhist

Динамически менюяющийся список полей у класса — это какой-то апофеоз быдлокода

У ТС он изначально задан и известен. В моем примере он, действительно, меняется динамически, но я всего-лишь хотел показать ТС, чтобы тот не забывал про *args.

beka
()
Ответ на: комментарий от iSlava

ну про update я тебе говорил. только вместо этого

self.__dict__.update({key:kwargs[key]})
лучше сделать
self.__dict__.update(kwargs)
после цикла

memnek
() автор топика
Ответ на: комментарий от beka

про *args я знаю и помню, только оно мне скорее всего не нужно будет.

memnek
() автор топика
Ответ на: комментарий от buddhist

зачем хранить отдельно в словаре, если атрибуты в виде словаря представлены в поле __dict__ ?

memnek
() автор топика
Ответ на: комментарий от buddhist

где костыль? в поле __dict__ хранится представление атрибутов экземпляра класса в виде словаря. хочешь напрямую обращайся, хочешь - работай со словарём:

class A:
    def __init__(self):
        self.a = 0
        self.b = 1
# доступ к полям:
a = A()
a.a = 2
a.b = 3
# получить атрибуты в виде словаря
print a.__dict__ # {'a': 2, 'b': 3}
А ты, как я понял, предлагаешь всё хранить в словаре. тогда доступ к атрибутам будет такой:
class A:
    def __init__(self):
        attrs = {'a': 0, 'b': 1}
# доступ выглядит костыльно
a.attrs['a'] = 2
a.attrs['b'] = 3
# либо, если есть сеттеры/геттеры
a.setA(2)
a.setB(3)
уж лучше всё хранить отдельно, а не в словаре

memnek
() автор топика
Ответ на: комментарий от memnek

что не так?

Как минимум 2 вещи: 1) ты изобретаешь какую-то НЕХ; 2) в процессе изобретения этой НЕХ ты демонстрируешь желание вмешиваться в низкоуровневые механизмы функционирования Python, которых не понимаешь (если бы понимал, то написал бы свою реализацию __getattr__ и __setattr__, или использовал метакласс, или еще что-нибудь).

tailgunner ★★★★★
()
Ответ на: комментарий от memnek

Если тебе нужен контейнер ключей со значениями по-умолчанию, просто используй словарь. *Просто* словарь.

Если это, всё-таки, нормальный класс, предполагающий поведение, то хотелось бы узнать реальный пример, зачем такое надо.

baverman ★★★
()
Ответ на: комментарий от baverman

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

# либо так
values = {'var1': 1, 'var2': 2, ...}
a = A(**values)
# либо так
a = A(var1=1, var2=2, ...)
чтобы не писать дофига строчек вида
self.var1 = var1
self.var2 = var2
...
я хочу сделать в цикле присваивание переданных значений.

То есть, в каждом классе-наследнике будут определяться дефолтные значения полей (и, возможно, некоторые действия), а в родительском классе будет собственно цикл, в котором все переданные в конструктор значения будут присваиваться полям. например, как тут

Если это неправильно, хочется увидеть, как надо это делать

memnek
() автор топика
Ответ на: комментарий от buddhist

А если он изначально известен, то лучше хранить все в словаре и для каждого поля определить аксессоры.

Мы изначально вели беседу про автоматическое создание экземпляра класса из заранее заданного списка атрибутов для нужд быстрого прототипирования. Вы же переключились на аксессоры, т.е. доступ к свойствам класса.

К примеру, есть код, почти как в оп:

from functools import wraps, partial
from inspect import getargspec, isfunction
from itertools import izip, ifilter, starmap
import types

def autoassign(*names, **kwargs):
    if kwargs:
        exclude, f = set(kwargs['exclude']), None
        sieve = lambda l:ifilter(lambda nv: nv[0] not in exclude, l)
    elif len(names) == 1 and isfunction(names[0]):
        f = names[0]
        sieve = lambda l:l
    else:
        names, f = set(names), None
        sieve = lambda l: ifilter(lambda nv: nv[0] in names, l)
    def decorator(f):
        fargnames, _, _, fdefaults = getargspec(f)
        fargnames, fdefaults = fargnames[1:], fdefaults or ()
        defaults = list(sieve(izip(reversed(fargnames), reversed(fdefaults))))
        @wraps(f)
        def decorated(self, *args, **kwargs):
            assigned = dict(sieve(izip(fargnames, args)))
            assigned.update(sieve(kwargs.iteritems()))
            for _ in starmap(assigned.setdefault, defaults): pass
            self.__dict__.update(assigned)
            return f(self, *args, **kwargs)
        return decorated
    return f and decorator(f) or decorator

class A(object):
    @autoassign('a','b')
    def __init__(self, a, b):
        pass

class B(A):
    @autoassign('c', 'd')
    def __init__(self, a, b, c, d=4):
        super(B, self).__init__(a,b)

values = {'a':1, 'b':2, 'c':3, 'd':4}
b=B(**values)
print b.a, b.b, b.c, b.d # печатает 1 2 3 4

Если где-то непременно нужно сделать property, с провекой присваемого значения - то это также легко и никак не связано с автоматическим созданием экземпляра класса. Можно вообще переписать autoassign так что, для каждого атрибута будет автоматически создаваться setter/getter - но практического смысла я в этом не вижу.

Еще раз - все вышесказанное для прототипирования, а не для продакшена.

beka
()
Ответ на: комментарий от memnek

Если это неправильно, хочется увидеть, как надо это делать

вопрос

I'm using __init__() like this in some SQLAlchemy ORM classes that have many parameters (upto 20).

def __init__(self, **kwargs):
    for k, v in kwargs.iteritems():
        setattr(self, k, v)

Is it «pythonic» to set attributes like this?

Ответ

Yes. Another way to do this is.

def __init__(self, **kwargs):
    self.__dict__.update( kwargs )

http://stackoverflow.com/questions/739625/setattr-with-kwargs-pythonic-or-not

beka
()
Ответ на: комментарий от memnek

чтобы не писать дофига строчек вида

Просто сделай отдельный метод.

class Boo(object):
    def __init__(self):
        self.boo = 10

    def init(self, **kwargs):
        self.__dict__.update(kwargs)
        return self

class Foo(Boo):
    def __init__(self):
        Boo.__init__(self)
        self.foo = 20

print vars(Foo().init(boo=30))
baverman ★★★
()
Ответ на: комментарий от memnek

почему НЕХ? я хочу вот это

Это и есть НЕХ.

вот, на мой взгляд, решение

Это формулировка задачи. И, если задача в самом деле такая, правильнее всего было бы освоить метаклассы. Или, на крайняк:

class Whatever(object): pass

def ginit(self, **kwargs):
  for key in kwargs:
    setattr(self, key, kwargs[key])

def mkcls(clsnm, defaults = {}):
  d = defaults.copy()
  d["__init__"] = ginit
  return type(clsnm, (Whatever,), d)

Foo_cls = mkcls("Foo", {"bzz": 123})

foo = Foo_cls(bar="bar", baz=0)
tailgunner ★★★★★
()
Ответ на: комментарий от Virtuos86

О, не знал об этом, спасибо. (и это питонщики говорят, что всегда должен быть только один способ выполнить что-то)

buddhist ★★★★★
()
Ответ на: комментарий от buddhist

Поправочка: один _прямой_ способ. И то, даже так не всегда верно.
Кстати, baverman выше тоже vars заюзал.

Virtuos86 ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.