Комментарии 29
Отличная статья, спасибо.
Очень интересно, спасибо; а можно добавить свои 5 копеек? )
>>… классический ООП подразумевает наличие только классов и объектов
О каком классическом ООП тут идет речь? :) Дело в том, что классический ООП — это, скорее Smaltalk, а там наличествуют и классы классов, и MetaObject Protocol, аналоги которого есть в Python и Ruby. Тот ООП, в котором этого нет — скорее, mainstream-ООП: c++, java, c#.
>>… классический ООП подразумевает наличие только классов и объектов
О каком классическом ООП тут идет речь? :) Дело в том, что классический ООП — это, скорее Smaltalk, а там наличествуют и классы классов, и MetaObject Protocol, аналоги которого есть в Python и Ruby. Тот ООП, в котором этого нет — скорее, mainstream-ООП: c++, java, c#.
Я пайтон почти не знаю но разве что немного синтаксис но блин все больше удивляюсь его возможностям, прям взрыв мозгов %) Жду еще подобных статей
давно собирался почитать про метаклассы и разобраться с чем их едят, а тут статья сама пришла :)
большое спасибо автору!
большое спасибо автору!
>Питон расширяет классическую парадигму, и сами классы в нем тоже становятся
равноправными объектами
Т.е. с метаклассами классы становятся первоклассным объектом в языке?
равноправными объектами
Т.е. с метаклассами классы становятся первоклассным объектом в языке?
Бинго.
Более того, и сами метаклассы тоже являются первоклассным объектом Питона, их можно передавать куда угодно, добавлять атрибуты и методы и проводить любые действия, возможные для объектов.
Более того, и сами метаклассы тоже являются первоклассным объектом Питона, их можно передавать куда угодно, добавлять атрибуты и методы и проводить любые действия, возможные для объектов.
«Объект конструируется вызовом класса оператором „()“. Создадим наследованием от
type метакласс, переопределяющий этот оператор:»
А зачем такое извращение? вызов на классе () это банальный вызов конструктора у type. Поэтому если уж вы наследуетесь от type переопределять нужно имхо __init__, а никак не __call__.
Другое дело что в качестве __metaclass__ совершенно необязательно присваивать наследника type, а можно использовать любую callable штуку. Так что если бы у вас был например class AttributeInitType(object): ваша конструкция имела бы смысл.
Или я ошибаюсь?
type метакласс, переопределяющий этот оператор:»
А зачем такое извращение? вызов на классе () это банальный вызов конструктора у type. Поэтому если уж вы наследуетесь от type переопределять нужно имхо __init__, а никак не __call__.
Другое дело что в качестве __metaclass__ совершенно необязательно присваивать наследника type, а можно использовать любую callable штуку. Так что если бы у вас был например class AttributeInitType(object): ваша конструкция имела бы смысл.
Или я ошибаюсь?
Ошибаетесь. Не проверял на Python 3.0, но на моем Python 2.6.2 получится вот так:
Почему же переопределялся __call__? потому что __init__ вызывается при определении класса, а не появлении объекта. Только что все проверил лично.
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__ вызывается при определении класса, а не появлении объекта. Только что все проверил лично.
Не знаю, насколько в тему. Вот хорошая статья о системе типов в Python (англ.): www.cafepy.com/article/python_types_and_objects/
Очень круто! Спасибо за статью. И раз уж Вы здесь и знакомы с Django, как достигается цепочка параметров при запросе — Model.objects.filter().order_by().values() и т.д.?
Имеем, например:
Можем, например, сделать так:
Так вот. objects — ссылка на экземпляр класса Manager. В этом классе собрана вся
логика работы с моделью, стандартная либо расширенная для данной конкретной модели. По
любому из его методов вроде all() или get() возращается объект QuerySet,
инкапсулирующий в себе информацию об отложенных запросах.
Методы all() и get_query_set() из класса Manager:
Теперь заглянем в определение класса QuerySet():
Что делают эти методы? Они создают копию последнего QuerySet, дописывают в атрибут
self.query новую, условного говоря, строчку, и возращают после этого копию.
Легко получается Apple.object.all().filter(color=«green»).get(). На каждом шаге,
в каждой точке, происходит копирование «наброска» запроса с расширением и
возвратом копии.
Так, кстати, и получаются эти самые «ленивые», «отложенные» вычисления. Запросы
к базе, скажем так, записываются в строчку, а выполняются уже одним пакетом либо
в уже шаблоне, либо по желанию программиста — преобразованием к обычному списку
объектов.
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?
>По умолчанию этот класс (метакласс) называется type.
Задам дурацкий вопрос, а какой метакласс у type?
<a href«www.cafepy.com/article/python_types_and_objects/»> ссылочка по теме, где очень внятно нарисовано и объяснено. Тут выше ее, кажется, уже приводили.
Оттуда или прямо из интерпретатора видим, что,
И еще экспериментрируем:
Ну так вот. type является «конкретизацией», или экземпляром, класса type(как бы рекурсивно) и наследует от object.
В то же время класс object является экземпляром класса type, но не наследует ни от чего.
Главное здесь:
1) Все, и классы, и метаклассы — наследуют от object. Значит, они являются первоклассными объектами.
2) Поведение класса в моменты объявления, вызова и наследования редактируется в метаклассе.
3) Поведение объектов редактируется в классе.
Оттуда или прямо из интерпретатора видим, что,
>>>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...» То есть можно будет проверять, правильные ли типы подсунуты на вход функции. Пример с того же дока:
Метаклассы — тонкий инструмент, использовать в большей части случаев не обязательно, даже избыточно. Но иной раз он позволяет изящно и красиво решать уникальные задачи. Типа DSL склепать в стиле джанговских моделей.
Очень хорошо представляю, как вы начнете всю эту логику наследования моделей, передачи атрибута _meta, учет абстрактных базовых классов решать в функциональном стиле (ModelClass = type (...))! Будет и больше, и мутнее, и некрасиво.
Дело, конечно, хозяйское, Питон дает несколько путей решения этих проблем. Насколько я понимаю слово pythonic — лучшее решение то, которое легче понять и прочитать.
Приятно, конечно, что прототипы позволяют избавиться от лишнего слоя абстракций в виде классов и, тем более, метаклассов. Более того, эти сущности при желании можно моделировать средствами 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, средняя задача на этом языке гораздо меньше. Но интересно было бы выработать какой-нибудь специфичный для прототипной схемы наследования подход в архитектуре.
Я пока не видел никаких особых руководств на эту тему… Надо поискать да подумать.
У меня до сих пор не стояла задача масштабного проектирования на 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. Т.е. логически все сделано. Но без абстракции доступа (синтаксического сахара, со стороны языка, если хотите).
В Javascript можно реализовать подобные правила, и описать их внутри методов. Но тогда и «снаружи» они будут выглядить как вызовы методов. Мне попадалось, например, такое: value = obj.findName('property').get(). Внутри findName() реализован механизм ресолвинга свойства по иерархии классов, а при помощи get() абстрагирован доступ к value. Т.е. логически все сделано. Но без абстракции доступа (синтаксического сахара, со стороны языка, если хотите).
Логика диспетчеризации запроса для получения свойства с использованием прототипа, примитивна до безобразия:
— Эй, Объект, гони сюда свойство property!
— Хм… а нету… (если есть, был обязан выдать)
— Тогда пусть вернет тот, кто тебя породил, такого урода! (объект бежит просить в словаре у своего конструктора)
Если и у конструктора такого свойства нет, то обычно, на этом все и заканчивается, ибо конструктор конструктора уже не при делах: «вассал моего вассала не мой вассал». Это все, что доступно в JS.
Диспетчеризация запроса по иерархии классов отражает совсем другое отношение: конкретное-общее (класс -> супер-класс) — и логика диспетчеризации запросов тут совершенно иная. Единственное что их роднит с «объект-конструктор», это транзитивное отношение «instanceOf», которое может означать и «объект типа», и «объект класса» (см. диаграмму www.cafepy.com/article/python_types_and_objects/images/relationships_transitivity.png). Но из-за этого «двойного» смысла и возникает путаница, будто одно можно выразить через другое. Это не так (не даром, на диаграмме стрелки выглядят по разному).
— Эй, Объект, гони сюда свойство property!
— Хм… а нету… (если есть, был обязан выдать)
— Тогда пусть вернет тот, кто тебя породил, такого урода! (объект бежит просить в словаре у своего конструктора)
Если и у конструктора такого свойства нет, то обычно, на этом все и заканчивается, ибо конструктор конструктора уже не при делах: «вассал моего вассала не мой вассал». Это все, что доступно в JS.
Диспетчеризация запроса по иерархии классов отражает совсем другое отношение: конкретное-общее (класс -> супер-класс) — и логика диспетчеризации запросов тут совершенно иная. Единственное что их роднит с «объект-конструктор», это транзитивное отношение «instanceOf», которое может означать и «объект типа», и «объект класса» (см. диаграмму www.cafepy.com/article/python_types_and_objects/images/relationships_transitivity.png). Но из-за этого «двойного» смысла и возникает путаница, будто одно можно выразить через другое. Это не так (не даром, на диаграмме стрелки выглядят по разному).
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Использование метаклассов в Python