面向对象之元类介绍

1.元类的介绍

首先来个知识储备:

补充知识点exec

全局作用域global()

局部作用域locals()

exec(object,globals(),locals()) 三个参数 需要运行的字符串,全局作用域,局部作用域

g = {
"x": 1,
"y": 2
}

l = {} # 放局部作用域字典

exec("""
global x , m #声明x,m为全局 作用域
x=10 #修改
m=1
z=100 # 函数内的局部作用域
""", g, l)

print(g) #{x:10,y:2,m:1....}
print(l) #{z:100}

1.类也是对象

python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例),因而我们可以将类当作一个对象去使用,同样满足第一类对象的概念,可以:

把类赋值给一个变量

把类作为函数参数进行传递

把类作为函数的返回值

在运行时动态地创建类

一切皆对象,对象可以怎么用?

1、都可以被引用,x=obj

2、都可以当作函数的参数传入

3、都可以当作函数的返回值

4、都可以当作容器类的元素,l=[func,time,obj,1]

比如:

class Foo:
pass
f1=Foo() #f1是通过Foo类实例化的对象

type函数可以查看类型,也可以用来查看对象的类,二者是一样的

print(type(f1)) # 输出:<class 'main.Foo'> 表示,obj 对象由Foo类创建
print(type(Foo)) # 输出:<type 'type'>

2.什么是元类

元类是类的类,是类的模板

元类是用来控制如何创建类的,正如类是创建对象的模板一样,而元类的主要目的是为了控制类的创建行为

元类的实例化的结果为我们用class定义的类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是 type 类的一个实例)

type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象

3.创建类的两种方式

方式一:使用class关键字

class Chinese(object):
country='China'
def init(self,name,age):
self.name=name
self.age=age
def talk(self):
print('%s is talking' %self.name)

方式二:就是手动模拟class创建类的过程):将创建类的步骤拆分开,手动去创建

我们用type来创建一个对象

定义类有三个要素1.类得名字,2,类得基类,3,类得名称空间

class_name = "Chinese" # 类得名字
class_base = (object,) #类得继承的类
class_body = '''
country='China'

def init(self,namem,age):
self.name=namem
self.age=age

def talk(self):
print('%s is talking' %self.name)
'''

class_dic = {} # 这个用法是用exec 把 class_body 转换成局部作用域,放在class_dic中

exec(class_body, globals(), class_dic)

print(class_dic) # 打印得到一个局部作用域

Chinese1 = type(class_name, class_base, class_dic) # 实际就是类创建过程
print(Chinese1) # 这样就会得到一个<class 'main.Chinese'>的类

五 自定义元类控制类的行为 !!!

一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类(顺便我们也可以瞅一瞅元类如何控制类的行为,工作流程是什么)

我们定义元类如下,来控制类的创建(必须有注释,且类名称首字母必须大写)

class Mymate(type):
def new(cls, name, base, attr):
if "doc" not in attr or not attr.get("doc").strip():
# 判断__doc__在不在字典里,还有在字典里值不能为空 否则抛出异常
raise TypeError('必须为类指定文档注释')
if not name.istitle():
raise TypeError('首字母大写') # 判断首字母大写
return type.new(cls, name, base, attr)

***********************************************

class MyMeta(type):
def init(self, class_name, class_base, class_body):

这个init方法是创建对象的时候触发 class Chinese() 时候就会触发

if not class_name.istitle():
raise TypeError('类名的首字母必须大写')

if 'doc' not in class_body or not class_body['doc'].strip():
raise TypeError('必须有注释,且注释不能为空')

super().init(class_name,class_base,class_body)
#*************************************************
上面的两种方法那种更合适???
class People(object, metaclass=Mymate):
'''dasda'''
country = "china"
skip = "yellow"

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

def talk(self):
print("%s is talking" % self.name)

控制类实例化的行为,那么需要先储备知识__call__方法的使用

class MyMeta(type):
def init(self, class_name, class_base, class_body):

这个init方法是创建对象的时候触发 class Chinese() 时候就会触发

if not class_name.istitle():
raise TypeError('类名的首字母必须大写')

if 'doc' not in class_body or not class_body['doc'].strip():
raise TypeError('必须有注释,且注释不能为空')

super().init(class_name, class_base, class_body)

def call(self, *args, **kwargs):

进入call方法做了几件事

1.生成一个实例对象

2.给新造的对象初始化

3.返回这个对象

print(self)
obj = object.new(self) # 造一个新的对象
print(obj)
self.init(obj, *args, **kwargs)
# 实例化的过程中产生的新对象调用新对象的init方法
# 这个地方其实就把实例的__dict__中添加我们设置好的属性
print("call", args)
print("call", kwargs)
print(obj)
return obj

class Chinese(object, metaclass=MyMeta):
"""..."""
country = "china"

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

def talk(self):
print("%s is talking !" % self.name)

obj = Chinese('alex', age=12) #实例化的过程调用__call__方法
obj.talk()
print(obj.dict)

在元类中控制自定义的类无需init方法

1.元类帮其完成创建对象,以及初始化操作;
  

2.要求实例化时传参必须为关键字形式,否则抛出异常TypeError: must use keyword argument
  

3.key作为用户自定义类产生对象的属性,且所有属性变成大写

class Mymetaclass(type):
# def new(cls,name,bases,attrs):
# update_attrs={}
# for k,v in attrs.items():
# if not callable(v) and not k.startswith('__'):
# update_attrs[k.upper()]=v
# else:
# update_attrs[k]=v
# return type.new(cls,name,bases,update_attrs)

def call(self, *args, **kwargs):
if args:
raise TypeError('must use keyword argument for key function')
obj = object.new(self) #创建对象,self为类Foo

for k,v in kwargs.items():
obj.dict[k.upper()]=v #这一步是直接把参数加到__dict__里面了 不用__init__方法,实际上 也是__call__调用init方法的,现在他自己实行
return obj

class Chinese(metaclass=Mymetaclass):
country='China'
tag='Legend of the Dragon' #龙的传人
def walk(self):
print('%s is walking' %self.name)

p=Chinese(name='egon',age=18,sex='male')
print(p.dict)

自定义元类控制类得实例化实现单例模式

单例模式后面的设计模式会讲到,这里不仔细讲解
主要是为了节约内存空间,避免多个重复实例的创建
先看下面的例子

class Mysql():
__instance = None # 设置类得属性,刚开始为none

def init(self):
self.addr = "127.0.0.0"
self.port = 1208

@classmethod
def singleton(cls):
if not cls.__instance: #判断实例是否已经创建了,没创建则创建
obj = cls()
# 这一步实际做的是实例化一个对象 用了类方法、把类传进去 实例化一个类得对象obj,赋值给__instance 这个类变量
# 赋值给cls.__instance 这个变量
cls.__instance = obj # 创建之赋值给__instance
return cls.__instance #返回生成的实例

一下的做法其实是生成两个mysql 实例 可是他们都是一样的 但是占用内存 所以引入单例模式 一样的就不创建了,用以前的就好

m1=Mysql()

m2=Mysql()

print(id(m1))

print(id(m2))

使用类方法的单例模式创建数据引擎,避免内存浪费

m1 = Mysql.singleton()
m2 = Mysql.singleton()
print(id(m1))
print(id(m2))

posted @ 2018-04-10 13:55  小狗子  阅读(476)  评论(0)    收藏  举报