python单例模式和魔术方法的实现
概述
单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)
专注于为中小企业提供做网站、成都网站建设服务,电脑端+手机端+微信端的三站合一,更高效的管理,为中小企业大东免费做网站提供优质的服务。我们立足成都,凝聚了一批互联网行业人才,有力地推动了上千余家企业的稳健成长,帮助中小企业通过网站建设实现规模扩充和转变。
魔法方法是python内置方法,不需要主动调用,存在的目的是为了给python的解释器进行调用,几乎每个魔法方法都有一个对应的内置函数,或者运算符,当我们对这个对象使用这些函数或者运算符时就会调用类中的对应魔法方法,可以理解为重写这些python的内置函数。
01_property商品应用.py
分页显示是一种非常常见的浏览和显示大量数据的方法,属于web编程中最常处理的事件之一。
类属性应用需求: 对于京东商城中显示电脑主机的列表页面,每次请求不可能把数据库
中的所有内容都显示到页面上,而是通过分页的功能局部显示,所以在向数据库中请求
数据时就要显示的指定获取从第start条到第end条的所有数据 这个分页的功能包括:
• 根据用户请求的当前页和总数据条数计算出 start 和 end
• 根据start 和 end 去数据库中请求数据
• 是否有上一页has_prev、下一页has_next
• 上一页prev、下一页next
• 总页数pages, 数据总条数total、当前页信息items
"""
class Pagintor(object):
"""实现商品分页的类"""
def __init__(self, objects_list, page=1, per_page=5):
"""
:param objects_list: 商品列表
:param page: 当前需要显示的页码信息
:param per_page: 每页显示的数据个数
"""
self.objects_list = objects_list
self.page = page
self.per_page = per_page
@property
def start(self):
return (self.page - 1) * self.per_page
@property
def end(self):
return self.page * self.per_page
@property
def total(self):
"""
数据总条数total
:return:
"""
return len(self.objects_list)
@property
def pages(self):
"""
总页数pages
if 总商品数量%每页显示数量==0: 刚好当前页显示满
else: 好友多与的部分, 在计算的结果上面加1
self.total = 5 pages=1
self.total = 6 pages=2
:return:
"""
result = self.total // self.per_page
if self.total % self.per_page == 0:
return result
else:
return result + 1
@property
def has_next(self):
return True if 0 < self.page + 1 <= self.pages else False
@property
def next(self):
next_page = self.page - 1
next_start = (next_page - 1) * self.per_page
next_end = self.page * self.per_page
return self.objects_list[next_start:next_end]
@property
def has_prev(self):
return True if 0 < self.page - 1 <= self.pages else False
@property
def prev(self):
prev_page = self.page - 1
prev_start = (prev_page - 1) * self.per_page
prev_end = self.page * self.per_page
return self.objects_list[prev_start:prev_end]
@property
def items(self):
"""
当前页信息items
:return:
"""
return self.objects_list[self.start:self.end]
if __name__ == '__main__':
# 应用场景二: 某一个属性不能直接返回, 需要计算的, 可以通过property属性实现
goods = ["电脑" + str(i) for i in range(5)]
#需求: 显示第三页时, 开始的索引是? 结束的索引为多少?
pagintor = Pagintor(goods, page=1, per_page=6)
print("第1页的商品信息为: ", goods[pagintor.start:pagintor.end])
print("是否有上一页?", pagintor.has_prev)
print("总页数?", pagintor.pages)
"""
02_property通过函数的方式实现类属性.py
class Person(object):
def __init__(self, name, age):
self.name = name
self.__age = age #私有属性
@property
def is_age_vaild(self):
return 0 < self.__age <= 150
def get_age(self):
if self.is_age_vaild:
return self.__age
else:
raise Exception("年龄不合法")
def set_age(self, age):
if self.is_age_vaild:
self.__age = age
else:
raise Exception("年龄不合法")
def del_age(self):
print("年龄属性删除......")
#类属性 即:在类中定义值为property对象的类属性
age = property(fget=get_age, fset=set_age, fdel=del_age)
if __name__ == '__main__':
p1 = Person("张三", 30)
print(p1.age)
p1.age = 31
print(p1.age)
del p1.age
03_property通过函数的方式实现类属性.py
class Person(object):
def __init__(self, name, age, score):
self.name = name
self.__age = age # 私有属性
self.__score = score
@property
def score(self):
return self.__score
@score.setter
def score(self, score):
self.__score = score
@property
def is_age_vaild(self):
return 0 < self.__age <= 150
@property # 获取age属性时执行的内容
def age(self):
if self.is_age_vaild:
return self.__age
else:
raise Exception("年龄不合法")
@age.setter # 设置age属性时执行的内容
def age(self, age):
if self.is_age_vaild:
self.__age = age
else:
raise Exception("年龄不合法")
@age.deleter # 删除age属性时执行的内容
def age(self):
print("年龄属性删除......")
if __name__ == '__main__':
p1 = Person("张三", 30, 100)
print(p1.age) # 获取年龄(), 执行@property def age(self):
p1.age = 31 # 设置年龄, age=31
print(p1.age)
del p1.age # 删除年龄属性
05_装饰器实现单例模式.py
from functools import wraps
def singleton(cls):
"""
实现单例模式的装饰器
思路: 当实例化对象时, 判断该类是否实例化过对象。
- 如果是, 返回之前实例化的对象。
- 如果不是, 实例化第一个对象, 并将实例化后的对象存储起来(缓存)。
"""
instances = {} # {'Person': obj}
@wraps(cls)
def wrapper(*args, **kwargs):
name = cls.__name__
if instances.get(name):
# 直接返回缓存中的对象
return instances.get(name)
else:
# 第一次实例化对象
obj = cls(*args, **kwargs)
#类名作为key值, 对象作为value值, 存储到instances字典中.
instances[name] = obj
return obj
return wrapper
@singleton
class Person(object):
pass
if __name__ == '__main__':
p1 = Person()
p2 = Person()
#面试题目: ==和is有什么区别?
print("单例模式是否成功?", p1 is p2)
魔术方法
在Python中,所有用""包起来的方法,都称为【魔术方法】(eg:len,init__)。
魔术方法一般是为了让显示器调用的,你自己并不需要调用它们。
特殊属性:
dir
查看属性
返回类或者对象的所有成员名称列表。dir() 函数就是调用dir()。
1). 如果dir([obj]) 参数obj包含方法 dir(),该方法将被调用。
2). 如果Obj 不包含 dir(),该方法将最大限度收集属性信息
python 中new, init, call的区别?
1). new的功能是在生成对象之前执行的内容,接受的参数是cls 类, 负责对象的创建
2). init的功能是在对象生成之后执行的内容, 接受的参数是self 对象, 负责对象的初始化
3). call的功能是在调用对象时执行的内容, 可以模拟函数的行为
06_new方法实现单例模式改进版.py
from datetime import date
class Person(object):
def __new__(cls, *args, **kwargs):
print("判断当前类是否拥有instance属性?", hasattr(cls, 'instance'))
if not hasattr(cls, 'instance'):
cls.instance = super(Person, cls).__new__(cls)
return cls.instance
def __init__(self, name):
self.name = name
p1 = Person("张三")
p2 = Person("张xxx")
print("单例模式是否成功? ", p1 is p2)
06_通过new方法实现单例模式.py
class Person(object):
# 1). 设置类属性, 存储已经创建好的对象。
_instance = None
def __new__(cls, *args, **kwargs):
print("new方法在实例化对象之前执行.....返回对象本身")
#2). 判断是否已经实例化对象?
if cls._instance:
return cls._instance
else:
self = object.__new__(cls)
cls._instance = self
#返回父类object的new方法创建的对象.....
return self
def __init__(self):
print("构造方法实例化对象之后执行......")
if __name__ == '__main__':
p1 = Person()
p2 = Person()
print(p1, p2)"""
07_call.py
1). new的功能是在生成对象之前执行的内容,接受的参数是cls 类, 负责对象的创建
2). init的功能是在对象生成之后执行的内容, 接受的参数是self 对象, 负责对象的初始化
3). call的功能是在调用对象时执行的内容, 可以模拟函数的行为.
"""
class Person(object):
def __new__(cls):
print("__new__")
return object.__new__(cls)
def __init__(self):
print("__init__")
def __call__(self, *args, **kwargs):
print('__call__')
def __del__(self):
# 析构方法: 当对象被删除或者从内存释放时自动执行
print("__del__")
p1 = Person()
p1()
08_call魔术方法实现缓存.py
from functools import lru_cache
class Fib(object):
@lru_cache(maxsize=1000)
def __call__(self, n):
if n in (1, 2):
return 1
else:
return self(n-1) + self(n-2)
fib = Fib()
print(fib(100)) # 1 1 2 3 5 8"""
可视化
类型判断要使用type或isinstance, 不能通过判断print输出是否带引号来判断输出值的类型。
1). str()与repr()都是python中的内置函数,是直接用来格式化字符串的函数。
2). 当使用内置函数str(obj)时, 自动执行obj.str()魔术方法。
3). 当使用内置函数repr(obj)时, 自动执行obj.repr()魔术方法。
4). 当str魔术方法不存在时, 自动执行repr()魔术方法的内容。
类型转换
09_可视化魔术方法.py
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def __int__(self):
return int(self.age)
#def __str__(self):
#return 'Person<%s>' %(self.name)
def __repr__(self):
return 'Person<%s>' %(self.name)
p1 = Person("fentiao", '100')
print(p1)
print(int(p1))
索引与切片
拓展小知识: slice() 函数实现切片对象,主要用在切片操作函数里的参数传递。
索引&切片魔术方法:
setitem:当属性被以索引、切片方式赋值的时候会调用该方法
getitem:一般如果想使用索引、切片访问元素时,就可以在类中定义这个方法
delitem:当使用索引、切片删除属性时调用该方法
10_魔术方法实现索引和切片.py
class Student(object):
def __init__(self, name, scores):
self.name = name
self.scores = scores
def __getitem__(self, index):
"""实现获取索引和切片值的魔术方法"""
print(index)
return self.scores[index]
def __setitem__(self, index, value):
"""实现修改/设置索引和切片值的魔术方法"""
self.scores[index] = value
def __delitem__(self, index):
del self.scores[index]
def __mul__(self, other):
"""重复操作"""
return self.scores * other
def __add__(self, other):
"""连接操作, 传入的时对象"""
return [ item[0]+item[1] for item in zip(self.scores, other.scores)]
def __contains__(self, item):
"""成员操作符"""
return item in self.scores
def __iter__(self):
# iter可以将可迭代对象转换成迭代器(可以调用next方法的)
return iter(self.scores)
stu1 = Student("张三", [100, 90, 100])
stu2 = Student("里斯", [100, 80, 100])
#1). 索引和切片的测试
#print(stu1[1:]) # 获取索引/切片值
#stu1[1:] = (80, 80) # 设置索引/切片对应的value值
#print(stu1.scores)
#del stu1[1:] # 删除索引/切片值
#print(stu1.scores)
##2). 连接、重复和成员操作符
#print(stu1*3)
#print(stu1 + stu2)
#print(150 in stu1)
for item in stu1:
print(item)
with语句安全上下文
with语句操作的对象必须是上下文管理器。那么,到底什么是上下文管理器呢?
1). 简单的理解,拥有 enter() 和 exit() 方法的对象就是上下文管理器。
enter(self):进入上下文管理器自动调用的方法,在 with 执行之前执行。如果 有 as子句,该
方法的返回值被赋值给 as 子句后的变量;该方法可以返回多个值。
exit(self, exc_type, exc_value, exc_traceback):退出上下文管理器自动调用的方法。在
with 执行之后执行(不管有无异常)。
2). 当 with as 操作上下文管理器时,就会在执行语句体之前,先执行上下文管理器的 enter() 方法,
然后再执行语句体,最后执行 exit() 方法。
构建上下文管理器,常见的有 2 种方式:基于类实现和基于生成器实现。
方法一: 装饰器 contextlib.contextmanager,来定义自己所需的基于生成器的上下文管理器
方法二: 基于类的上下文管理器: 只要一个类实现了 enter() 和 exit() 这 2 个方
法,程序就可以使用 with as 语句来管理它
11_魔术方法实现with语句上下文管理器.py
class Connect(object):
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.f = open(self.filename)
return self.f
def __exit__(self, exc_type, exc_val, exc_tb):
print('with语句执行之后......')
self.f.close()
#Connect就是上下文管理器。 拥有 __enter__() 和 __exit__() 方法的对象就是上下文管理器
with Connect('/etc/passwd') as conn:
pass
12_装饰器实现上下文管理器的方法.py
import contextlib
import tempfile
import shutil
@contextlib.contextmanager
def make_temp_dir():
try:
tmp_dir = tempfile.mkdtemp()
yield tmp_dir
finally:
shutil.rmtree(tmp_dir)
with make_temp_dir() as f:
pass
13_比较大小.py
class Int(object):
def __init__(self, number, weight):
self.number = number
self.weight = weight
def __gt__(self, other):
"""判断大于的魔术方法"""
return self.number * self.weight > other.number * other.weight
def __ge__(self, other):
"""判断大于等于的魔术方法"""
return self.number * self.weight >= other.number * other.weight
def __eq__(self, other):
"""判断等于的魔术方法"""
return self.number * self.weight == other.number * other.weight
i1 = Int(20, 3)
i2 = Int(20, 3)
print(i1 > i2)
print(i1 < i2)
print(i1 >= i2)
print(i1 == i2)
print(i1 != i2)
01_call魔术方法实现类装饰器.py
from functools import wraps
import time
def timeit(unit='s'):
def wrapper1(fun): # fun=add
@wraps(fun)
def wrapper(*args, **kwargs):
if unit == 's':
start_time = time.time()
result = fun(*args, **kwargs) # add(1, 2) result=3
end_time = time.time()
print("%s函数运行时间为%.2f s" %(fun.__name__, end_time-start_time))
return result
else:
print("当前功能不支持......")
return wrapper
return wrapper1
#类装饰器: 装饰器需要传递的参数通过__init__传递进入.被装饰函数执行的内容在__call__魔术方法中编写。
class TimeIt(object):
def __init__(self, unit='s'):
self.unit = unit
def __call__(self,fun):
@wraps(fun)
def wrapper(*args, **kwargs):
if self.unit == 's':
start_time = time.time()
result = fun(*args, **kwargs) #add(1, 2) result=3
end_time = time.time()
print("%s函数运行时间为%.2f s" % (fun.__name__, end_time - start_time))
return result
else:
print("当前功能不支持......")
return wrapper
#@timeit(unit='s') #@wrapper1 ==> add = wrapper1(add) ===> add =wrapper
#def add(num1, num2):
#time.sleep(0.333)
#return num1 + num2
"""
@TimeIt(unit='h')
#1). TimeIt_obj = TimeIt(unit='h')
#2). @TimeIt_obj
#3). add=TimeIt_obj(add)
#4). add = wrapper
"""
@TimeIt(unit='s')
def add(num1, num2):
time.sleep(0.333)
return num1 + num2
#add(1, 2) ==> wrapper(1, 2)
add(1, 2)
02_call魔术方法实现偏函数.py
from functools import partial
max_100 = partial(max,10, 100) # 返回对象
print(max_100(1, 2, 3)) # 100"""
03_基于call魔术方法和filter实现文件过滤器.py
from wtforms import StringField,SubmitField
import os
#作为基类/父类
class FileAcceptor(object):
def __init__(self, accepted_extensions):
"""
eg: ['.png', '.jpg']
:param accepted_extensions: 可以接受的扩展名
"""
self.accepted_extensions = accepted_extensions
def __call__(self, filename):
"""
eg: hello.jpg
:param filename: 需要判断的文件名
:return:
"""
#base = 'hello', ext='.jpg'
base, ext = os.path.splitext(filename)
return ext in self.accepted_extensions
#子类
class ImageFileAcceptor(FileAcceptor):
def __init__(self):
image_ext = ('.jpg', '.jepg', '.png')
super(ImageFileAcceptor, self).__init__(image_ext)
#子类
class ExcelFileAcceptor(FileAcceptor):
def __init__(self):
image_ext = ('.xls', '.xlsx')
super(ExcelFileAcceptor, self).__init__(image_ext)
if __name__ == '__main__':
filenames = [
'hello.jpg',
'hello.xls',
'hello.txt'
]
"""
1). ImageFileAcceptor() 实例化对象, 执行__new__和__init__魔术方法。
2). imagefileacceptor_obj
3). imagefileacceptor_obj('hello.jpg') True
3). imagefileacceptor_obj('hello.xls') False
3). imagefileacceptor_obj('hello.txt') False
4). ['hello.jpg']
"""
images_file = filter(ImageFileAcceptor(), filenames)
excels_file = filter(ExcelFileAcceptor(), filenames)
print(list(images_file))
print(list(excels_file))
04_通过分支语句动态的创建类.py
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__)
05_通过type动态地创建类.py
#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()
魔术方法汇总
基本的魔法方法
有关属性的魔术方法
比较操作符
算数运算符
反运算
增量赋值运算
一元操作符
类型转换
上下文管理(with 语句)
容器类型
元类
类也是对象
Python一切皆对象
Linux一切皆文件
元类是类的类,是类的模板
元类的实例为类,正如类的实例为对象。
类的本质是对象, 于是可以对类做如下的操作:
- 你可以将它赋值给一个变量
- 你可以拷⻉它
- 你可以为它增加属性
- 你可以将它作为函数参数进行传递
1). 元类就是创建类的类。函数type就是是元类。
2). Python中一切皆对象。包括整数、字符串、函数以及类都是对象,且都是从type类创建而来。
3). 动态生成类,不能控制类是如何生成的。python3 的metaclass可动态创建类。
4). 很多Web框架都会使用metaclass 来创建类。掌握元类对理解源代码至关重要。eg: ORM框架类06_metaclass自定义元类.py
#实现单例模式的方法:
#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)
#99%情况不需要自己自定义元类。
抽象基类
抽象基类有两个特点:
1.规定继承类必须具有抽象基类指定的方法
2.抽象基类无法实例化
基于上述两个特点,抽象基类主要用于接口设计
实现抽象基类可以使用内置的abc模块
07_抽象基类.py
import abc
class Human(metaclass=abc.ABCMeta):
"""基类, 定义一个抽象类"""
@abc.abstractmethod
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()
动态语言与静态语言的不同?
- 动态语言:可以在运行的过程中,修改代码
- 静态语言:编译时已经确定好代码,运行过程中不能修改
slots
如果我们想要限制实例的属性怎么办? - Python允许在定义class的时候,定义一个特殊的slots变量,来限制该class实例能添加的属性
- 使用slots要注意,slots定义的属性仅对当前类实例起作用,对继承的子类是不起作用的
08_slots限制对象属性.py
import time
from datetime import date
#d = date.today()
#print("对象类型: ", type(d)) #
#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)) #
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())
09_垃圾回收机制.py
#1). 整数在程序中的使用非常广泛,Python为了优化速度,使用了小整数对象池,
#避免为整数频繁申请和销毁内存空间。
#2). Python对小整数的定义是[-5,257)
>>> a = 1
>>> id(a)
139883638752032
>>> b = 1
>>> id(b)
139883638752032
>>> c = 257
>>> d = 257
>>> id(c), id(d)
(139883633580400, 139883633580432)
>>> e=-5;f=-5
>>> id(e), id(f)
(139883638751840, 139883638751840)
#********************************2. 字符串驻留机制 ********************************
#1). string interning(字符串驻留): 它通过维护一个字符串常量池(string intern pool),
#从而试图只保存唯一的字符串对象,达到既高效又节省内存地处理字符串的目的。
>>> a = 'hello'
>>> b = 'hello'
>>> id(a), id(b)
(139883511138480, 139883511138480)
>>> c = 'pythonchjdshfcejhfkjrehfkjrehfkjrehfregjrkhgkjrg'
>>> d = 'pythonchjdshfcejhfkjrehfkjrehfkjrehfregjrkhgkjrg'
>>> id(c),id(d)
(139883633099360, 139883633099360)
#2). 字符串(含有空格),不可修改,没开启intern机制,不共用对象,引用计数为0,销毁。
>>> a = 'a b'
>>> b = 'a b'
>>> id(a), id(b)
(139883511138608, 139883511138544)
10_引用计数机制.py
#导致引用计数+1的情况
>>> #1). 对象被创建,例如a=23
...
>>> name = 'fentiao'
>>>
>>> #2). 对象被作为参数,传入到一个函数中
...
>>> import sys
>>> sys.getrefcount(name)
2
>>> #3). 对象被引用,例如b=a
...
>>> cat_name = name
>>> sys.getrefcount(name)
3
>>> #4). 对象作为一个元素,存储在容器中,例如list1=[a,a]
...
>>> l = [name, 'hello', 'python']
>>> sys.getrefcount(name)
4
#***************************导致引用计数-1的情况*************************
>>> #1). 对象的别名被显式销毁,例如del a
...
>>> del cat_name
>>> sys.getrefcount(name)
3
>>> #2). 对象的别名被赋予新的对象,例如a=24
>>> name1 = name
>>> sys.getrefcount(name)
4
>>> name1 = 'hello'
>>> sys.getrefcount(name)
3
>>> #4). 对象所在的容器被销毁,或从容器中删除对象
...
>>> l
['fentiao', 'hello', 'python']
>>> del l[0]
>>> sys.getrefcount(name)
2
#****************************gc模块使用***********************************
#1). 分代回收的频率
>>> gc.get_threshold()
(700, 10, 10)
>>>gc.set_threshold(700, 90, 90)
KeyboardInterrupt
#2). 垃圾回收机制是否开启
>>>gc.isenabled()
True
>>>gc.disable()
>>>gc.isenabled()
False
>>> gc.enable()
>>>gc.isenabled()
True
当前文章:python单例模式和魔术方法的实现
本文链接:http://cdiso.cn/article/poeppj.html