Pull to refresh

Comments 29

Очень интересно, спасибо; а можно добавить свои 5 копеек? )

>>… классический ООП подразумевает наличие только классов и объектов
О каком классическом ООП тут идет речь? :) Дело в том, что классический ООП — это, скорее Smaltalk, а там наличествуют и классы классов, и MetaObject Protocol, аналоги которого есть в Python и Ruby. Тот ООП, в котором этого нет — скорее, mainstream-ООП: c++, java, c#.

Ну… Smalltalk он, конечно, стар, крут и прапапа всего сущего, но все же ООП вошел в мэйнстрим благодаря прежде всего C++/Java, не так ли?

Поэтому на них и захотелось сослаться.
Не вопрос :) Хотел уточнить, чтобы убедиться, что я все правильно понял ) Спасибо за ответ.
В С++ целый океан говна помимо классов и объектов.
Я пайтон почти не знаю но разве что немного синтаксис но блин все больше удивляюсь его возможностям, прям взрыв мозгов %) Жду еще подобных статей
UFO just landed and posted this here
про метаклассы есть большая куча статей в гугле только они все старые… и специфические очень.
а тут наконец-то что-то собирающее всё в одной куче. респект автору.
>Питон расширяет классическую парадигму, и сами классы в нем тоже становятся
равноправными объектами

Т.е. с метаклассами классы становятся первоклассным объектом в языке?
Бинго.

Более того, и сами метаклассы тоже являются первоклассным объектом Питона, их можно передавать куда угодно, добавлять атрибуты и методы и проводить любые действия, возможные для объектов.
>Более того, и сами метаклассы тоже являются первоклассным объектом Питона, их можно передавать куда угодно, добавлять атрибуты и методы и проводить любые действия, возможные для объектов.

Собсно, это и подразумевается под «первоклассностью».
«Объект конструируется вызовом класса оператором „()“. Создадим наследованием от
type метакласс, переопределяющий этот оператор:»

А зачем такое извращение? вызов на классе () это банальный вызов конструктора у type. Поэтому если уж вы наследуетесь от type переопределять нужно имхо __init__, а никак не __call__.

Другое дело что в качестве __metaclass__ совершенно необязательно присваивать наследника type, а можно использовать любую callable штуку. Так что если бы у вас был например class AttributeInitType(object): ваша конструкция имела бы смысл.

Или я ошибаюсь?
Ответ вам — чуть ниже. По ошибке не туда залепил.
Ошибаетесь. Не проверял на Python 3.0, но на моем Python 2.6.2 получится вот так:

    class AttributeInitType(object):
        def __call__(self, *args, **kwargs):
            """ Вызов класса создает новый объект. """
            # Перво-наперво создадим сам объект...
            obj = type.__call__(self, *args)
            # ...и добавим ему переданные в вызове аргументы в качестве атрибутов.
            for name in kwargs:
                setattr(obj, name, kwargs[name])
            # вернем готовый объект
            return obj
    
    class Man(object):
        __metaclass__ = AttributeInitType
    
    me = Man(height = 180, weigth = 80)
    
    print me.height

>>> Traceback (most recent call last):
  File "/tmp/py7637jbF", line 13, in <module>
    class Man(object):
TypeError: Error when calling the metaclass bases
    object.__new__() takes no parameters


Почему же переопределялся __call__? потому что __init__ вызывается при определении класса, а не появлении объекта. Только что все проверил лично.
очень в тему, знаком со статьей. Одно из лучших пояснений к системе типов Питонаю
Очень круто! Спасибо за статью. И раз уж Вы здесь и знакомы с Django, как достигается цепочка параметров при запросе — Model.objects.filter().order_by().values() и т.д.?
Имеем, например:

  class Apple(Model):
      pass


Можем, например, сделать так:

  Apple.objects.all()  


Так вот. objects — ссылка на экземпляр класса Manager. В этом классе собрана вся
логика работы с моделью, стандартная либо расширенная для данной конкретной модели. По
любому из его методов вроде all() или get() возращается объект QuerySet,
инкапсулирующий в себе информацию об отложенных запросах.

Методы all() и get_query_set() из класса Manager:

  def get_query_set(self):
      """Returns a new QuerySet object.  Subclasses can override this method
      to easily customize the behavior of the Manager.
      """
      return QuerySet(self.model)

  def all(self):
      return self.get_query_set()


Теперь заглянем в определение класса QuerySet():

  def all(self):
      """
      Returns a new QuerySet that is a copy of the current one. This allows a
      QuerySet to proxy for a model manager in some cases.
      """
      return self._clone()

  def filter(self, *args, **kwargs):
      """
      Returns a new QuerySet instance with the args ANDed to the existing
      set.
      """
      return self._filter_or_exclude(False, *args, **kwargs)

  def _filter_or_exclude(self, negate, *args, **kwargs):
      if args or kwargs:
          assert self.query.can_filter(), \
                  "Cannot filter a query once a slice has been taken."

      clone = self._clone()
      if negate:
          clone.query.add_q(~Q(*args, **kwargs))
      else:
          clone.query.add_q(Q(*args, **kwargs))
      return clone


Что делают эти методы? Они создают копию последнего QuerySet, дописывают в атрибут
self.query новую, условного говоря, строчку, и возращают после этого копию.

Легко получается Apple.object.all().filter(color=«green»).get(). На каждом шаге,
в каждой точке, происходит копирование «наброска» запроса с расширением и
возвратом копии.

Так, кстати, и получаются эти самые «ленивые», «отложенные» вычисления. Запросы
к базе, скажем так, записываются в строчку, а выполняются уже одним пакетом либо
в уже шаблоне, либо по желанию программиста — преобразованием к обычному списку
объектов.
Туман рассеялся — все стало на свои места. Спасибо!
>Но если класс — объект, то какому классу он соответствует?
>По умолчанию этот класс (метакласс) называется type.
Задам дурацкий вопрос, а какой метакласс у type?
<a href«www.cafepy.com/article/python_types_and_objects/»> ссылочка по теме, где очень внятно нарисовано и объяснено. Тут выше ее, кажется, уже приводили.

Оттуда или прямо из интерпретатора видим, что,
>>>type(type)
<type 'type'>


И еще экспериментрируем:
>>> isinstance(object, type)
True
>>> isinstance(type, object)
True
>>> 


Ну так вот. type является «конкретизацией», или экземпляром, класса type(как бы рекурсивно) и наследует от object.

В то же время класс object является экземпляром класса type, но не наследует ни от чего.

Главное здесь:
1) Все, и классы, и метаклассы — наследуют от object. Значит, они являются первоклассными объектами.

2) Поведение класса в моменты объявления, вызова и наследования редактируется в метаклассе.

3) Поведение объектов редактируется в классе.
Бляха… Не пойму, что тут с тегом творится. Или ошибся сам где-то?
Итак, классический ООП подразумевает наличие только классов и объектов.

В ООП достаточно объектов и типов. Оффтопичный Javascript обходится лишь этим. Я не спорю. В этой области с терминологией творится полный кошмар.

Как показано на картинке по ссылке выше, пунктирчато-стрелочное отношение instanceOf транзитивно. Но, тем не менее, у каждого объекта есть один единственный, самый близкий ему тип. Тот, который его породил.

В Javascript отношение между объектом и «самым близким типом» реализуется через неявную ссылку obj.[[Prototype]], который можно выдернуть, сказав obj.constructor. В python — через obj.__class__ (который, не смотря на название, содержит-таки ссылку на этот «самый близкий тип»). Это я к тому, что термины: constructor и __class__ это синонимы. Поэтому, когда речь заходит об объекте, создающим объекты, то его стоит называть конструктором. Правда это не то же самое, что __init__(self), которая всего лишь процедура инициализации, связанная с объектом (она ни чего не создает, ей объект прилетает уже созданным, в самом первом аргументе self :) ).

В принципе, ссылки от объекта к своему конструктору (который тоже объект и у которого есть свой конструктор, котор…) уже достаточно для того, чтобы порождать магию instanceOf (то бишь typeof), и говорить о наследовании. Т.е. если представить, что у питоньего объекта есть лишь __class__ и __dict__, то станет понятно как живется несчастному жаваскрипту, без классов. :)

Но, в самом деле, как же «класс-ическое» ООП без классов? :) Class это обычный конструктор, характерной чертой которого является поддержка пусто-стрельчатого отношения IsA (т.е. наследования как показано на картинке) между объектами. Для этого в питоньих классах и появляется атрибут __bases__, где хранится кортеж супер-классов (список непосредственных родителей для данного класса, в смысле), суровое шаманство для иерархического поиска атрибутов в этой куче (в дополнение к поиску по иерархии типов), логика instanceOf начинает использовать isA, и определенные правила для манипуляции с object.__dict__. Мрак.

Для того, чтобы создавать классы «на лету» не обязательно придумывать новые конструкторы (мета-классы). Python предоставляет несколько встроенных и шикарную библиотеку (import types) на все оставшиеся 90% случаев жизни. Например:
>>> ClassOfPythons = type('ThePython', (object,), {'voice': 'sh-sh-sshhhhhh....'})
>>> squasher = ClassOfPythons()
>>> squasher.voice
sh-sh-sshhhhhh....

ну или так:
ClassOfPythons = object.__class__('ThePython', (object,), {'voice': 'sh-sh-sshhhhhh....'})

что абсолютно тоже самое, поскольку id(object.__class__) == id(type)

Мета-класс это узко-специальная магия для того, чтобы класс мог выбрать себе конструктора. Фактически это ситуация равносильна тому, когда хвост станет махать собакой — бессмыслица, но иногда очень нужно. :) В метакласс в django-модели используется сугубо как синтаксический сахар. Пол-статьи ни о чем. А вот пример с абстрактным классом, просто красавчик. За это даже плюсик в карму, если получится. :)
Очень люблю Javascript за его подход к ООП. Очень. Но не понимаю, вообще не понимаю, при чем здесь язык :)

Приятно, конечно, что прототипы позволяют избавиться от лишнего слоя абстракций в виде классов и, тем более, метаклассов. Более того, эти сущности при желании можно моделировать средствами JS.

Но все же людям привычно классифицировать понятия, которыми оперирует система; оттуда и страсть, ИМХО, к этому стилю (есть класс и его объект) во множестве популярнейших языков. С такой аргментации начинается среднее введение в ООП.

зачем вы привели сейчас либу types? Цитирую справку: «This module defines names for some object types that are used by the standard Python interpreter...» То есть можно будет проверять, правильные ли типы подсунуты на вход функции. Пример с того же дока:

from types import *
def delete(mylist, item):
    if type(item) is IntType:
       del mylist[item]
    else:
       mylist.remove(item)


Метаклассы — тонкий инструмент, использовать в большей части случаев не обязательно, даже избыточно. Но иной раз он позволяет изящно и красиво решать уникальные задачи. Типа DSL склепать в стиле джанговских моделей.

Очень хорошо представляю, как вы начнете всю эту логику наследования моделей, передачи атрибута _meta, учет абстрактных базовых классов решать в функциональном стиле (ModelClass = type (...))! Будет и больше, и мутнее, и некрасиво.

Дело, конечно, хозяйское, Питон дает несколько путей решения этих проблем. Насколько я понимаю слово pythonic — лучшее решение то, которое легче понять и прочитать.
Приятно, конечно, что прототипы позволяют избавиться от лишнего слоя абстракций в виде классов и, тем более, метаклассов. Более того, эти сущности при желании можно моделировать средствами JS.

Извиняюсь за сумбурность вышесказанного. Ведь хотелось подвести к прямо противоположному выводу. :) Объекты и типы — вот базовая штука в ООП. А классы — это такой жутко полезный тип, — которого в javascript, как раз, серьезно не хватает. Ну и они там не реализуется по-человечески, поскольку язык не позволяет абстрагировать основную оопшную точкозапись «obj.property».
Поясните фразу «не позволяет абстрагировать». Через точку можно обращаться к свойствам объекта-словаря? Можно. В чем проблема?

У меня до сих пор не стояла задача масштабного проектирования на JS, средняя задача на этом языке гораздо меньше. Но интересно было бы выработать какой-нибудь специфичный для прототипной схемы наследования подход в архитектуре.

Я пока не видел никаких особых руководств на эту тему… Надо поискать да подумать.
Вы пишете obj.property, явно желая получить о объекта «obj», нечто, связанное с именем «property». Но где объект возьмет это «нечто»? В конструкторе? В __dict__? В каком-то из супер-классов? Для этого, в new-классах python используются умопомрачительные правила: www.cafepy.com/article/python_attributes_and_methods/python_attributes_and_methods.html#id402018 (следующая статья по вашей ссылке :) ). Другие типы (не-new-классы) имеют другие правила. А благодаря «магическим» методам (__getattr__, __setattr__ и т.п.) и вы можете придумать что-то свое. Но, что бы вы там не напридумывали, «снаружи» это все равно будет выглядить как «obj.property». Вот это и есть абстракция доступа.

В Javascript можно реализовать подобные правила, и описать их внутри методов. Но тогда и «снаружи» они будут выглядить как вызовы методов. Мне попадалось, например, такое: value = obj.findName('property').get(). Внутри findName() реализован механизм ресолвинга свойства по иерархии классов, а при помощи get() абстрагирован доступ к value. Т.е. логически все сделано. Но без абстракции доступа (синтаксического сахара, со стороны языка, если хотите).
Ну понятно…

Никто, в общем-то, и не говорил, что js — пирамида Хеопса компьютерного языкостроения, да и слава богу.

В любом случае, некоторые особенности языка не могут не вызвать симпатии. Другие же — недоумение от ограниченности. :)
Логика диспетчеризации запроса для получения свойства с использованием прототипа, примитивна до безобразия:
— Эй, Объект, гони сюда свойство property!
— Хм… а нету… (если есть, был обязан выдать)
— Тогда пусть вернет тот, кто тебя породил, такого урода! (объект бежит просить в словаре у своего конструктора)

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

Диспетчеризация запроса по иерархии классов отражает совсем другое отношение: конкретное-общее (класс -> супер-класс) — и логика диспетчеризации запросов тут совершенно иная. Единственное что их роднит с «объект-конструктор», это транзитивное отношение «instanceOf», которое может означать и «объект типа», и «объект класса» (см. диаграмму www.cafepy.com/article/python_types_and_objects/images/relationships_transitivity.png). Но из-за этого «двойного» смысла и возникает путаница, будто одно можно выразить через другое. Это не так (не даром, на диаграмме стрелки выглядят по разному).
Sign up to leave a comment.

Articles