Класс с аргументами как аргумент в функции
Всем привет! Есть одна функция которая принимает класс как аргумент, но у этого класса нельзя вызвать аргументы самого класса, а если я вызову класс вместе с аргументами, произойдёт ошибка TypeError: ‘SubProcessor’ object is not callable , код:
Передавать не обязательно именно класс. Достаточно передать что-то, что сконструирует его объект, например функцию. Только не забудьте проинициализировать базовый класс обработчика.
Также можно объявить класс прямо в методе
я не знаю как у вас вызываются методы get , post , serve , поэтому я их вызвал в лоб. Попробуйте:
Всё ещё ищете ответ? Посмотрите другие вопросы с метками python ооп функции классы аргументы или задайте свой вопрос.
Site design / logo © 2022 Stack Exchange Inc; user contributions licensed under cc by-sa. rev 2022.6.10.42345
Нажимая «Принять все файлы cookie», вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.
Метаклассы в Python
Как сказал один из пользователей StackOverflow, «using SO is like doing lookups with a hashtable instead of a linked list». Мы снова обращаемся к этому замечательному ресурсу, на котором попадаются чрезвычайно подробные и понятные ответы на самые различные вопросы.
В этот раз мы обсудим, что такое метаклассы, как, где и зачем их использовать, а также почему обычно этого делать не стоит.
Классы как объекты
Перед тем, как изучать метаклассы, надо хорошо разобраться с классами, а классы в Питоне — вещь весьма специфическая (основаны на идеях из языка Smalltalk).
В большинстве языков класс это просто кусок кода, описывающий, как создать объект. В целом это верно и для Питона:
Но в Питоне класс это нечто большее — классы также являются объектами.
Как только используется ключевое слово class , Питон исполняет команду и создаёт объект. Инструкция
создаст в памяти объект с именем ObjectCreator .
Этот объект (класс) сам может создавать объекты (экземпляры), поэтому он и является классом.
- его можно присвоить переменной,
- его можно скопировать,
- можно добавить к нему атрибут,
- его можно передать функции в качестве аргумента,
Динамическое создание классов
Так как классы являются объектами, их можно создавать на ходу, как и любой объект.
Например, можно создать класс в функции, используя ключевое слово class :
Однако это не очень-то динамично, поскольку по-прежнему нужно самому писать весь класс целиком.
Поскольку классы являются объектами, они должны генерироваться чем-нибудь.
Когда используется ключевое слово class , Питон создаёт этот объект автоматически. Но как и большинство вещей в Питоне, есть способ сделать это вручную.
Помните функцию type ? Старая-добрая функция, которая позволяет определить тип объекта:
На самом деле, у функции type есть совершенно иное применение: она также может создавать классы на ходу. type принимает на вход описание класса и созвращает класс.
(Я знаю, это по-дурацки, что одна и та же функция может использоваться для двух совершенно разных вещей в зависимости от передаваемых аргументов. Так сделано для обратной совместимости)
type работает следующим образом:
может быть создан вручную следующим образом:
Возможно, вы заметили, что мы используем «MyShinyClass» и как имя класса, и как имя для переменной, содержащей ссылку на класс. Они могут быть различны, но зачем усложнять?
type принимает словарь, определяющий атрибуты класса:
можно переписать как
и использовать как обычный класс
Конечно, можно от него наследовать:
В какой-то момент вам захочется добавить методов вашему классу. Для этого просто определите функцию с нужной сигнатурой и присвойте её в качестве атрибута:
Уже понятно, к чему я клоню: в Питоне классы являются объектами и можно создавать классы на ходу.
Это именно то, что Питон делает, когда используется ключевое слово class , и делает он это с помощью метаклассов.
Что такое метакласс (наконец)
Метакласс это «штука», которая создаёт классы.
Мы создаём класс для того, чтобы создавать объекты, так? А классы являются объектами. Метакласс это то, что создаёт эти самые объекты. Они являются классами классов, можно представить это себе следующим образом:
Мы уже видели, что type позволяет делать что-то в таком духе:
Это потому что функция type на самом деле является метаклассом. type это метакласс, который Питон внутренне использует для создания всех классов.
Естественный вопрос: с чего это он его имя пишется в нижнем регистре, а не Type ?
Я полагаю, это просто для соответствия str , классу для создания объектов-строк, и int , классу для создания объектов-целых чисел. type это просто класс для создания объектов-классов.
Это легко проверить с помощью атрибута __class__ :
В питоне всё (вообще всё!) является объектами. В том числе числа, строки, функции и классы — они все являются объектами и все были созданы из класса:
А какой же __class__ у каждого __class__ ?
Итак, метакласс это просто штука, создающая объекты-классы.
Если хотите, можно называть его «фабрикой классов»
type это встроенный метакласс, который использует Питон, но вы, конечно, можете создать свой.
Атрибут __metaclass__
При написании класса можно добавить атрибут __metaclass__ :
В таком случае Питон будет использовать указанный метакласс при создании класса Foo .
Осторожно, тут есть тонкость!
Хоть вы и пишете class Foo(object) , объект-класс пока ещё не создаётся в памяти.
Питон будет искать __metaclass__ в определении класса. Если он его найдёт, то использует для создания класса Foo . Если же нет, то будет использовать type .
То есть когда вы пишете
Питон делает следующее:
Есть ли у класса Foo атрибут __metaclass__ ?
Если да, создаёт в памяти объект-класс с именем Foo , используя то, что указано в __metaclass__ .
Если Питон не находит __metaclass__ , он ищет __metaclass__ в родительском классе Bar и попробует сделать то же самое.
Если же __metaclass__ не находится ни в одном из родителей, Питон будет искать __metaclass__ на уровне модуля.
И если он не может найти вообще ни одного __metaclass__ , он использует type для создания объекта-класса.
Теперь важный вопрос: что можно положить в __metaclass__ ?
Ответ: что-нибудь, что может создавать классы.
А что создаёт классы? type или любой его подкласс, а также всё, что использует их.
Пользовательские метаклассы
Основная цель метаклассов — автоматически изменять класс в момент создания.
Обычно это делает для API, когда хочется создавать классы в соответсвии с текущим контекстом.
Представим глупый пример: вы решили, что у всех классов в вашем модуле имена атрибутов должны быть записать в верхнем регистре. Есть несколько способов это сделать, но один из них — задать __metaclass__ на уровне модуля.
В таком случае все классы этого модуля будут создаваться с использованием указанного меакласса, а нам остаётся только заставить метакласса переводить имена всех атрибутов в верхний регистр.
К счастью, __metaclass__ может быть любым вызываемым объектом, не обязательно формальным классом (я знаю, что-то со словом «класс» в названии не обязано быть классом, что за ерунда? Однако это полезно).
Так что мы начнём с простого примера, используя функцию.
А теперь то же самое, только используя настояший класс:
Но это не совсем ООП. Мы напрямую вызываем type и не перегружаем вызов __new__ родителя. Давайте сделаем это:
Вы, возможно, заметили дополнительный аргумент upperattr_metaclass . Ничего особого в нём нет: метод всегда получает первым аргументом текущий экземпляр. Точно так же, как вы используете self в обычным методах.
Конечно, имена, которые я тут использовал, такие длинные для ясности, но как и self , есть соглашение об именовании всех этих аргументов. Так что реальный метакласс выгляит как-нибудь так:
Можно сделать даже лучше, использовав super , который вызовет наследование (поскольку, конечно, можно создать метакласс, унаследованный от метакласса, унаследованного от type ):
Вот и всё. О метаклассах больше ничего и не сказать.
Причина сложности кода, использующего метаклассы, не в самих метаклассах. Она в том, что обычно метаклассы используются для всяких изощрённых вещей, основанных на интроспекции, манипуляцией наследованием, переменными вроде __dict__ и тому подобном.
- перехватить создание класса
- изменить класс
- вернуть модифицированный
Зачем использовать метаклассы вместо функций?
Поскольку __metaclass__ принимает любой вызываемый объект, с чего бы вдруг использовать класс, если это очевидно сложнее?
- Назначение яснее. Когда вы видите UpperAttrMetaclass(type) , вы сразу знаете, что дальше будет.
- Можно использовать ООП. Метаклассы могту наследоваться от метаклассов, перегружая родитальские методы.
- Лучше структурированный код. Вы не будете использовать метаклассы для таких простых вещей, как в примере выше. Обычно это что-то сложное. Возможность создать несколько методов и сгруппировать их в одном классе очень полезна, чтобы сделать код более удобным для чтения.
- Можно использовать __new__ , __init__ и __call__ . Конечно, обычно можно всё сделать в __new__ , но некоторым комфортнее использовать __init__
- Они называются метаклассами, чёрт возьми! Это должно что-то значить!
Зачем вообще использовать метаклассы?
Наконец, главный вопрос. С чего кому-то использовать какую-то непонятную (и способствующую ошибкам) фичу?
Ну, обычно и не надо использовать:
Гуру Питона Тим Питерс
Основное применение метаклассов это создание API. Типичный пример — Django ORM.
Она позволяет написать что-то в таком духе:
Однако если вы выполните следующий код:
вы получите не IntegerField , а int , причём значение может быть получено прямо из базы данных.
Это возможно, потому что models.Model определяет __metaclass__ , который сотворит некую магию и превратит класс Person , который мы только что определили простым выражением в сложную привязку к базе данных.
Django делает что-то сложное выглядящим простым, выставляя наружу простое API и используя метаклассы, воссоздающие код из API и незаметно делающие всю работу.
Напоследок
ВО-первых, вы узнали, что классы это объекты, которые могут создавать экземпляры.
На самом деле, классы это тоже экземпляры. Экземпляры метаклассов.
Всё что угодно является объектом в Питоне: экземпляром класса или экземпляром метакласса.
type является собственным метаклассом. Это нельзя воспроизвести на чистом Питоне и делается небольшим читерством на уровне реализации.
Класс и объект в Python
Python — это процедурно-ориентированный и одновременно объектно-ориентированный язык программирования.
Процедурно-ориентированный
«Процедурно-ориентированный» подразумевает наличие функций. Программист может создавать функции, которые затем используются в сторонних скриптах.
Объектно-ориентированный
«Объектно-ориентированный» подразумевает наличие классов. Есть возможность создавать классы, представляющие собой прототипы для будущих объектов.
Создание класса в Python
Синтаксис для написания нового класса:
- Для создания класса пишется ключевое слово class , его имя и двоеточие (:). Первая строчка в теле класса описывает его. (По желанию) получить доступ к этой строке можно с помощью ClassName.__doc__
- В теле класса допускается объявление атрибутов, методов и конструктора.
Атрибут:
Атрибут — это элемент класса. Например, у прямоугольника таких 2: ширина ( width ) и высота ( height ).
Метод:
- Метод класса напоминает классическую функцию, но на самом деле — это функция класса. Для использования ее необходимо вызывать через объект.
- Первый параметр метода всегда self (ключевое слово, которое ссылается на сам класс).
Конструктор:
- Конструктор — уникальный метод класса, который называется __init__ .
- Первый параметр конструктора во всех случаях self (ключевое слово, которое ссылается на сам класс).
- Конструктор нужен для создания объекта.
- Конструктор передает значения аргументов свойствам создаваемого объекта.
- В одном классе всегда только один конструктор.
- Если класс определяется не конструктором, Python предположит, что он наследует конструктор родительского класса.
Создание объекта с помощью класса Rectangle:
Что происходит при создании объекта с помощью класса?
При создании объекта класса Rectangle запускается конструктор выбранного класса, и атрибутам нового объекта передаются значения аргументов. Как на этом изображении:
Конструктор с аргументами по умолчанию
В других языках программирования конструкторов может быть несколько. В Python — только один. Но этот язык разрешает задавать значение по умолчанию.
Все требуемые аргументы нужно указывать до аргументов со значениями по умолчанию.
Сравнение объектов
В Python объект, созданный с помощью конструктора, занимает реальное место в памяти. Это значит, что у него есть точный адрес.
Если объект AA — это просто ссылка на объект BB , то он не будет сущностью, занимающей отдельную ячейку памяти. Вместо этого он лишь ссылается на местоположение BB .
Оператор == нужен, чтобы узнать, ссылаются ли два объекта на одно и то же место в памяти. Он вернет True , если это так. Оператор != вернет True , если сравнить 2 объекта, которые ссылаются на разные места в памяти.
Атрибуты
В Python есть два похожих понятия, которые на самом деле отличаются:
- Атрибуты
- Переменные класса
Стоит разобрать на практике:
Атрибут
Объекты, созданные одним и тем же классом, будут занимать разные места в памяти, а их атрибуты с «одинаковыми именами» — ссылаться на разные адреса. Например:
Python умеет создавать новые атрибуты для уже существующих объектов. Например, объект player1 и новый атрибут address .
Встроенные атрибуты класса
Объекты класса — дочерние элементы по отношению к атрибутам самого языка Python. Таким образом они заимствуют некоторые атрибуты:
Атрибут | Описание |
---|---|
__dict__ | Предоставляет данные о классе коротко и доступно, в виде словаря |
__doc__ | Возвращает строку с описанием класса, или None , если значение не определено |
__class__ | Возвращает объект, содержащий информацию о классе с массой полезных атрибутов, включая атрибут __name__ |
__module__ | Возвращает имя «модуля» класса или __main__ , если класс определен в выполняемом модуле. |
Переменные класса
Переменные класса в Python — это то же самое, что Field в других языках, таких как Java или С#. Получить к ним доступ можно только с помощью имени класса или объекта.
Для получения доступа к переменной класса лучше все-таки использовать имя класса, а не объект. Это поможет не путать «переменную класса» и атрибуты.
У каждой переменной класса есть свой адрес в памяти. И он доступен всем объектам класса.
Составляющие класса или объекта
В Python присутствует функция dir , которая выводит список всех методов, атрибутов и переменных класса или объекта.