Два предложения, чтобы легко понять самую сложную точку знания Python — метакласс

задняя часть Python рептилия Django

Два предложения, чтобы освоить самый сложный пункт знаний о python — метакласс

Не пугайтесь заявлений о том, что «метаклассы — это функция, которую не будут использовать 99% программистов на Python». так какКаждый китаец является естественным пользователем метакласса

Чтобы узнать о метаклассах, вам нужно знать всего два предложения:

  • Дао рождает одно, одно рождает двоих, двое рождают троих, а трое рождают все вещи.
  • кто я? откуда я? Куда я иду?

В мире питонов существует вечное Дао, которое есть «тип». Пожалуйста, имейте в виду, что тип — это Дао. Такая обширная экосистема python создается по типам.

Дао производит одно, одно производит два, два производят три, а три производят все вещи.

  1. ДорогаI.e. Тип
  2. одинЭто метакласс (метакласс или генератор классов)
  3. дваТо есть класс (класс или генератор экземпляров)
  4. триЭто экземпляр (экземпляр)
  5. всеТо есть различные атрибуты и методы экземпляра, которые вызываются, когда мы обычно используем python.

Дао и один — это предложения, которые мы сегодня обсудим, а два, три и все остальное — это классы, экземпляры, свойства и методы, которые мы часто используем.В качестве примера возьмем hello world:

# 创建一个Hello类,拥有属性say_hello ----二的起源
class Hello():
    def say_hello(self, name='world'):
        print('Hello, %s.' % name)


# 从Hello类创建一个实例hello ----二生三
hello = Hello()

# 使用hello调用方法say_hello ----三生万物
hello.say_hello()

Выходной эффект:

Hello, world.

Это стандартный процесс «два порождает три, три порождают все».От класса к методам, которые мы можем вызывать, мы используем эти два шага.

Поэтому мы не можем не спросить, откуда взялся этот класс? Вернитесь к первой строке кода. класс Hello на самом деле является «семантической аббревиатурой» для функции, просто чтобы упростить понимание кода.Другой способ написания:

def fn(self, name='world'): # 假如我们有一个函数叫fn
    print('Hello, %s.' % name)
    
Hello = type('Hello', (object,), dict(say_hello=fn)) # 通过type创建Hello class ---- 神秘的“道”,可以点化一切,这次我们直接从“道”生出了“二”

Этот способ написания точно такой же, как и предыдущий способ написания Class Hello, вы можете попробовать создать экземпляр и вызвать

# 从Hello类创建一个实例hello ----二生三,完全一样
hello = Hello()

# 使用hello调用方法say_hello ----三生万物,完全一样
hello.say_hello()

Выходной эффект:

Hello, world. ----调用结果完全一样。

Мы оглядываемся на самые чудесные места,Тао сразу родила двоих:

Hello = type('Hello', (object,), dict(say_hello=fn))

Это «Дао», источник мира питонов, и вы можете восхищаться им. Обратите внимание на три его параметра! Оно совпадает с тремя вечными суждениями человека: кто я, откуда я пришел и куда я иду.

  • Первый параметр: кто я. Здесь мне нужно имя, отличное от всего остального, в приведенном выше примере меня назвали «Привет».
  • Второй параметр: откуда я родом
    Здесь мне нужно знать, откуда он взялся, какой у меня «родительский класс», который в приведенном выше примере является «объектом» — очень рудиментарным классом в python.
  • Третий параметр: куда я хочу пойти
    Здесь мы включаем вызываемые методы и свойства в словарь и передаем их в качестве параметров. В приведенном выше примере у нас есть метод say_hello, заключенный в словарь.

Стоит отметить, что тремя вечными утверждениями обладают все классы, все экземпляры и даже все атрибуты и методы экземпляров. Само собой разумеется, что их «создатели», Дао и Единое, а именно тип и метакласс, также имеют эти три параметра. Но обычно три вечных суждения класса передаются не как параметры, а следующим образом

class Hello(object){
# class 后声明“我是谁”
# 小括号内声明“我来自哪里”
# 中括号内声明“我要到哪里去”
    def say_hello(){
        
    }
}
  • Творец, может непосредственно создать одного человека, но это каторга. Создатель сначала создаст вид «человек», а затем создаст отдельных особей партиями. И три вечных предложения были переданы.
  • «Дао» может напрямую производить «два», но сначала он будет производить «один», а затем производить «два» партиями.
  • Type может генерировать классы напрямую, но вы также можете сначала генерировать метаклассы, а затем использовать метаклассы для пакетной настройки классов.

Метаклассы - Дао рождает одного, жизнь двоих

Обычно имена метаклассов имеют суффикс Metalass. Представьте, что нам нужен метакласс, который может автоматически здороваться, методы класса в нем иногда требуют say_Hello, иногда say_Hi, иногда say_Sayolala, а иногда say_Nihao.

Какая ужасная рутина, если каждый встроенный say_xxx должен быть объявлен один раз внутри класса! лучше использоватьметаклассрешить проблему.

Ниже приведен код категории, который создает специальное «приветствие»:

class SayMetaClass(type):

    def __new__(cls, name, bases, attrs):
        attrs['say_'+name] = lambda self,value,saying=name: print(saying+','+value+'!')
        return type.__new__(cls, name, bases, attrs)

Помните две вещи:
1. Метакласс является производным от «типа», поэтому родительский класс должен передать тип. 【Дао производит одно, поэтому одно должно содержать Дао

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

В __new__, у меня только операция, то есть

attrs['say_'+name] = lambda self,value,saying=name: print(saying+','+value+'!')

Он создает метод класса с именем класса. Например, класс, который мы создали из метакласса, называется «Hello», затем при его создании автоматически создается метод класса «say_Hello», а затем имя класса «Hello» используется в качестве параметра по умолчанию и передается в метод. Затем передайте параметр, переданный при вызове метода hello, в качестве значения и, наконец, распечатайте его.

Итак, как метакласс проходит путь от создания до вызова? Приходить! Давайте вместе войдем в жизненный цикл метакласса в соответствии с принципами даосизма, одна жизнь, две, две, три и три.

# 道生一:传入type
class SayMetaClass(type):

    # 传入三大永恒命题:类名称、父类、属性
    def __new__(cls, name, bases, attrs):
        # 创造“天赋”
        attrs['say_'+name] = lambda self,value,saying=name: print(saying+','+value+'!')
        # 传承三大永恒命题:类名称、父类、属性
        return type.__new__(cls, name, bases, attrs)

# 一生二:创建类
class Hello(object, metaclass=SayMetaClass):
    pass

# 二生三:创建实列
hello = Hello()

# 三生万物:调用实例方法
hello.say_Hello('world!')

Выход

Hello, world!

Примечание. Для классов, созданных с помощью метаклассов, первым параметром является родительский класс, а вторым параметром — метакласс.

Обычные люди не умеют говорить при рождении, но некоторые люди будут говорить «привет», «привет», «сайолала», когда родятся, в этом сила таланта. Это избавит нас от бесчисленных проблем в объектно-ориентированном программировании.

Теперь, сохранив метакласс без изменений, мы также можем продолжить создание класса Sayolala, Nihao следующим образом:

# 一生二:创建类
class Sayolala(object, metaclass=SayMetaClass):
    pass

# 二生三:创建实列
s = Sayolala()

# 三生万物:调用实例方法
s.say_Sayolala('japan!')

вывод

Sayolala, japan!

также может говорить по-китайски

# 一生二:创建类
class Nihao(object, metaclass=SayMetaClass):
    pass

# 二生三:创建实列
n = Nihao()

# 三生万物:调用实例方法
n.say_Nihao('中华!')

вывод

Nihao, 中华!

Еще небольшой пример:

# 道生一
class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        # 天赋:通过add方法将值绑定
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)
        
# 一生二
class MyList(list, metaclass=ListMetaclass):
    pass
    
# 二生三
L = MyList()

# 三生万物
L.add(1)

Теперь напечатаем L

print(L)

>>> [1]

Обычный список не имеет метода add()

L2 = list()
L2.add(1)

>>>AttributeError: 'list' object has no attribute 'add'

чудесный! Узнав об этом, испытал ли ты радость Творца?

Все в мире питонов у вас под рукой.

Молодой творец, пожалуйста, присоединяйтесь ко мне в создании нового мира.

Мы выбираем две области, одна из которых является основной идеей Django, «Object Relational Mapping», то есть объектно-реляционное сопоставление, или сокращенно ORM.

Это одна из самых сложных частей Django, но после изучения метаклассов все становится ясно. Ваше понимание Django будет поднято на новый уровень!

Еще одно поле — поле краулера (хакерское поле), автоматический поиск доступных прокси в сети, а затем смена IP для прорыва чужих ограничений антикраулера.

Эти два навыка очень полезны и очень веселы!

Задача 1: создать ORM с помощью метаклассов

Подготовка, создайте класс Field

class Field(object):

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

    def __str__(self):
        return '<%s:%s>' % (self.__class__.__name__, self.name)
        

что он делает
Когда создается экземпляр класса Field, он получает два параметра, name и column_type, которые будут связаны как частные свойства поля.Если вы хотите преобразовать поле в строку, оно вернет «Field:XXX», где XXX передается в nameName.

Подготовка: создание StringField и IntergerField

class StringField(Field):

    def __init__(self, name):
        super(StringField, self).__init__(name, 'varchar(100)')

class IntegerField(Field):

    def __init__(self, name):
        super(IntegerField, self).__init__(name, 'bigint')

что он делает
При инициализации экземпляров StringField и IntegerField автоматически вызывается метод инициализации родительского класса.

Дао Шэнъи

class ModelMetaclass(type):

    def __new__(cls, name, bases, attrs):
        if name=='Model':
            return type.__new__(cls, name, bases, attrs)
        print('Found model: %s' % name)
        mappings = dict()
        for k, v in attrs.items():
            if isinstance(v, Field):
                print('Found mapping: %s ==> %s' % (k, v))
                mappings[k] = v
        for k in mappings.keys():
            attrs.pop(k)
        attrs['__mappings__'] = mappings # 保存属性和列的映射关系
        attrs['__table__'] = name # 假设表名和类名一致
        return type.__new__(cls, name, bases, attrs)

Он делает следующие вещи

  1. Создать новое сопоставление словаря
  2. Пройдите пары ключ-значение свойств каждого класса через .items(). Если значением является класс Field, напечатайте ключ и привяжите пару к словарю отображения.
  3. Удалите свойство, которое только что было передано как класс Field.
  4. Создайте специальный атрибут __mappings__ для хранения сопоставлений словаря.
  5. Создайте специальный атрибут __table__, который содержит имя переданного класса.

жизнь вторая

class Model(dict, metaclass=ModelMetaclass):

    def __init__(self, **kwarg):
        super(Model, self).__init__(**kwarg)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError("'Model' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

    # 模拟建表操作
    def save(self):
        fields = []
        args = []
        for k, v in self.__mappings__.items():
            fields.append(v.name)
            args.append(getattr(self, k, None))
        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join([str(i) for i in args]))
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))

Если вы создаете подкласс User из Model:

class User(Model):
    # 定义类的属性到列的映射:
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

В это время id= IntegerField('id') автоматически преобразуется в:

Model.__setattr__(self, 'id', IntegerField('id'))

Поскольку IntergerField('id') является экземпляром подкласса Field, __new__ метакласса запускается автоматически, поэтому сохраните IntergerField('id') в __mappings__ и удалите пару ключ-значение.

Два порождает три, три порождают все

Когда вы инициализируете экземпляр и вызываете метод save()

u = User(id=12345, name='Batman', email='batman@nasa.org', password='iamback')
u.save()

В это время сначала завершается процесс два-три:

  1. Сначала вызовите модель .__ setattr__, объекты, загруженные закрытым ключом.
  2. Затем назовите «талант» метакласс, ModelMetaclass .__ new__, и автоматически хранит частные объекты в модели, пока они представляют собой экземпляры поля, в u .__ mappites__.

Следующим шагом является завершение процесса трех живых существ:

Смоделируйте операцию записи в базу данных с помощью u.save(). Здесь мы просто проходим операцию __mappings__, виртуализируем sql и печатаем его, на самом деле он запускается путем ввода оператора sql и базы данных.

Выход

Found model: User
Found mapping: name ==> <StringField:username>
Found mapping: password ==> <StringField:password>
Found mapping: id ==> <IntegerField:id>
Found mapping: email ==> <StringField:email>
SQL: insert into User (username,password,id,email) values (Batman,iamback,12345,batman@nasa.org)
ARGS: ['Batman', 'iamback', 12345, 'batman@nasa.org']
  • Молодой творец, мы с вами пережили великий процесс эволюции от «Дао» ко «всему вещам», что также является основным принципом раздела «Модель» в Django.
  • Затем, пожалуйста, присоединяйтесь ко мне в более веселой битве краулеров (ну, вы теперь младший хакер): сканировании сетевых агентов!

Challenge 2: скалолазание сетевых агентов

Подготовьте работу, сначала зайдите на страницу, чтобы поиграть

Пожалуйста, убедитесь, что установлены два пакета request и pyquery.

# 文件:get_page.py
import requests

base_headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36',
    'Accept-Encoding': 'gzip, deflate, sdch',
    'Accept-Language': 'zh-CN,zh;q=0.8'
}


def get_page(url):
    headers = dict(base_headers)
    print('Getting', url)
    try:
        r = requests.get(url, headers=headers)
        print('Getting result', url, r.status_code)
        if r.status_code == 200:
            return r.text
    except ConnectionError:
        print('Crawling Failed', url)
        return None

Здесь мы используем пакет запроса для сканирования исходного кода Baidu.

Попробуйте поймать Baidu

Вставьте этот абзац после get_page.py и удалите его.

if(__name__ == '__main__'):
    rs = get_page('https://www.baidu.com')
    print('result:\r\n', rs)
попробуй поймать прокси

Вставьте этот абзац после get_page.py и удалите его.

if(__name__ == '__main__'):
    from pyquery import PyQuery as pq
    start_url = 'http://www.proxy360.cn/Region/China'
    print('Crawling', start_url)
    html = get_page(start_url)
    if html:
        doc = pq(html)
        lines = doc('div[name="list_proxy_ip"]').items()
        for line in lines:
            ip = line.find('.tbBottomLine:nth-child(1)').text()
            port = line.find('.tbBottomLine:nth-child(2)').text()
            print(ip+':'+port)

Далее давайте доберемся до темы: пакетные прокси, используя метаклассы

Пакетная обработка прокси-серверов сканирования

from getpage import get_page
from pyquery import PyQuery as pq


# 道生一:创建抽取代理的metaclass
class ProxyMetaclass(type):
    """
        元类,在FreeProxyGetter类中加入
        __CrawlFunc__和__CrawlFuncCount__
        两个参数,分别表示爬虫函数,和爬虫函数的数量。
    """
    def __new__(cls, name, bases, attrs):
        count = 0
        attrs['__CrawlFunc__'] = []
        attrs['__CrawlName__'] = []
        for k, v in attrs.items():
            if 'crawl_' in k:
                attrs['__CrawlName__'].append(k)
                attrs['__CrawlFunc__'].append(v)
                count += 1
        for k in attrs['__CrawlName__']:
            attrs.pop(k)
        attrs['__CrawlFuncCount__'] = count
        return type.__new__(cls, name, bases, attrs)


# 一生二:创建代理获取类

class ProxyGetter(object, metaclass=ProxyMetaclass):
    def get_raw_proxies(self, site):
        proxies = []
        print('Site', site)
        for func in self.__CrawlFunc__:
            if func.__name__==site:
                this_page_proxies = func(self)
                for proxy in this_page_proxies:
                    print('Getting', proxy, 'from', site)
                    proxies.append(proxy)
        return proxies


    def crawl_daili66(self, page_count=4):
        start_url = 'http://www.66ip.cn/{}.html'
        urls = [start_url.format(page) for page in range(1, page_count + 1)]
        for url in urls:
            print('Crawling', url)
            html = get_page(url)
            if html:
                doc = pq(html)
                trs = doc('.containerbox table tr:gt(0)').items()
                for tr in trs:
                    ip = tr.find('td:nth-child(1)').text()
                    port = tr.find('td:nth-child(2)').text()
                    yield ':'.join([ip, port])

    def crawl_proxy360(self):
        start_url = 'http://www.proxy360.cn/Region/China'
        print('Crawling', start_url)
        html = get_page(start_url)
        if html:
            doc = pq(html)
            lines = doc('div[name="list_proxy_ip"]').items()
            for line in lines:
                ip = line.find('.tbBottomLine:nth-child(1)').text()
                port = line.find('.tbBottomLine:nth-child(2)').text()
                yield ':'.join([ip, port])

    def crawl_goubanjia(self):
        start_url = 'http://www.goubanjia.com/free/gngn/index.shtml'
        html = get_page(start_url)
        if html:
            doc = pq(html)
            tds = doc('td.ip').items()
            for td in tds:
                td.find('p').remove()
                yield td.text().replace(' ', '')


if __name__ == '__main__':
    # 二生三:实例化ProxyGetter
    crawler = ProxyGetter()
    print(crawler.__CrawlName__)
    # 三生万物
    for site_label in range(crawler.__CrawlFuncCount__):
        site = crawler.__CrawlName__[site_label]
        myProxies = crawler.get_raw_proxies(site)
Даошэн 1: В __new__ метакласса выполняются четыре вещи:
  1. Вставьте имена методов класса, начинающиеся с «crawl_», в ProxyGetter.__CrawlName__
  2. Вставьте сам метод класса, начинающийся с «crawl_», в ProxyGetter.__CrawlFunc__
  3. Подсчитайте количество методов класса, начинающихся с «crawl_».

Как насчет этого? Это очень похоже на процесс __mappings__ создания ORM раньше?

Время жизни 2: класс определяет метод захвата элементов страницы с помощью pyquery.

Все прокси, отображаемые на странице, были просканированы с трех бесплатных прокси-сайтов соответственно.

Если вы не знакомы с использованием yield, вы можете проверить:
Учебное пособие по Python от Ляо Сюэфэна: Генераторы

От двух до трех: создайте обходчик экземпляров объектов

немного

Три вещи: пройтись по каждой __CrawlFunc__
  1. На ProxyGetter.__CrawlName__ получите имя URL-адреса, которое можно сканировать.
  2. Метод класса триггера ProxyGetter.get_raw_proxies(сайт)
  3. Перейдите ProxyGetter.__CrawlFunc__, если имя метода и имя URL совпадают, выполните этот метод.
  4. Каждый URL для получения прокси интегрирован в вывод массива.

Так. . . Как с помощью массовых агентов атаковать чужие сайты, получать чужие пароли, рассылать рекламу и регулярно беспокоить клиентов? Эм-м-м! Что ты думаешь! Разберитесь в этом сами! Если вы не можете понять это, пожалуйста, слушайте следующую разбивку!

Молодой творец, инструмент для создания мира уже в ваших руках, пожалуйста, используйте его силу на пределе возможностей!

Помните мантру для размахивания инструментами:

  • Дао рождает одно, одно рождает двоих, двое рождают троих, а трое рождают все вещи.
  • кто я, откуда я, куда я иду