Maison > Article > développement back-end > Que sont les métaclasses Python ? Introduction aux métaclasses Python
Cet article vous explique qu'est-ce qu'une métaclasse Python ? L'introduction aux métaclasses Python a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer, j'espère qu'elle vous sera utile.
Ne vous laissez pas berner par les soi-disant "métaclasses sont une fonctionnalité que 99% des programmeurs Python vont pas utiliser" Une telle rhétorique me fait peur. Parce que Tout chinois est un utilisateur naturel des métaclasses
Pour apprendre les métaclasses, il suffit de connaître deux phrases :
Tao en donne naissance à une , un donne naissance à deux, deux donne naissance à trois, trois donne naissance à toutes choses
Qui suis-je ? D'où je viens ? Où vais-je ? (Le blogueur pense que cela devrait être : Qui suis-je ? D'où est-ce que je viens ? Que puis-je faire ?)
Dans le monde python, il existe un Tao éternel, c'est "type", s'il vous plaît gardez-le à l'esprit, le type est Tao. Un si vaste écosystème python est entièrement généré par type.
Tao en génère un, un en génère deux, deux en génère trois et trois en génère toutes choses.
est de type
- est une métaclasse (métaclasse, ou appelé générateur de classe)
II est la classe (classe ou générateur d'instance)
Trois est l'instance (instance )
Tout sont les différents attributs et méthodes de l'instance Lorsque nous utilisons habituellement python, nous les appelons.
Tao et un sont les propositions dont nous discutons aujourd'hui, tandis que deux, trois et toutes choses sont les classes, instances, attributs et méthodes que nous utilisons souvent. Utilisons hello world comme exemple. . :
# 创建一个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()
Effet de sortie :
Hello, world.
Il s'agit d'un processus standard de « deux engendre trois, trois engendre toutes choses ». De la classe aux méthodes que nous pouvons appeler, cela passe par ces deux étapes.
Alors on ne peut s’empêcher de se demander : d’où viennent les cours ? Revenez à la première ligne de code.
class Hello est en fait une "abréviation sémantique" d'une fonction, juste pour rendre le code plus facile à comprendre. Une autre façon de l'écrire est :
def fn(self, name='world'): # 假如我们有一个函数叫fn print('Hello, %s.' % name) Hello = type('Hello', (object,), dict(say_hello=fn)) # 通过type创建Hello class ---- 神秘的“道”,可以点化一切,这次我们直接从“道”生出了“二”
Cette façon d'écrire est identique à la précédente. L'écriture de Class Hello a exactement le même effet. Vous pouvez essayer de créer une instance et d'appeler
# 从Hello类创建一个实例hello ----二生三,完全一样 hello = Hello() # 使用hello调用方法say_hello ----三生万物,完全一样 hello.say_hello()
Effet de sortie :
Hello, world. ----调用结果完全一样。
Revenons sur la partie la plus excitante, est directement généré Li Er :
Hello = type('Hello', (object,), dict(say_hello=fn))
C'est "Tao", l'origine du monde python, vous pouvez en être étonné.
Faites attention à ses trois paramètres ! Cela coïncide avec les trois propositions éternelles de l’humanité : qui suis-je, d’où je viens et où est-ce que je veux aller.
Le premier paramètre : qui suis-je. Ici, j'ai besoin d'un nom différent de tout le reste. L'exemple ci-dessus me nomme "Bonjour"
Deuxième paramètre : D'où je viens
Ici, j'ai besoin de savoir d'où cela vient, qui est ma "classe parent". Dans l'exemple ci-dessus, ma classe parent est "objet" - une classe très junior en python.
Le troisième paramètre : où est-ce que je veux aller
Ici, nous incluons les méthodes et les attributs qui doivent être appelés dans un dictionnaire, puis nous les transmettons en tant que paramètres. Dans l’exemple ci-dessus, nous avons une méthode say_hello enveloppée dans un dictionnaire.
Il convient de noter que les trois propositions éternelles sont possédées par toutes les classes, toutes les instances et même tous les attributs et méthodes d'instance. Il va de soi que leurs « créateurs », Tao et Yi, c'est-à-dire le type et la métaclasse, ont également ces trois paramètres. Mais généralement, les trois propositions éternelles de la classe ne sont pas transmises en paramètres, mais de la manière suivante
class Hello(object){ # class 后声明“我是谁” # 小括号内声明“我来自哪里” # 中括号内声明“我要到哪里去” def say_hello(){ } }
Le créateur peut créer directement une seule personne, mais c'est difficile travail. Le Créateur créera d’abord l’espèce « humaine », puis créera des individus spécifiques par lots. Et transmettez les trois propositions éternelles.
"Tao" peut produire directement "deux", mais il produira d'abord "un" puis en produira "deux" par lots.
type peut générer directement une classe (classe), mais il peut également générer d'abord une métaclasse (métaclasse), puis utiliser la métaclasse pour personnaliser la classe par lots (classe).
De manière générale, les métaclasses sont nommées avec le suffixe Metalass. Imaginez que nous ayons besoin d'une métaclasse qui puisse automatiquement dire bonjour. En ce qui concerne les méthodes de classe qu'elle contient, parfois nous avons besoin de say_Hello, parfois nous avons besoin de say_Hi, parfois nous avons besoin de say_Sayolala et parfois nous avons besoin de say_Nihao.
Si chaque say_xxx intégré doit être déclaré une fois dans la classe, quel travail terrible ce sera ! Il est préférable d'utiliser la métaclasse pour résoudre le problème.
Ce qui suit est le code pour créer une métaclasse spécifiquement pour « dire bonjour » :
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)
Rappelez-vous deux points :
1 La métaclasse est dérivée de « type ». la classe parent doit transmettre le type. 【Tao en crée un, donc il faut contenir Tao】
2、元类的操作都在 __new__中完成,它的第一个参数是将创建的类,之后的参数即是三大永恒命题:我是谁,我从哪里来,我将到哪里去。 它返回的对象也是三大永恒命题,接下来,这三个参数将一直陪伴我们。
在__new__中,我只进行了一个操作,就是
attrs['say_'+name] = lambda self,value,saying=name: print(saying+','+value+'!')
它跟据类的名字,创建了一个类方法。比如我们由元类创建的类叫“Hello”,那创建时就自动有了一个叫“say_Hello”的类方法,然后又将类的名字“Hello”作为默认参数saying,传到了方法里面。然后把hello方法调用时的传参作为value传进去,最终打印出来。
那么,一个元类是怎么从创建到调用的呢?
来!一起根据道生一、一生二、二生三、三生万物的准则,走进元类的生命周期吧!
# 道生一:传入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!
注意:通过元类创建的类,第一个参数是父类,第二个参数是metaclass
普通人出生都不会说话,但有的人出生就会打招呼说“Hello”,“你好”,“sayolala”,这就是天赋的力量。它会给我们面向对象的编程省下无数的麻烦。
现在,保持元类不变,我们还可以继续创建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]
而普通的list没有add()方法
L2 = list() L2.add(1) >>>AttributeError: 'list' object has no attribute 'add'
太棒了!学到这里,你是不是已经体验到了造物主的乐趣?
我们选择两个领域,一个是Django的核心思想,“Object Relational Mapping”,即对象-关系映射,简称ORM。
这是Django的一大难点,但学完了元类,一切变得清晰。你对Django的理解将更上一层楼!
另一个领域是爬虫领域(黑客领域),一个自动搜索网络上的可用代理,然后换着IP去突破别的人反爬虫限制。
这两项技能非常有用,也非常好玩!
准备工作,创建一个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的私有属性,如果要将Field转化为字符串时,将返回“Field:XXX” , XXX是传入的name名称。
准备工作:创建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)
它做了以下几件事
创建一个新的字典mapping
将每一个类的属性,通过.items()遍历其键值对。如果值是Field类,则打印键值,并将这一对键值绑定到mapping字典上。
将刚刚传入值为Field类的属性删除。
创建一个专门的__mappings__属性,保存字典mapping。
创建一个专门的__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))
如果从Model创建一个子类User:
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()
这时先完成了二生三的过程:
先调用Model.__setattr__,将键值载入私有对象
然后调用元类的“天赋”,ModelMetaclass.__new__,将Model中的私有对象,只要是Field的实例,都自动存入u.__mappings__。
接下来完成了三生万物的过程:
通过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中的Model版块核心原理。
接下来,请和我一起进行更好玩的爬虫实战(嗯,你现在已经是初级黑客了):网络代理的爬取吧!
请确保已安装requests和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
这里,我们利用request包,把百度的源码爬了出来。
试一试抓百度
把这一段粘在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('p[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('p[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)
道生一:元类的__new__中,做了四件事:
将“crawl_”开头的类方法的名称推入ProxyGetter.__CrawlName__
将“crawl_”开头的类方法的本身推入ProxyGetter.__CrawlFunc_
计算符合“crawl_”开头的类方法个数
删除所有符合“crawl_”开头的类方法
怎么样?是不是和之前创建ORM的__mappings__过程极为相似?
一生二:类里面定义了使用pyquery抓取页面元素的方法
分别从三个免费代理网站抓取了页面上显示的全部代理。
如果对yield用法不熟悉,可以查看:
廖雪峰的python教程:生成器
二生三:创建实例对象crawler
三生万物:遍历每一个__CrawlFunc__
在ProxyGetter.__CrawlName__上面,获取可以抓取的的网址名。
触发类方法ProxyGetter.get_raw_proxies(site)
遍历ProxyGetter.__CrawlFunc__,如果方法名和网址名称相同的,则执行这一个方法
把每个网址获取到的代理整合成数组输出。
那么。。。怎么利用批量代理,冲击别人的网站,套取别人的密码,狂发广告水贴,定时骚扰客户? 呃!想啥呢!这些自己悟!如果悟不到,请听下回分解!
请记住挥动工具的口诀:
道生一,一生二,二生三,三生万物
我是谁,我来自哪里,我要到哪里去
相关推荐:
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!