学懂python的元类,只要记住两句话:
- 道生一,一生二,二生三,三生万物
- 我是谁,我从哪里来,我要到哪里去?
在python的世界里,一切皆是对象,而所有的对象的源头都是type,type其实是一个类,只是用了小写的形式.
在python的世界里,type
就是道
,python中所的一切都是由type创造出来的.
道生一,一生二,二生三,三生万物
- 1.道 -> 即使type类
- 2.一 -> 就是元类(metaclass)或者说是类生成器
- 3.二 -> Class(类,或者叫实例生成器)
- 4.三 -> 实例(instance)
- 5.万物 -> 即是实例的各种属性和方法.
下面用Hello World来说明下以上的内容
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/8 16:31'
# 创建一个Hello类,拥有属性say_hello -- 二的起源(类的起源)
class Hello(object):
def say_hello(self,name='world'):
print('Hello,{}'.format(name))
# 从Hello类创建一个实例hello --> 二生三
hello = Hello()
# 使用实例hello调用方法say_hello --> 三生万物
hello.say_hello()
这是一个标准的二生三,三生万物的过程.
下面研究一下这个二是怎么来的?其实上述代码只是一种语义化的简称,Python的解释器会翻译以上的代码
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/8 16:37'
def fn(self,name='world'):
print('Hello,{}'.format(name))
Hello = type('Hello',(object,),(dict(say_hello=fn)))
hello = Hello()
hello.say_hello()
这样的写法和刚才的写法的效果完全一样,你可以尝试一下
我们回头看下最精彩的部分:道直接生出了二
Hello = type('Hello', (object,), dict(say_hello=fn))
这就是“道”,python世界的起源,你可以为此而惊叹。
注意它的三个参数!暗合人类的三大永恒命题:我是谁,我从哪里来,我要到哪里去。
- 第一个参数: 我是谁? 类的名称,区分和其他类的不同,Hello
- 第二个参数: 我从哪里来,我继承自谁,这里会传入我的父类是谁?
- 第三个参数: 我要到哪里去?这里将属性和参数当成一个字典传入.创建字典的方式,可以使用dict()的方式
值得注意的是,三大永恒的命题,是一切的类,一切的实例,甚至一切的属性和方法都具有的,毕竟这些都是继承过来的.
但是平常,一般不是用这种方式传入的,而是用下面这种方式传入的.
Class Hello(object):
def say_hello(self):
pass
# Class 后面是我是谁?
# 类名后面()中放置的我从哪里来.
# 下面的代码区域就是我要到哪里去
- type可以创建一切的类,但是这样比较麻烦.道可以直接生出二,但是一般的它会先生出一,然后再由一创造二.
- type可以直接生出类(class),也可以生出元类(metaclass),再使用元类批量定制类
一般来说,元类均被命名后缀为Metalass。想象一下,我们需要一个可以自动打招呼的元类,它里面的类方法呢,有时需要say_Hello,有时需要say_Hi,有时又需要say_Sayolala,有时需要say_Nihao。
如果每个内置的say_xxx都需要在类里面声明一次,那将是多么可怕的苦役! 不如使用元类来解决问题。
以下是创建一个专门“打招呼”用的元类代码:
元类的创建
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/8 16:50'
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)
记住两点:
- 元类是由“type”衍生而出,所以父类需要传入type。【道生一,所以一必须包含道】
- 元类的操作都在 new中完成,它的第一个参数是将创建的类,之后的参数即是三大永恒命题:我是谁,我从哪里来,我将到哪里去。 它返回的对象也是三大永恒命题,接下来,这三个参数将一直陪伴我们。
在__new__中
,我们只进行了一个操作,就是
attrs['say_'+name] = lambda self,value,saying=name: print(saying+','+value+'!')
它根据类的名字,创建一个类方法.比如我们由元类创建的类叫'Hello',那创建时候,就自动有了一个叫'say_hello'的类方法,然后又将name值'Hello'作为默认参数saying,传递到方法里面.
那么元类,该怎么使用呢?
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/8 17:33'
# 定义元类
# 道生一,传入type
class SayMetaClass(type):
def __new__(cls, name,bases,attrs):
print('调用一次')
# 创造天赋
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() # 注意元类中的__new__方法只会别调用一次.Hello()以后调用的是__call__方法
hello1 = Hello()
# 三生万物:调用实例方法
hello.say_Hello('world!')
hello1.say_Hello('baby')
理解类也是一个对象
理解元类之前,你必须了解,类也是对象.
class ObjectCreator(object):
pass
将在内存中创建一个对象,名字就是ObjectCreator.这个对象(类)自身拥有创建对象(类实例)的能力,这就是它作为一个类的原因.但是它本质上也是一个对象,你可以将它赋值给一个变量,你可以拷贝它,你可以给它增加属性,你可以将它作为函数参数进行传递.
print (ObjectCreator) # 你可以打印一个类,因为它其实也是一个对象
#输出:<class '__main__.ObjectCreator'>
Idef echo(o):
print (o)
echo(ObjectCreator) # 你可以将类做为参数传给函数
#输出:<class '__main__.ObjectCreator'>
print (hasattr(ObjectCreator, 'new_attribute'))
#输出:False
ObjectCreator.new_attribute = 'foo' # 你可以为类增加属性
print (hasattr(ObjectCreator, 'new_attribute'))
#输出:True
print (ObjectCreator.new_attribute)
#输出:foo
ObjectCreatorMirror = ObjectCreator # 你可以将类赋值给一个变量
print (ObjectCreatorMirror())
#输出:<__main__.ObjectCreator object at 0x108551310>
动态的创建类
1.通过return class动态的构建类
因为类也是对象,你可以在运行的时候动态的创建它们,就像其他任何对象一样.首先,你可以在函数中创建一个类,然后通过return返回.
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/8 18:08'
def create_class(name):
if name == 'foo':
class Foo(object):
pass
return Foo # 返回的是类对象,不是类的实例
else:
class Bar(object):
pass
return Bar
MyClass = create_class('foo') # 函数返回的是类,不是实例
# 输出 <class '__main__.create_class.<locals>.Foo'>
print(MyClass)
print(MyClass()) # 可以通过这个类创建类实例,也就是类的对象
# 输出 <__main__.create_class.<locals>.Foo object at 0x000002B642B1EA20>
2.通过type构造类
但是这还不够动态,python中可以使用type语法构建一个类,返回一个类对象.
type语法:
type(类名,父类元组,属性字典)
class MyClass(object):
pass
MyClass = type('MyClass',(object,),{}) # 返回一个类对象
print(MyClass)
输出:<class '__main__.MyShinyClass'>
print(MyClass())
#输出:<__main__.MyShinyClass object at 0x1085cd810>
通过type创建类的示例:
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/8 18:34'
# 构建Foo类
class Foo(object):
bar = True
# 使用type构建
Foo = type('Foo',(object,),dict(bar=True))
# 继承Foo类
class FooChild(Foo):
pass
# 使用Type构建
FooChild = type('FooChild',(Foo,),{})
print(FooChild) # 输出<class '__main__.FooChild'>
print(FooChild.bar) # 输出True
# 为FooChild增加新方法
def echo_bar(self):
print(self.bar)
FooChild = type('FooChild',(Foo,),dict(echo_bar=echo_bar))
print(hasattr(Foo,'echo_bar')) # 输出False
print(hasattr(FooChild,'echo_bar')) # 输出True
my_foo = FooChild()
my_foo.echo_bar() # 输出True
元类
什么是元类
我们知道Python的类也是对象了,那么这些类是由谁创建的呢,答案就是元类.创建类的类就是元类,也就是说是类的类.
元类是由type创建的.type是源头
Class = MetaClass() # 元类创建类对象
myobject = Class() # 类创建我们的实例对象
函数type实际上是一个元类。type就是Python在背后用来创建所有类的元类。现在你想知道那为什么type会全部采用小写形式而不是Type呢?好吧,我猜这是为了和str保持一致性,str是用来创建字符串对象的类,而int是用来创建整数对象的类。type就是创建类对象的类。你可以通过检查class属性来看到这一点。Python中所有的东西,注意,我是指所有的东西——都是对象。这包括整数、字符串、函数以及类。它们全部都是对象,而且它们都是从一个类创建而来。
那么任何一个
__class__
的__class__
属性又是什么呢?因此,python的元类就是创建类的这种东西,而type就是python的内建元类,当然你也可以创建自己的元类.
metaclass
属性
Python2中使用给metaclass赋值的方式创建元类,而Python3则通过在类的括号中通过metaclass=的方式来指定元类.
使用函数创建元类
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/8 19:05'
def upper_attr(name, bases, attrs):
'''返回一个类对象,将属性都转换为大写形式'''
attrs = {name: value for name, value in attrs.items() if not name.startswith('__')}
# 将它们转为大写形式
attrs = {name.upper(): value for name, value in attrs.items()} # 这里改变它的属性的大小写
return type(name,bases,attrs) # 返回一个类
class Foo(object,metaclass=upper_attr):
bar = '牛逼'
PHT = '哄哄'
print(hasattr(Foo,'bar')) # 属性已经大写,这里会变成False
print(hasattr(Foo,'BAR')) # 这里会变成True
f = Foo()
print(f.BAR) # 这里打印牛逼
使用类创建元类
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/8 19:15'
# 1.请记住,'type'实际上是一个类,就像'str'和'int'一样。所以,你可以从type继承
# 2. __new__ 是在__init__之前被调用的特殊方法,
# __new__是用来创建对象并返回之的方法,__new_()是一个类方法
# 3. 而__init__只是用来将传入的参数初始化给对象,它是在对象创建之后执行的方法。
# 4. 你很少用到__new__,除非你希望能够控制对象的创建。这里,创建的对象是类,我
# 们希望能够自定义它,所以我们这里改写__new__如果你希望的话,
# 你也可以在__init__中做些事情。还有一些高级的用法会涉及到改写__call__特殊方法,
# 但是我们这里不用,下面我们可以单独的讨论这个使用
class UpperAttrMetaClass(type):
def __new__(cls, name,bases,attrs):
attrs = {name.upper():value for name,value in attrs.items() if not name.startswith('__')}
return type.__new__(cls,name,bases,attrs)
使用super方法使得其更加的清晰
class UpperAttrMetaClass(type):
def __new__(cls, name, bases, attrs):
attrs = {name.upper(): value for name, value in attrs.items() if not name.startswith('__')}
return super(UpperAttrMetaClass,cls).__new__(cls,name,bases,attrs)
使用元类实现Django的ORM功能
我们通过创建一个类似Django中的ORM来熟悉一下元类的使用,通常元类用来创建API是非常好的选择.使用元类的编写很复杂,但是使用者可以非常简单的调用API
#我们想创建一个类似Django的ORM,只要定义字段就可以实现对数据库表和字段的操作。
class User(Model):
# 定义类的属性到列的映射:
id = IntegerField('id')
name = StringField('username')
email = StringField('email')
password = StringField('password')
使用的时候
# 创建一个实例:
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
# 保存到数据库:
u.save()
元类实现ORM的类似功能
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/8 19:37'
# 首先来定义Field类,它负责保存数据库表的字段名和字段类型
class Field(object):
def __init__(self, name, column_type):
self.name = name
self.column_type = column_type
def __str__(self):
return '<{}:{}>'.format(self.__class__.__name__, self.name)
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')
# 定义元类,控制Model对象的创建
class ModelMetaClass(type):
def __new__(cls, name, bases, attrs):
if name == 'Model':
return super(ModelMetaClass, cls).__new__(cls, name, bases, attrs)
mapping = dict()
for k, v in attrs.items():
if isinstance(v, Field):
# 保存属性和列的映射关系到mappings字典
print("找到映射关系: {}==>{}".format(k, v))
mapping[k] = v
for k in mapping.keys():
# 将类属性移除,使定义的类字段不污染用户的类属性,只有在实例中可以访问这些key
attrs.pop(k)
attrs['__table__'] = name.lower()
attrs['__mapping__'] = mapping
return super(ModelMetaClass, cls).__new__(cls, name, bases, attrs)
# 编写Model的积累
class Model(dict, metaclass=ModelMetaClass):
def __init__(self, **kwargs):
super(Model, self).__init__(**kwargs)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Model' object has no attribute '%s'" % key)
def __setattr__(self, key, value):
self[key] = value
def save(self):
fields = []
params = []
args = []
for k, v in self.__mapping__.items():
fields.append(v.name)
params.append('?')
args.append(getattr(self, k, None))
sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
print('SQL: %s' % sql)
print('ARGS: %s' % str(args))
# 最后,我们使用定义好的ORM接口,使用起来非常方便
class User(Model):
id = IntegerField('id')
name = StringField('username')
email = StringField('email')
password = StringField('password')
# 创建一个实例
u = User(id=12345,name='Fioman',email='test@orm.org',password='123456')
# 保存到数据库
u.save()
输出
找到映射关系: id==><IntegerField:id>
找到映射关系: name==><StringField:username>
找到映射关系: email==><StringField:email>
找到映射关系: password==><StringField:password>
SQL: insert into user (id,username,email,password) values (?,?,?,?)
ARGS: [12345, 'Fioman', 'test@orm.org', '123456']
使用__new__
方法和元类(metaclass)
方式分别实现单例模式
首先弄明白这三个函数的作用.
1.__new__()
它负责创建一个实例对象,一个对象创建的时候,它会先去调用__new__()
方法,如果__new__
返回一个对象的实例,则它就会执行__init__()
方法对这个实例进行初始化,如果__new__
不返回一个实例,则不会调用__init__
方法.
2.__init__()
它负责对一个创建的实例进行初始化,在对象创建之后调用该方法,在__new__
方法返回一个实例对象时对对象进行初始化.__init__
方法可以没有返回值
3.__call__()
方法,其实和类的创建过程和实例化没有太大的关系,一个类的__call__
方法,只有当由它所创建的对象被当作像函数那样调用的时候才会调用.也就是说,如果一个对象使用这种方式来调用object()
,则它调用的就是这个对象所对应的创建它的类的__call__
方法.
例如:
class A(object):
def __call__(self):
print("__call__ be called")
a = A() # 因为A的元类应该是type,所以这里A()调用的应该是type的`__call__方法`,A也是对象,它是type元类创建的对象
a() # 因为a是A类创建的对象,所以这里调用的是A的`__call__方法`
#输出
#__call__ be called
子类如果重写了__new__
方法,一般依然要调用父类的__new__
方法
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/9 14:08'
class Foo(object):
def __new__(cls, *args, **kwargs):
print('Foo 的 __new__被调用.')
return super().__new__(cls)
class A(Foo):
pass
class B(Foo):
def __new__(cls, *args, **kwargs):
print('B 的__new__被调用.')
return super().__new__(cls)
a = A()
b = B()
# 结果
Foo 的 __new__被调用.
B 的__new__被调用.
Foo 的 __new__被调用.
注意,类的__new__方法之后,必须生成本类的实例才能自动调用本类的
init方法进行初始化,否则不会自动调用
init``
class Foo(object):
def __init__(self, *args, **kwargs):
print ("Foo __init__")
def __new__(cls, *args, **kwargs):
return object.__new__(Stranger)
class Stranger(object):
def __init__(self,name):
print ("class Stranger's __init__ be called")
self.name = name
foo = Foo("test")
print type(foo) #<class '__main__.Stranger'>
print foo.name #AttributeError: 'Stranger' object has no attribute 'name'
#说明:如果new方法返回的不是本类的实例,那么本类(Foo)的init和生成的类(Stranger)的init都不会被调用
使用__new__()
方法来创建单例模式
步骤: 在__new__()
方法中,判断有没有这个实例,用一个类的属性来表示实例,如果这个实例存在,直接返回,如果不存在就调用父类的__new__()
方法进行创建,并保存在_instance属性当中
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/9 14:00'
import threading
class Singleton(object):
_thread_lock = threading.Lock()
def __init__(self):
pass
def __new__(cls, *args, **kwargs):
if not hasattr(cls,'_instance'):
with Singleton._thread_lock as lock:
if not hasattr(cls,'_instance'): # 如果没有就创建
Singleton._instance = super(Singleton,cls).__new__(cls)
return Singleton._instance # 返回这个实例
使用元类实现单例模式
知识点主要有两点
- 当我们在用一个类实例化对象的时候A(),相当于是调用了创建A这个类的元类的
__call__方法.
- 创建一个单例类,在元类中限定它在创建对象的时候只有一个对象,用它的元类的一个属性来表示,这个属性存在就直接返回,不存在再创建.
# encoding:utf-8
import threading
import time
__author__ = 'Fioman'
__time__ = '2019/3/9 14:21'
# 创建元类,一般继承自type
class SingletonMetaClass(type):
# 线程锁
_thread_lock = threading.Lock()
def __init__(self, *args, **kwargs):
self._instance = None
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
if self._instance is None:
with SingletonMetaClass._thread_lock:
if self._instance is None:
self._instance = super().__call__(*args, **kwargs) # 注意这里调用的是父类的__call__方法创建实例
return self._instance
# 通过上面的元类,来创建类
class Sing(metaclass=SingletonMetaClass):
def sing(self,i):
print('{}在唱歌'.format(i))
def task(i):
s = Sing()
s.sing(id(s))
if __name__ == '__main__':
for i in range(10):
t = threading.Thread(target=task, args=(i,))
t.start()
打印结果
2012339149176在唱歌
2012339149176在唱歌
2012339149176在唱歌
2012339149176在唱歌
2012339149176在唱歌
2012339149176在唱歌
2012339149176在唱歌
2012339149176在唱歌
2012339149176在唱歌
2012339149176在唱歌
都是一个对象,多个线程创建也是同一个对象
网友评论