面向对象之 —— 类、元类、单例
类
Python中类的概念借鉴于Smalltalk。在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立。
class ObjectCreator(object): pass my_object = ObjectCreator() print my_object # <__main__.ObjectCreator object at 0x8974f2c>
但是,Python中的类还远不止如此。类同样也是一种对象。只要你使用关键字class,Python解释器在执行的时候就会创建一个对象。将在内存中创建一个对象,名字就是ObjectCreator。
class ObjectCreator(object): pass
这个对象(类)自身拥有创建对象(类实例)的能力,而这就是为什么它是一个类的原因。但是,它的本质仍然是一个对象,于是你可以对它做如下的操作:
- 将它赋值给一个变量
- 拷贝它
- 为它增加属性
- 将它作为函数参数进行传递
# 打印一个类,因为它其实也是一个对象 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.使用class关键字,通过return class动态的构建需要的类。
def choose_class(name): if name == 'foo': class Foo(object): pass return Foo # 返回的是类,不是类的实例 else: class Bar(object): pass return Bar MyClass = choose_class('foo') # 函数返回的是类,不是类的实例 print MyClass # <class '__main__.Foo'> # 你可以通过这个类创建类实例,也就是对象 print MyClass() # <__main__.Foo object at 0x1085ed950
2.通过type函数构造类
当使用class关键字时,Python解释器自动创建这个对象。但Python仍然提供手动处理的方法——使用type实例化来动态的创建类
type的语法: type(类名,父类的元组(针对继承的情况,可以为空),类的名称空间(包含属性的字典(名称:值))) 比如下面的代码: class MyShinyClass(object): pass 构建Foo类: #构建目标代码 class Foo(object): bar = True #使用type构建 Foo = type('Foo', (), {'bar':True}) # 可以使用字典来定义类的属性
例如:
# myclass 包含的代码 code = """ name = "张三" age = 18 def hello(self): print("hello %s" % self.name) """ # 类的名字 class_name = "MyClass" # 类的的父类们 base_classes = (object,) # 类的名称空间 namespace = {} exec(code,{},namespace) res = type(class_name,base_classes,namespace) # 返回一个类对象 # 创建一个该类的实例 print(res()) # <__main__.MyClass object at 0x000001DB50757780> print(res.name) # 张三 print(res.age) # 18 print(res.hello) # <function hello at 0x000001DB508F50D0>
在Python中,类也是对象,你可以动态的创建类。这就是当你使用关键字class时Python在幕后做的事情,而这就是通过元类来实现的。
创建模块的过程
- 1.创建一个空的名称空间
- 2.执行内部的代码
- 3.将得到的名字放到名称空间中
控制类的创建过程
- 1.创建一个元类 (需要继承type)
- 2.覆盖__init__方法,该方法会将新建的类对象、类名、父类们、名称空间都传进来,可以利用这些信息在做处理
- 3.对于需要被控制的类,需要指定metaclass 为上面的元类
控制类实例化对象的过程
- 1.创建一个元类 (需要继承type)
- 2.覆盖__call__方法,会将正在实例化对象的类、调用类时传入的参数,都传进来
- 3.在__call__方法中,必须要先编写模板代码
- 3.1创建空对象 object.__new__(类)
- 3.2调用类的__init__方法来初始化这个空对象
- 3.3返回该对象
- 4.加入你需要控制的逻辑
元类 -> 实例化产生 -> 类 -> 实例化产生 -> 对象
元类:用于产生类的类,(默认)type就是元类
元类就是用来创建类的“东西”。元类就是类的类。
所有的自定义类都是通过type(一个内建函数)实例化得来(继承type),可以通过检查__class__属性来看到这一点。
MyClass = MetaClass() #元类创建 MyObject = MyClass() #类创建实例 实际上MyClass就是通过type()来创创建出MyClass类,它是type()类的一个实例;
同时MyClass本身也是类,也可以创建出自己的实例,这里就是MyObject
__metaclass__属性:
你可以在写一个类的时候为其添加__metaclass__属性,定义了__metaclass__就定义了这个类的元类。
语法: class Foo(object): #py2 __metaclass__ = something… class Foo(metaclass=something): #py3 __metaclass__ = something… 定义一个类 : class Foo(Bar): pass
属性查找顺序
在该类并定义的时候,它还没有在内存中生成,想要知道它是否被调用。Python做了如下的操作:
- Foo中有__metaclass__这个属性吗?如果有,Python会在内存中通过__metaclass__创建一个名字为Foo的类对象。
- 如果Python没有找到__metaclass__,它会继续在父类中寻找__metaclass__属性,并尝试做上一步操作。
- 如果Python在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做上一步操作。
- 如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象。
那么可以在__metaclass__中放置些什么代码呢?——放置可以创建一个类的东西。
那么什么可以用来创建一个类呢?——type,或者任何使用到type或者子类化type的东西都可以。
自定义元类 —— 控制类的创建过程
元类的主要目的就是为了当创建类时能够自动地改变类。
要控制类的创建过程,只需要找到类所属元类中的__init__方法即可。
#但凡继承了type的类才能称之为自定义的元类,否则就是只是一个普通的类 class MyMeta(type): #第一个参数为创建的类,其余参数,是类名,类的父类元组,类的名称空间 def __init__(self,class_name,class_bases,namespaces): #Python会自动创建属性赋值,不需要我们再重新创建 #书写逻辑 if not class_name.istitle(): #书写类名大写打开的逻辑 raise TypeError("类的名字必须以大写字母开头") #主动抛出异常 if not self.__doc__ or len((self.__doc__).strip())==0: raise TypeError("类必须有文档注释且内容不能为空") class Student(object,metaclass=MyMeta): #Student=Mymeta('Student',(object,),{名称空间}) """ xxx """ def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex def study(self): print("%s正在学习。。。"%self.name) stu=Student("abc",18,"male") # 会触发Student的类(即元类)中的__call__函数 print(stu.name) print(stu.__dict__) # 当不写文档注释或者文档注释为中没有字符时,会报错 # TypeError: 类必须有文档注释且内容不能为空
自定义元类控制类的创建过程的步骤:
- 创建一个自定义元类,继承type
- 覆盖type方法中的__init__方法,传入的参数为:正在创建的类、类名,类的父类元组,类的名称空间
- 加入控制逻辑
- 对于需要被控制的类,指定metaclass=自定义的元类
自定义元类 —— 控制类的对象的实例化
元类的主要目的就是为了当创建类时能够自动地改变类。
在介绍自定义元类控制类的对象的实例化的过程之前,我们介绍一个内置方法__call__
__call__方法:对象后面加括号,触发执行。
__call__:调用。
- 在调用对象时,会执行该对象所属类中的__call__方法
- 调用类时,会执行该类所属元类中的__call__方法。
一般这个方法只会在元类中使用,可以控制类被调用时即类实例化对象过程的一些逻辑判断。
# 自定义一个元类 元类也是一个类 但是需要继承type class MyMeta(type): def __call__(self, *args, **kwargs): # self 表示要创建对象的那个类(Person) *args是调用Person类时传入的参数 print("MyMte中的 call run'") print(self,*args,**kwargs) # 下面的三步是固定写法 一个模板 只要你需要控制对象的创建过程 就应该先把模板写出来 # 1.创建空对象 obj = object.__new__(self) # 2.调用初始化方法 self.__init__(obj,*args,**kwargs) # self.__init__(obj) # 3.得到一个完整的对象 return obj class Person(metaclass=MyMeta): # 修改Person类的元类为MyMeta def __init__(self,name,age): self.name = name self.age = age def __call__(self, *args, **kwargs): print("call run...") p = Person("王二狗",66) # 调用Person这个对象时 执行的是 Person的类(type)中__call__ 方法 print(p) # 当调用对象时 会执行该对象所属类中的__call__方法 # p() 可以这样调用哟 print(p.name) print(p.age)
注意:
- __init__构造方法的执行是由创建对象触发的,即:对象 = 类名() ;
- 而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
自定义元类控制类的对象的实例化过程
#但凡继承了type的类才能称之为自定义的元类,否则就是只是一个普通的类 class MyMeta(type): #self是Student这个类,*args和**kwargs是指类实例化对象时传入的参数 def __call__(self, *args, **kwargs): print("MyMeta is running") #创建一个空对象 obj=object.__new__(self) #调用初始化函数 self.__init__(obj,*args,**kwargs) #返回对象 return obj class Student(object,metaclass=MyMeta): #Student=Mymeta('Student',(object,),{名称空间}) def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex def study(self): print("%s正在学习。。。"%self.name) stu=Student("abc",18,"male") # 会触发Student的类(即元类)中的__call__函数 print(stu.name) print(stu.__dict__) #======================================================= MyMeta is running abc {'name': 'abc', 'age': 18, 'sex': 'male'}
自定义元类的目的
- 可以通过__call__来控制对象的创建过程即类实例化对象的过程。
- 可以控制类的创建过程。
元类中的__new__
__new__的作用是创建一个空的类(实例)对象,在对象被创建的时候调用该方法,它是一个类方法。作为一个类对象,必须具备的三个组成部分(类名、父类的元组、名称空间),所以调用type中的__new__来完成组装,得到这个类对象后需要将返回一个实例,以供__init__来使用(自动的调用__init__方法,对实例进行初始化)。如果__new__方法不返回值,或者返回的不是实例,那么它就不会自动的去调用__init__方法。(即必须生成本类的实例才能自动调用本类的__init__方法进行初始化,否则不会自动调用__init__)
__init__ 方法负责将该实例对象进行初始化,在对象被创建之后调用该方法,在__new__方法创建出一个实例后对实例属性进行初始化。__init__方法可以没有返回值。想为一个类添加一个属性,在__init__中实现就可以了,没有必要重写__new__方法。
如果将创建实例的过程比作建一个房子。 class就是一个房屋的设计图,他规定了这个房子有几个房间,每个人房间的大小朝向等。这个设计图就是累的结构 __new__就是一个房屋的框架,每个具体的房屋都需要先搭好框架后才能进行专修,当然现有了房屋设计才能有具体的房屋框架出来。这个就是从类到类实例的创建。 __init__就是装修房子的过程,对房屋的墙面和地板等颜色材质的丰富就是它该做的事情,当然先有具体的房子框架出来才能进行装饰了。这个就是实例属性的初始化,它是在__new__出一个实例后才能初始化。 __call__就是房子的电话,有了固定电话,才能被打电话嘛(就是通过括号的方式像函数一样执行)。
#coding:utf-8 class Foo(object): def __new__(cls, *args, **kwargs): #__new__是一个类方法,在对象创建的时候调用 print("excute __new__") return super(Foo,cls).__new__(cls) def __init__(self,value): #__init__是一个实例方法,在对象创建后调用,对实例属性做初始化 print("excute __init__") self.value = value f1 = Foo(1) print(f1.value) f2 = Foo(2) print(f2.value) # excute __new__ # excute __init__ # 1 # excute __new__ # excute __init__ # 2 # 可以看出new方法在init方法之前执行
子类如果重写__new__方法,一般依然要调用父类的__new__方法。
单例(一种设计模式/套路)
一个类如果只有一个实例,那么该类称之为单例
如果需要实现的功能相同,且数量众多(并发量大)或者说要处理的数据只有一份时,所有对象的属性都相同时-----使用单例
为什么需要单例:通过这种方法,可以保证功能相同的对象可以在内存中仅创建一个(以__call__控制实例化过程,保证每一次调用都返回一个实例),需要时都去调用,节省内存。
# 单例类定义 class Foo(object): __instance = None @staticmethod def singleton(): if Foo.__instance: return Foo.__instance else: Foo.__instance = Foo() return Foo.__instance # 获取实例 obj = Foo.singleton()
对于Python单例模式,创建对象时不能再直接使用:obj = Foo(),而应该调用特殊的方法:obj = Foo.singleton() 。
# -- 实现元类为CarMeta的类至少有生产日期(production_date)、发动机编号(engine_number)及载客量(capacity)三个基本属性 class CarMeta(type): def __call__(self, *args, **kwargs): print(args) if len(args)<3: raise ValueError(" ") obj = object.__new__(self) self.__init__(obj,*args,**kwargs) if not ("production_date"in obj.__dict__ and "engine_number"in obj.__dict__ and "capacity"in obj.__dict__): raise ValueError(" ") return obj class BigCar(metaclass=CarMeta): def __init__(self,production_date,engine_number,capacity): self.production_date=production_date self.engine_number = engine_number self.capacity=capacity car=BigCar("2018 12 21","xxxxxx",6) print(car)
单例模式的要点:
- 某个类只能有一个实例
- 它必须自行创建这个实例
- 它必须自行向整个系统提供这个实例。
实现单例的方式:
- 使用__new__方法实现单例
- 使用元类(metaclass)实现单例
- 使用模块实现单例
- 使用装饰器(decorator)实现单例
1.使用__new__方法实现单例
class MyClass(object): _instance = None def __new__(cls, *args, **kwargs): if not cls._instance: cls._instance = super(MyClass, cls).__new__(cls, *args, **kwargs) return cls._instance class HerClass(MyClass): a = 1 one = HerClass() two = HerClass() print(one == two) #True print(one is two) #True print(id(one), id(two)) #42818864 42818864
在上面的代码中,我们将类的实例和一个类变量 _instance
关联起来,如果 cls._instance
为 None 则创建实例,否则直接返回 cls._instance
。
重写了__new__方法,继承MyClass类。主要用于需要对类的结构进行改变的情况。
2.使用元类(metaclass)实现单例
元类(metaclass)可以控制类的创建过程,它主要做三件事:
- 拦截类的创建
- 修改类的定义
- 返回修改后的类
使用元类实现单例模式的代码如下:
class Singleton(type): _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) return cls._instances[cls] # Python2 # class MyClass(object): # __metaclass__ = Singleton # Python3 class MyClass(metaclass=Singleton): pass
优点:
- 实例控制:单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
- 灵活性:因为类控制了实例化过程,所以类可以灵活更改实例化过程。
缺点:
- 开销:虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。
- 可能的开发混淆:使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用 new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
- 对象生存期:不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用。
class MyMeta(type): obj=None def __call__(self, *args, **kwargs): if not MyMeta.obj: #创建空对象 obj=object.__new__(self) #调用初始化函数 self.__init__(obj,*args,**kwargs) MyMeta.obj=obj return MyMeta.obj class Printer(metaclass=MyMeta): obj=None def __init__(self,name,brand,type): self.name=name self.brand=brand self.type=type def printing(self): print("正在打印。。。") p1=Printer("ES005","爱普生","彩色打印机") p2=Printer("ES005","爱普生","彩色打印机") p3=Printer("ES005","爱普生","彩色打印机") print(p1) print(p2) print(p3) #============================================ <__main__.Printer object at 0x000002740AE4AF60> <__main__.Printer object at 0x000002740AE4AF60> <__main__.Printer object at 0x000002740AE4AF60>
3.使用模块实现单例
Python 的模块就是天然的单例模式。因为模块在第一次导入时,会生成 .py
文件,当第二次导入时,就会直接加载 .py
文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。
#tests1.py class MyClass(object): def foo(self): print('MyClass.foo') my_class_obj=MyClass() # 将上面的代码保存在文件 tests1.py 中,然后导入使用: from .tests1 import my_class_obj my_class_obj.foo()
4.使用装饰器(decorator)实现单例
装饰器(decorator)可以动态地修改一个类或函数的功能。所以可以使用装饰器来装饰某个类,使其只能生成一个实例
from functools import wraps def singleton(cls): instances = {} @wraps(cls) def getinstance(*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return getinstance @singleton class MyClass(object): a = 1
定义一个装饰器 singleton
,返回一个内部函数 getinstance
,该函数会判断某个类是否在字典 instances
中,如果不存在,则会将 cls
作为 key,cls(*args, **kw)
作为 value 存到 instances
中,否则,直接返回 instances[cls]
。
#!/usr/bin/env python #coding:utf-8 from wsgiref.simple_server import make_server ————————单例类定义—————————————— class DbHelper(object): __instance = None def __init__(self): self.hostname = '1.1.1.1' self.port = 3306 self.password = 'pwd' self.username = 'root' @staticmethod def singleton(): if DbHelper.__instance: return DbHelper.__instance else: DbHelper.__instance = DbHelper() return DbHelper.__instance def fetch(self): # 连接数据库 # 拼接sql语句 # 操作 pass def create(self): # 连接数据库 # 拼接sql语句 # 操作 pass def remove(self): # 连接数据库 # 拼接sql语句 # 操作 pass def modify(self): # 连接数据库 # 拼接sql语句 # 操作 pass class Handler(object): def index(self): obj = DbHelper.singleton() print id(single) obj.create() return 'index' def news(self): return 'news' def RunServer(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) url = environ['PATH_INFO'] temp = url.split('/')[1] obj = Handler() is_exist = hasattr(obj, temp) if is_exist: func = getattr(obj, temp) ret = func() return ret else: return '404 not found' if __name__ == '__main__': httpd = make_server('', 8001, RunServer) print "Serving HTTP on port 8001..." httpd.serve_forever()