1.元类
类也是对象
元类是类的类,是类的模板
元类的实例为类,正如类的实例为对象。
类的本质是对象, 于是可以对类做如下的操作:
- 你可以将它赋值给一个变量
- 你可以拷⻉它
- 你可以为它增加属性
- 你可以将它作为函数参数进行传递
1.1 创建类的方式
方法一: 通过分支语句动态地创建类(极少用)
因为类也是对象,运行时在函数中使用class关键字动态的创建类。
当使用class关键字时,Python解释器自动创建这个对象。当然Python提供手动处理的方法。
def create_class(name):
if name == 'foo':
class Foo(object):
pass
return Foo
else:
class Bar(object):
pass
return Bar
cls = create_class(name='foo1')
print(cls.__name__)
方法二: 通过type函数动态创建类
- type函数功能一: 判断对象的类型。
- type函数功能二: 动态的创建类。type可以接受一个类的描述作为参数,然后返回一个类。
- type函数语法: type(类名, 父类名称的元组, 属性信息)
# type函数语法:
# type(类名, 父类名称的元组, 属性信息)
# class Person(object):
# country= 'China'
def hello(self):
print("hello")
Person = type('Person',(object, ), {'country':'China', 'hello':hello})
p1 = Person()
print(p1.country)
p1.hello()
1.2 什么是元类
- 元类就是创建类的类。函数type就是是元类。
- Python中一切皆对象。包括整数、字符串、函数以及类都是对象,且都是从type类创建而来。
- 动态生成类,不能控制类是如何生成的。python3 的metaclass可动态创建类。
- 很多Web框架都会使用metaclass 来创建类。掌握元类对理解源代码至关重要。eg: ORM框架类
1.3 自定义元类
metaclass的原理是什么呢? 应用场景: Django(ORM)
元类本身而言,它们其实是很简单的:
- 拦截类的创建
- 修改类
- 返回修改之后的类
单例模式作为软件设计模式中最重要的一个模式之一,需好好掌握。当然,在python3中,单例模式的实现不仅仅是元类,还有其他方式,比如说使用装饰器,重写__new__方法等方式。(大部分情况下,不需要自定义元类)
# 实现单例模式的方法:
# 1. 装饰器
# 2. new魔术方法
# 3. metaclass自定义元类
class Singleton(type):
"""
type(name, bases, attrs)
自定义元类实现单例模式, 父类是type
"""
# 所有类和实例化对象之间的关系; eg: {'Person': Pseron()}
cache = {}
# 1). 为什么是__call__魔术方法?
def __call__(cls):
# 判断类是否已经实例化, 如果没有, 实例化后存储到缓存中。 最后将缓存的信息返回给用户。
if cls not in cls.cache:
cls.cache[cls] = super(Singleton, cls).__call__()
return cls.cache[cls]
# type('Pseron', (), {})
# 创建以各类Person, 指定创建Person类的类(元类)是type.
# 2. metaclass是在做什么? 指定元类为Singleton。
class Person(object, metaclass=Singleton):
# Person = Singleton.__new__(Person, (objects, ), {})
pass
# Person是Singleton元类实例化出的对象, Person()就是对象(), 执行Singleton.__call__魔术方法.
p1 = Person()
p2 = Person()
print(p1, p2)
2. 抽象基类
抽象基类有两个特点:
- 规定继承类必须具有抽象基类指定的方法
- 抽象基类无法实例化
基于上述两个特点,抽象基类主要用于接口设计。
实现抽象基类可以使用内置的abc模块
import abc
class Human(metaclass=abc.ABCMeta):
"""基类, 定义一个抽象类"""
@abc.abstractmethod #规定子类必须有名为introduce的实例方法
def introduce(self):
print("introduce.....")
@abc.abstractmethod
def hello(self):
print('hello')
class Person(Human):
# 1).规定继承类必须具有抽象基类指定的方法
def introduce(self):
print('person')
def hello(self):
print('person hello')
# 2). 抽象基类无法实例化
# h = Human()
p = Person()
p.introduce()
p.hello()
3.自省机制
- 在日常生活中,自省(introspection)是一种自我检查行为。
- 在计算机编程中,自省是指这种能力:检查某些事物以确定它是什么、它知道什么以及它能做什么。自省向程序员提供了极大的灵活性和控制力。
- 例如Python, Ruby, object-C, C++都有自省的能力,这里面的C++的自省的能力最弱,只能够知道是什么类型,而像Python可以知道是什么类型,还有什么属性。
Python中比较常见的自省(introspection)机制(函数用法)有:
dir()、type()、hasattr(),、setattr()、getattr()、delattr()、isinstance(),通过这些函数,我们能够在程序运行时得知对象的类型,判断对象是否存在某个属性,访问对象的属性。
4. __ slots__
动态语言与静态语言的不同?
- 动态语言:可以在运行的过程中,修改代码
- 静态语言:编译时已经确定好代码,运行过程中不能修改
默认情况下每个类都会有一个dict,通过__dict__访问,这个dict维护了这个实例的所有属性,举例如下:
class base(object):
var=9 #类变量
def __init__(self):
pass
b=base()
print b.__dict__
b.x=2 #添加实例变量
print b.__dict__
可见:实例的dict只保持实例的变量,对于类的属性是不保存的,类的属性包括变量和函数。由于每次实例化一个类都要分配一个新的dict,因此存在空间的浪费,因此有了__slots__。
__slots__是一个元组,包括了当前能访问到的属性。
当定义了slots后,slots中定义的变量变成了类的描述符,相当于java,c++中的成员变量声明,类的实例只能拥有slots中定义的变量,不能再增加新的变量。注意:定义了slots后,就不再有dict。如下:
class base(object):
__slots__=('x')
var=8
def __init__(self):
pass
b=base()
b.x=88 #添加实例变量
print b.x
#b.y=99 #无法添加slots之外的变量 (AttributeError: 'base' object has no attribute 'y')
#print b.__dict__ #定义了__slots__后,就不再有__dict__ (AttributeError: 'base' object has no attribute '__dict__')
Python是一门动态语言,可以在运行过程中,修改实例的属性和增删方法。一般,任何类的实例包含一个字典__dict__,
Python通过这个字典可以将任意属性绑定到实例上。有时候我们只想使用固定的属性,而不想任意绑定属性,
这时候我们可以定义一个属性名称集合,只有在这个集合里的名称才可以绑定。__slots__就是完成这个功能的。
class test_slots(object):
__slots__ = 'x', 'y'
def printHello(self):
print('hello!')
class test(object):
def printHello(self):
print('hello')
print(dir(test_slots))
# 可以看到test_slots类结构里面包含__slots__,x,y
print(dir(test))
# test类结构里包含__dict__
print('**************************************')
ts = test_slots()
t = test()
print(dir(ts)) # 可以看到ts实例结构里面包含__slots__,x,y,不能任意绑定属性
print(dir(t)) # t实例结构里包含__dict__,可以任意绑定属性
print('***************************************')
ts.x = 1 # 只能绑定__slots__名称集合里的属性
t.x = 2 # 可以任意绑定属性
print(ts.x, t.x)
ts.y = 11 # 只能绑定__slots__名称集合里的属性
t.y = 22 # 可以任意绑定属性
print(ts.y, t.y)
# ts.z=33 #无法绑定__slots__集合之外的属性(AttributeError: 'test_slots' object has no attribute 'z')
t.z = 33 # 可以任意绑定属性
print(t.z)
运行结果如下:
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'printHello', 'x', 'y']
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'printHello']
***************************************
1 2
11 22
33
如果我们想要限制实例的属性怎么办?
- Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性
- 使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的
import time
from datetime import date
#
# d = date.today()
# print("对象类型: ", type(d)) # <class 'datetime.date'>
# print("判断是否有year这个属性?", hasattr(d, 'year')) # True
# print("判断是否有time这个属性?", hasattr(d, 'time')) # False
# # setattr(d, 'time', '10:10:10') # 报错
class Date(object):
# __slots__ 来限制该对象能添加的属性信息
__slots__ = '__year', '__month', '__day'
def __new__(cls, year, month, day):
self = object.__new__(cls)
self.__year = year
self.__month = month
self.__day = day
return self
@property
def year(self):
return self.__year
@property
def month(self):
return self.__month
@property
def day(self):
return self.__day
@classmethod
def today(cls):
time_t = time.localtime()
return cls(time_t.tm_year, time_t.tm_mon, time_t.tm_mday)
def __str__(self):
return '%s-%s-%s' %(self.__year, self.__month, self.__day)
d = Date(2019, 10, 10)
print("对象类型: ", type(d)) # <class 'datetime.date'>
print("判断是否有year这个属性?", hasattr(d, 'year')) # True
print("判断是否有time这个属性?", hasattr(d, 'time')) # False
# setattr(d, 'time', '10:10:10') # Error
# print('time:', getattr(d, 'time')) # Error
print(Date.today())
综上所述,在确定了类的属性固定的情况下,可以使用__slots__来优化内存。
提醒:不要贸然进行这个优化,把它用在所有地方。这种做法不利于代码维护,而且只有生成数以千计的实例的时候才会有明显效果。