Привет всем.
Меня тут один умник попросил прокомментировать некоторые куски моего кода, а я подумал, что это может оказаться интересным для многих. Да и критику я люблю. Только нормальную, а не: "Это, скуцо, всё фуфло! Медведа на вас нет!.."

Итак, как я уже говорил, пишу я сейчас, главным образом на PyQt4. В Qt, пришедшем из C++, мы привыкли к управлению свойствами класса в стиле:
class c(QWidget):
    ...
    def variable(self):
        return ...
    def setVariable(self, Value):
        ...
Это довольно удобно, особенно когда нужно переопределить метод. Однако питон предоставляет более красивую возможность:
class c(QWidget):
    def variable(self):
        return ...
    def setVariable(self, Value):
        ...
    Variable = property(variable, setVariable, lambda self: None)
Теперь мы можем использовать Variable гораздо удобнее:
o = c()
o.Variable = True
Var = o.Variable
Третий параметр property нужен для невозможности удалить Variable путём "del o.Variable". Его можно опустить.
Если нам нужно передать в setVariable несколько аргументов, то замечательно работает такая запись:
class c(QWidget):
    def variable(self):
        return ...
    def setVariable(self, arg1, arg2, arg3):
        ...
    Variable = property(variable,
                lambda self, args: self.setVariable(*args),
                lambda self: None)

o = c()
o.Variable = arg1, arg2, arg3
Думаю, что тут всё понятно.

Довольно интересная проблема состоит в том, что если переопределить методы variable и setVariable, то вызываться они не будут, потому что property сохраняет ссылки на методы того класса в котором они определены. На помощь нам придёт lambda.
class c(QWidget):
    def variable(self):
        return ...
    def setVariable(self, Value):
        ...
    Variable = property(
                lambda self: self.variable(),
                lambda self, Value: self.setVariable(Value),
                lambda self: None)
Ламбда функции это вообще очень сильный механизм. Когда я в него въехал, то многие вещи стали решаться гораздо легче, чем раньше.

Вот небольшой пример из моего проекта:
class ModulesStack(QDialog):
    def __init__(self, Parent):
        ...  ## Определение, заполнение виджета...
        self.Modules = [mod1, mod2, mod3]    ## У модуля (modX) есть параметр Visible, 
                                     ##определённый, кстати, с помощью property и возвращающий True или False.

    def visibleModules(self): return filter(lambda Mod: Mod.Visible, self.Modules)
    VisibleModules = property(
                lambda self: self.visibleModules(),
                lambda self, Modules: self.Modules = Modules,
                lambda self: None)
Или, вообще страшный вариант:
class User(QObject):
    '''Информация о пользователи + кэш'''
    def __init__(self, UserId):
        QObject.__init__(self)
        self.UserId = UserId
        self.LoadTimestamp = None
        self.load()

    def clear(self):
        for par in [
                    "Login", "CreateTimestamp", "UpdateTimestamp", "DeleteTimestamp", "Inactive",
                    "Surname", "Name", "Patronymic", "Sex", "Birthday", "Description",
                    "CountryId", "RegionId", "SityId", "Address",
                    "EMail", "Jabber", "ICQ", "Http", "Ftp",
                    "PhoneHome", "PhoneWork", "PhoneMobile"
                ]:
            exec """self.%s = None""" % par

        self.ExpireDate = QDate.fromString("2099-12-31", "yyyy-MM-dd")

    def load(self):
        '''Эта функция фактически состоит из запроса серверу (опущен) и множества приравниваний.'''
        ...
        self.Login = rq.Dict["System"].get("Login", None)
        self.CreateTimestamp = rq.Dict["System"].get("CreateTimestamp", None)
        self.UpdateTimestamp = rq.Dict["System"].get("UpdateTimestamp", None)
        ...

    def profLoad(self):
        '''Вызывается при каждом запросе параметра, проверяет, не пора ли обновить '''
        '''информацию и, в случае надобности, вызывает load. '''
        '''Собственно ради неё все эти замутнения и сделаны.'''
        ...

    for par in [
                "UserId", "Login", "CreateTimestamp", "DeleteTimestamp",
                "UpdateTimestamp", "Inactive", "ExpireDate",
                "Surname", "Name", "Patronymic", "Nick", "Sex", "Birthday", "Description",
                "CountryId", "RegionId", "SityId", "Address",
                "EMail", "Jabber", "ICQ", "Http", "Ftp",
                "PhoneHome", "PhoneWork", "PhoneMobile"
            ]:
        exec """_%s = None""" % par
        exec \
        """def %s(self):
            self.profLoad()
            return self._%s""" % (par[0].lower() + par[1:], par)
        exec \
        """def set%s(self, Value):
            self._%s = Value
            self._emitSetSignal("%s", Value)""" % ((par,) * 3)
        exec \
        """%s = property(%s, set%s, lambda self: self.set%s(None))""" % (par, par[0].lower() + par[1:], par, par)
    del par
P.S. Вы уж скажите, если кому интересно, а то, может, я зря распинаюсь и засоряю форум...

P.P.S. Мне тут намекнули на то, что в такой нотации классы становятся похожи на VCL. Блин, действительно. А я и не заметил. Просто последняя дельфя, которую я видел, была четвёртой версии...

Last edited April 3, 2008, 5:42 a.m.

...так кто ж ты, наконец?
-- Я -- часть той силы, что вечно хочет зла
и вечно совершает благо.
эм. это особенности QT?
Почему мы не заводим просто переменную self.variable, а создаем для нее кучу методов?
SHIZA
эм. это особенности QT?
Нет. Это всё чистый питон. property в __builtins__ появился в python 2.2.
Просто я попытался показать это в сравнении с Qt-стилем.

SHIZA
Почему мы не заводим просто переменную self.variable, а создаем для нее кучу методов?
В моём последнем примере это ясно показано. Когда мы делаем"o.Variable = Value", то у нас вызывается обработчик ("o.setVariable"), в котором мы "можем много всего делать..." (с) Масяня
Можно, конечно, воспользоваться таким методом:
class c:
    def __setattr__(self, Var, Val):
        if Var == "Variable":
            ...
        elif...
        else:
            c.__class__.__setattr__(self, Var, Val)

   def __getattr__(self, Var):
        if Var == "Variable":
            ...
            return ...
        elif...
        else:
            return c.__class__.__getattr__(self, Var)
Но, ИМХО, это совсем уж неудобно.
...так кто ж ты, наконец?
-- Я -- часть той силы, что вечно хочет зла
и вечно совершает благо.
Насчет - как делать - все понятно.
ZZZ
"можем много всего делать..." (с) Масяня
Не понятно - чем обусловлена необходимость так делать? Ведь не просто так такая куча кода городится. Может всетки QT к этому подталкивает? (сам с QT дела не имел)

Last edited April 3, 2008, 6:53 a.m.

Все. вроде воткнул.
Наример мы меняем цвет фона у чего-нибудь и после этого делаем какой-нибудь redraw().
SHIZA
Не понятно - чем обусловлена необходимость так делать? (ведь не просто так такая куча кода городится).
А как бы сделал ты? Перепиши предпоследний пример (VisibleModules).
Я знаю, ты бы просто не стал городить property. Но ведь это удобно! Посмотри на её поведение. При запросе мы получаем лист модулей, у которых Visible == True. При присваивании мы заменяем Modules.
...так кто ж ты, наконец?
-- Я -- часть той силы, что вечно хочет зла
и вечно совершает благо.
Ок. А как насчет того, чтоб кажый раз не городить огород - сделать базовый класс, который, если наследовать его вместе с нужным класслм - будет все это сам делать.

Last edited April 3, 2008, 4 p.m.

SHIZA
Ок. А как насчет того, чтоб кажый раз не городить огород - сделать базовый класс, который, если наследовать его вместе с нужным класслм - будет все это сам делать.
И как ты себе это представляешь?
class c:
    def __getattr__(self, Var):
        if Var[0].isupper() and Var[0].lower() + Var[1:] in self.__dict__ and callable(self.__dict__[Var[0].lower() + Var[1:]]):
            return self.__dict__[Var[0].lower() + Var[1:]]()
        else:
            try:
                return self.__dict__[self, Var]
            except KeyError:
                raise AttributeError

    def __setattr__(self, Var, Val):
        if "set" + Var in self.__dict__ and callable(self.__dict__["set" + Var]):
            self.__dict__["set" + Var](Val)
        else:
            self.__dict__[Var] = Val

class Module(c, QWidget):
    def __init__(self):
        QWidget.__init__(self)
        ...
Кривовато, однако. Хотя и интересно.
Опять же:
1. 'Проблема нескольких аргументов'
2. Oтсутствие методов в dir(Module) (хотя setVariable и variable будут)
3. Невозможность нормально документировать всё это (у property есть четвёртый аргумент)

Last edited April 3, 2008, 7:25 p.m.

...так кто ж ты, наконец?
-- Я -- часть той силы, что вечно хочет зла
и вечно совершает благо.
ZZZ
Или, вообще страшный вариант:
class User(QObject):
    '''Информация о пользователи + кэш'''
    def __init__(self, UserId):
        QObject.__init__(self)
        self.UserId = UserId
        self.LoadTimestamp = None
        self.load()

    def clear(self):
        for par in [
                    "Login", "CreateTimestamp", "UpdateTimestamp", "DeleteTimestamp", "Inactive",
                    "Surname", "Name", "Patronymic", "Sex", "Birthday", "Description",
                    "CountryId", "RegionId", "SityId", "Address",
                    "EMail", "Jabber", "ICQ", "Http", "Ftp",
                    "PhoneHome", "PhoneWork", "PhoneMobile"
                ]:
            exec """self.%s = None""" % par

        self.ExpireDate = QDate.fromString("2099-12-31", "yyyy-MM-dd")

    def load(self):
        '''Эта функция фактически состоит из запроса серверу (опущен) и множества приравниваний.'''
        ...
        self.Login = rq.Dict["System"].get("Login", None)
        self.CreateTimestamp = rq.Dict["System"].get("CreateTimestamp", None)
        self.UpdateTimestamp = rq.Dict["System"].get("UpdateTimestamp", None)
        ...

    def profLoad(self):
        '''Вызывается при каждом запросе параметра, проверяет, не пора ли обновить '''
        '''информацию и, в случае надобности, вызывает load. '''
        '''Собственно ради неё все эти замутнения и сделаны.'''
        ...

    for par in [
                "UserId", "Login", "CreateTimestamp", "DeleteTimestamp",
                "UpdateTimestamp", "Inactive", "ExpireDate",
                "Surname", "Name", "Patronymic", "Nick", "Sex", "Birthday", "Description",
                "CountryId", "RegionId", "SityId", "Address",
                "EMail", "Jabber", "ICQ", "Http", "Ftp",
                "PhoneHome", "PhoneWork", "PhoneMobile"
            ]:
        exec """_%s = None""" % par
        exec \
        """def %s(self):
            self.profLoad()
            return self._%s""" % (par[0].lower() + par[1:], par)
        exec \
        """def set%s(self, Value):
            self._%s = Value
            self._emitSetSignal("%s", Value)""" % ((par,) * 3)
        exec \
        """%s = property(%s, set%s, lambda self: self.set%s(None))""" % (par, par[0].lower() + par[1:], par, par)
    del par
Этот код - просто отвал башки. Это не питон, это что-то не понятное.

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

P.P.S. Мне тут намекнули на то, что в такой нотации классы становятся похожи на VCL. Блин, действительно. А я и не заметил. Просто последняя дельфя, которую я видел, была четвёртой версии...
ZZZ, попробуй почитать свой код через полгода-год после окончания работы с проектом. Что если в качестве умника, которому нужны комментарии по коду, будешь ты сам?
Be easy, stay cool
j2a
Этот код - просто отвал башки. Это не питон, это что-то не понятное.
ZZZ, попробуй почитать свой код через полгода-год после окончания работы с проектом. Что если в качестве умника, которому нужны комментарии по коду, будешь ты сам?
class _socketobject(object):
    ...
    family = property(lambda self: self._sock.family, doc="the socket family")
    type = property(lambda self: self._sock.type, doc="the socket type")
    proto = property(lambda self: self._sock.proto, doc="the socket protocol")

    _s = ("def %s(self, *args): return self._sock.%s(*args)\n\n"
          "%s.__doc__ = _realsocket.%s.__doc__\n")
    for _m in _socketmethods:
        exec _s % (_m, _m, _m, _m)
    del _m, _s
Это тоже плохой код?
Собственной персоной "/usr/lib/python2.5/socket.py"...
Общая идея взята именно отсюда.

А как ещё ты прикажешь сделать это? Каждый метод описывать в ручную?
Питон на то и питон, чтобы быть динамическим.
...так кто ж ты, наконец?
-- Я -- часть той силы, что вечно хочет зла
и вечно совершает благо.