面向对象之元类介绍

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  小狗子  阅读(454)  评论(0编辑  收藏  举报