Метапрограммирование Python: контролируйте все, что хотите

задняя часть Python программист

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

Подзаголовок — контролировать все, что вы хотите контролировать, на самом деле эта статья посвящена одному — использованию функций, которые Python предоставляет нам, чтобы сделать код как можно более элегантным и лаконичным. В частности, с помощью методов программирования характеристики уровня абстракции изменяются на более высоком уровне абстракции.

Во-первых, все в Python — это объект, клише. Кроме того, Python предоставляет множество механизмов «метапрограммирования», таких как специальные методы, метаклассы и т. д. Такие вещи, как динамическое добавление атрибутов к объектам, вовсе не являются «метапрограммированием» в Python, но являются сложными вещами в некоторых статических языках. Давайте поговорим о том, что также легко сбивает с толку программистов на Python.

Давайте сначала разделим объекты на слои.Обычно мы знаем, что объект имеет свой тип.Питон уже давно реализовал типы как объекты. Таким образом, у нас есть объекты экземпляра и объекты класса. Это два уровня. Читатели с небольшими базовыми знаниями будут знать, что существуют также метаклассы, Короче говоря, метаклассы — это «классы» «классов», то есть вещи выше классов. Это другой уровень. Больше?

ImportTime vs RunTime

Если мы возьмем другой угол, нам не нужно использовать те же критерии, что и на предыдущих трех уровнях. Выделим две вещи: ImportTime и RunTime, между ними нет четкой границы, как следует из названия, это два момента, import time и runtime.

Что происходит, когда модуль импортируется? Выполняются операторы в глобальной области видимости (неопределяющие операторы). А определения функций? Объект-функция создается, но код в нем не выполняется. Как насчет определений классов? Создается объект класса, выполняется код в домене определения класса, а код в методе класса, естественно, не выполняется.

А время выполнения? Код в функциях и методах выполняется. Конечно, вы должны позвонить им в первую очередь.

метакласс

Так что можно сказать, что метаклассы и классы принадлежат ImportTime, после импорта модуля они будут созданы. Объект экземпляра принадлежит RunTime, и одиночный импорт не создаст объект экземпляра. Но слова не могут быть слишком абсолютными, потому что, если вы создаете экземпляр класса в области модуля, экземпляр объекта также будет создан. Просто мы обычно пишем их в функциях, поэтому они так и разделены.

Что делать, если вы хотите управлять свойствами результирующего экземпляра объекта? Слишком просто, переопределите метод __init__ в определении класса. Итак, мы хотим управлять некоторыми свойствами класса? Есть ли такая необходимость? Есть конечно!

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

Самый простой способ сделать это так

class _Spam:
    def __init__(self):
        print("Spam!!!")

_spam_singleton =None

def Spam():
    global _spam_singleton
    if _spam_singleton is not None:
        return _spam_singleton
    else:
        _spam_singleton = _Spam()
        return _spam_singleton

Заводской узор, менее изящный. Давайте еще раз взглянем на требования: может быть только один экземпляр класса. Методы, которые мы определяем в классе, — это поведение экземпляра объекта, поэтому, если мы хотим изменить поведение класса, нам нужно что-то на более высоком уровне. Вполне уместно, что в это время появляются метаклассы. Как упоминалось ранее, метакласс — это класс классов. То есть метод __init__ метакласса является методом инициализации класса. Мы знаем, что есть еще вещь __call__, которая позволяет вызывать экземпляр как функцию, поэтому этот метод метакласса — это метод, который класс вызывает при создании экземпляра.

Код можно записать:

class Singleton(type):
    def __init__(self, *args, **kwargs):
        self._instance = None
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if self._instance is None:
            self._instance = super().__call__(*args, **kwargs)
            return self._instance
        else:
            return self._instance


class Spam(metaclass=Singleton):
    def __init__(self):
        print("Spam!!!")

Есть два основных отличия от общих определений классов: первое состоит в том, что базовым классом Singleton является тип, а второе состоит в том, что существует метакласс=Singleton, в котором определен Spam. что такое тип? Это подкласс объекта, а объект является его экземпляром. Другими словами, тип — это класс всех классов, то есть самый базовый метакласс, который определяет некоторые операции, которые необходимы всем классам при их создании. Таким образом, наш пользовательский метакласс должен быть подклассом. В то же время тип также является объектом, поэтому он является подклассом объекта. Это немного сложно понять, но вы, вероятно, знаете это.

декоратор

Давайте снова поговорим о декораторах. Большинство людей считают декораторы одной из самых сложных для понимания концепций Python. На самом деле это просто синтаксический сахар, после понимания того, что функции тоже объекты. Вы можете легко написать свой собственный декоратор.

from functools import wraps

def print_result(func):

    @wraps(func)
    def wrappper(*args, **kwargs):
        result = func(*args, **kwargs)
        print(result)
        return result

    return wrappper

@print_result
def add(x, y):
    return x + y
#相当于:
#add = print_result(add)

add(1, 3)


Здесь мы также используем декоратор @wraps, который используется для того, чтобы внутренняя оболочка функции, которую мы возвращаем, имела ту же сигнатуру функции, что и исходная функция.По сути, мы должны добавить ее при написании декоратора.

Я написал в комментариях, что форма @decorator эквивалентна func=decorator(func), понимая это, мы можем писать больше видов декораторов. Например, декораторы классов и написание декоратора как класса.

def attr_upper(cls):
    for attrname,value in cls.__dict__.items():
        if isinstance(value,str):
            if not value.startswith('__'):
                setattr(cls,attrname,bytes.decode(str.encode(value).upper()))
    return cls    

@attr_upper
class Person:
    sex = 'man'

print(Person.sex) # MAN

Обратите внимание на разницу между реализацией обычных декораторов и декораторов класса.

Абстракция данных — дескриптор

Если мы хотим, чтобы некоторые классы имели одни и те же функции или чтобы управлять ими в определении класса, мы можем настроить метакласс и сделать его метаклассом этих классов. Если мы хотим, чтобы некоторые функции имели одинаковую функциональность без необходимости копировать и вставлять код, мы можем определить декоратор. Итак, что, если мы хотим, чтобы свойства экземпляра имели определенные общие характеристики? Некоторые люди могут сказать, что имущество можно использовать, конечно. Но эта логика должна быть написана каждый раз, когда класс определяется. Если мы хотим, чтобы некоторые свойства экземпляров этих классов имели одинаковые характеристики, мы можем настроить класс дескриптора.

Что касается дескрипторов, эта статьяdocs.Python.org/3/как/…Это хороший доклад, и он также объясняет, как за функциями скрываются дескрипторы для достижения единства и различия функций и методов. Здесь мы приводим несколько примеров.

class TypedField:
    def __init__(self, _type):
        self._type = _type

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return getattr(instance, self.name)

    def __set_name__(self, cls, name):
        self.name = name

    def __set__(self, instance, value):
        if not isinstance(value, self._type):
            raise TypeError('Expected' + str(self._type))
        instance.__dict__[self.name] = value

class Person:
    age = TypedField(int)
    name = TypedField(str)

    def __init__(self, age, name):
        self.age = age
        self.name = name

jack = Person(15, 'Jack')
jack.age = '15'  # 会报错

В этом несколько ролей, TypedField — это класс дескриптора, а атрибут Person — это экземпляр класса дескриптора.Похоже, что дескриптор существует как Person, то есть атрибут класса, а не атрибут экземпляра. Но на самом деле дескриптор будет работать, как только экземпляр Person получит доступ к одноименному свойству. Следует отметить, что в Python 3.5 и более ранних версиях нет специального метода __set_name__, а это значит, что если вы хотите узнать, какое имя дается дескриптору в определении класса, вам нужно явно передать его при создании экземпляра дескриптора, то есть требуется еще один параметр. Однако в Python 3.6 эта проблема решена, просто перепишите метод __set_name__ в определении класса дескриптора. Следует также отметить, что метод записи __get__ в основном необходим для оценки экземпляра, иначе будет сообщено об ошибке. Причину понять нетрудно, поэтому не буду вдаваться в подробности.

Управление созданием подкласса — замена методов метакласса

В Python 3.6 мы можем настроить создание подклассов, реализуя специальный метод __init_subclass__, чтобы в некоторых случаях избавиться от неприятных вещей метаклассов.

class PluginBase:
    subclasses = []

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.subclasses.append(cls)

class Plugin1(PluginBase):
    pass

class Plugin2(PluginBase):
    pass


резюме

Метапрограммирование, такое как метаклассы, малопонятно большинству людей, и большую часть времени они вам не нужны. Но реализации большинства фреймворков используют эти приемы, чтобы код, написанный пользователем, был кратким и простым для понимания. Если вы хотите узнать больше об этих методах, вы можете обратиться к некоторым книгам, таким как «Fluent Питон", "Питон" Cookbook» (к ним относится часть содержания этой статьи), или см. некоторые главы в официальной документации, такие как дескриптор HowTo, упомянутый выше, и Data Раздел модели и так далее. Или посмотрите непосредственно на исходный код Python, включая исходный код, написанный на Python и CPython.

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