马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
x
本帖最后由 Stubborn 于 2019-10-28 18:31 编辑
前言:说起来这个魔法函数,刚刚学到对象的魔法函数,大多数都是懵懵懂懂,我就是,反正学完之后,我对魔法函数的应用还只停留在__init__初始化上面,到前些天,我在学习Python核心编程上,才有新的理解,可能看完Python基础课程的[扩展阅读]Python 魔法方法详解我们都会知道,魔法函数可以这么用,但是具体它的应用场景在哪里,应该怎么用?心里面肯定是一团浆糊,以至于到现在,我基本都是用__init__,没有用过其他的魔法函数,好了前言废话说到这里,开始正文。
其实相对魔法函数详细介绍,我更愿意用Python自带的abc模块下的collections模块来介绍,我想会比较好理解,魔法函数可以怎么用,应该这样用。在Python的lib目录下面,可以看到一个‘_collections_abc.py’文件,这里抽出顶部一些做讲解:
__all__ = ["Awaitable", "Coroutine",
"AsyncIterable", "AsyncIterator", "AsyncGenerator",
"Hashable", "Iterable", "Iterator", "Generator", "Reversible",
"Sized", "Container", "Callable", "Collection",
"Set", "MutableSet",
"Mapping", "MutableMapping",
"MappingView", "KeysView", "ItemsView", "ValuesView",
"Sequence", "MutableSequence",
"ByteString",
]
其中__all__包括了所有的抽象基类,我更愿意称呼他们协议,由于这里是讲解魔法函数,这里就不过多讲解,这里就抽取Sequence例子,其他更多的源码,有兴趣的朋友,可以自己去看源码。
[b]点击进Sequence的源码:Ps.不喜欢看源码,可以跳过,直接看下面实例讲解[/b]class Sequence(Reversible, Collection):
"""All the operations on a read-only sequence.
Concrete subclasses must override __new__ or __init__,
__getitem__, and __len__.
"""
__slots__ = ()
@abstractmethod
def __getitem__(self, index):
raise IndexError
def __iter__(self):
i = 0
try:
while True:
v = self[i]
yield v
i += 1
except IndexError:
return
def __contains__(self, value):
for v in self:
if v is value or v == value:
return True
return False
def __reversed__(self):
for i in reversed(range(len(self))):
yield self[i]
def index(self, value, start=0, stop=None):
'''S.index(value, [start, [stop]]) -> integer -- return first index of value.
Raises ValueError if the value is not present.
Supporting start and stop arguments is optional, but
recommended.
'''
if start is not None and start < 0:
start = max(len(self) + start, 0)
if stop is not None and stop < 0:
stop += len(self)
i = start
while stop is None or i < stop:
try:
v = self[i]
if v is value or v == value:
return i
except IndexError:
break
i += 1
raise ValueError
def count(self, value):
'S.count(value) -> integer -- return number of occurrences of value'
return sum(1 for v in self if v is value or v == value)
Sequence.register(tuple)
Sequence.register(str)
Sequence.register(range)
Sequence.register(memoryview)
其父类Reversible源码:class Reversible(Iterable):
__slots__ = ()
@abstractmethod
def __reversed__(self):
while False:
yield None
@classmethod
def __subclasshook__(cls, C):
if cls is Reversible:
return _check_methods(C, "__reversed__", "__iter__")
其Reversible父类Iterable源码:class Iterable(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __iter__(self):
while False:
yield None
@classmethod
def __subclasshook__(cls, C):
if cls is Iterable:
return _check_methods(C, "__iter__")
return NotImplemented
其父类Collection源码:class Collection(Sized, Iterable, Container):
__slots__ = ()
@classmethod
def __subclasshook__(cls, C):
if cls is Collection:
return _check_methods(C, "__len__", "__iter__", "__contains__")
return NotImplemented
其Collection父类Sized源码:class Sized(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __len__(self):
return 0
@classmethod
def __subclasshook__(cls, C):
if cls is Sized:
return _check_methods(C, "__len__")
return NotImplemented
其Collection父类Container源码:class Container(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __contains__(self, x):
return False
@classmethod
def __subclasshook__(cls, C):
if cls is Container:
return _check_methods(C, "__contains__")
return NotImplemented
应用场景:假如我们需要一个类,可支持切片,索引,求长度,元素判断in,以及支持元素反转的“容器”类,知道一个怎么设计吗?如上面源码:其实Sequence就是一个序列类。如果我们自己实现一个,需要实现什么代码,才可以把这个类叫做序列类呢?(Ps:鸭子类型:当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子,在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。)我们应该实现什么方法,才能让这只“鸟”(即将实现的类)看起来会像一只“鸭子”(序列类)
现在反转到上面,看下Sequence的源码,我们设计一个序列类的鸭子,需要实现什么方法才能让这只鸟看起来比较想鸭子!
从Sequence源码看出我们需要实现的魔法方法__getitem__
从父类Reversible知道我们需要实现的魔法方法__iter__
从父类Container知道我们需要实现的魔法方法__contains__
从Reversible的父类Iterable知道我们需要实现的魔法方法__reversed__
从Container的父类Sized知道我们需要实现的魔法方法__len__
当我们的鸟类实现了以上的魔法方法,它将会看起来像一只鸭子类(序列类)
因为上面提到的魔法函数,因为使用@abstractmethod装饰函数将会限制,要求其子类,派生类,在继承是,必须进行重构,否则会报错
class Group(object):
"""支持切片操作,实现这个协议,需要实现一下魔法函数"""
def __init__(self, name, company, staffs):
self.name = name
self.company = company
self.staffs = staffs
def __reversed__(self):
self.staffs.reverse()
def __getitem__(self, item):
"""
注意,如果是切片,item则是一个slice,如果是取值,item是一个int
在对实例化对象进行索引,切片在这里实现对应的逻辑
"""
import numbers
cls = type(self)
if isinstance(item, slice):
return cls(name=self.name, company=self.company, staffs=self.staffs[item])
elif isinstance(item, numbers.Integral):
return cls(name=self.name, company=self.company, staffs=[self.staffs[item]])
def __len__(self):
"""在对实例对象求长度"""
return len(self.staffs)
def __iter__(self):
"""返回一个迭代器,支持被遍历"""
return iter(self.staffs)
def __contains__(self, item):
"""实例对象支持in操作,"""
if item in self.staffs:
return True
return False
如上,我们对我们的【鸟】类,实现了一些方法,让它跑起来,叫起来,像一只【鸭子】,那么这个【鸟】类,就可以被叫做【鸭子类】【Ps:序列类】
实现如上的魔法函数后,我们可以对实例对象,进行切片,索引,迭代,in。但是这个实例不支持一些运行操作,因为我们未实现关于运算的魔法方法。有兴趣的伙伴,可以自己进行实现哦
上面我们实现了一个序列类,我更喜欢叫它序列协议,下面再抽两个出来,讲解下有关于上下文管理协议,以及迭代器协议
上下文管理协议(with 语句)
前言:
我们学到文件管理的时候,应该都会接触到这一句,with open(path,'w') as f: 知道他可以用来写文件。可能你一般不知道,为什么这个可以写文件,为什么要用这个写文件,怎么设计一个类似的函数,或者类,可以实现这样的功能,这里就涉及到我们的上下文管理协议了。
正文:
其实如果你接触之后,就会知道,其实,with语句从根本上来说它就是try excepe finally的简化语句,一个协议都会有对应的魔法函数去支持这个协议,而对上下文管理协议的魔法函数则是这两个__enter__,__exit__,当我们实现这两个魔法函数,可以使我们的【鸟】变的像一只【鸭子】class Open(object):
"""上下文管理器协议,实现enter,exit魔法函数,就可以使用with语句"""
def write(self):
print("this is write")
def __enter__(self):
print('__enter__\t 获取资源')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('__exit__\t释放资源')
print(exc_type)
print(exc_val)
print(exc_tb)
print("-----end-----")
"""返回True表示吞掉异常,False抛出异常"""
return True
看,我们让鸟变成了鸭子(上下文管理协议),现在这个鸭子可以支持with Open() as op: 操作了。Ps.实际上,python为我们提供更简单的方法,函数装饰器,即可实现上下文管理器协议# 使用contextlib简化上下文管理器
import contextlib
@contextlib.contextmanager
def file_open(file, method):
print(f"This is file:{file}")
# __enter__
yield {"file_open":"fp"}
print(f"this is method:{method}")
# __exit__
with file_open("test.txt", "a") as fp:
print(f"this is fp:{fp}")
迭代协议
前言:
说起迭代,可能很多人都会知道,例如,列表->lsit,字符串->srt,元祖->tuple,他们都是可以迭代的对象,有想过,他们为什么可以进行迭代的操作?因为它们实现了Iterable(可迭代)和Iterator(迭代器),实现了迭代协议,进而支持迭代。迭代器是什么?迭代器是访问集合内元素的一种方式,一般用来遍历数据,迭代器和以下标的访问器不一样,迭代器是不能返回的,迭代器提供了一种惰性方式访问数据
正文:
听起来很迷糊,真正接触后,你会觉得,其他它们并不困难,现在我们需要实现一个迭代协议,在此之前,用例子简介下,Iterable(可迭代)和Iterator(迭代器)的不同from collections.abc import Iterable,Iterator
a = [1,2]
print(isinstance(a, Iterator))
print(isinstance(a, Iterable))
iter_rator = iter(a)
print(isinstance(iter_rator, Iterator))
自己实现的迭代器类from collections.abc import Iterable,Iterator
class Company:
def __init__(self, var):
self.var = var
def __iter__(self):
"""优先考虑"""
return Iterators(self.var)
class Iterators(Iterator):
"""迭代器"""
def __init__(self, var):
self.var = var
self._index = 0
def __next__(self):
# 真正返回迭代值的逻辑
try:
result = self.var[self._index]
except IndexError:
raise StopIteration
self._index += 1
return result
if __name__ == '__main__':
company = Company([1,2,3,4,5])
# for 循环做的事
my_itor = iter(company)
while True:
try:
print(next(my_itor))
except StopIteration:
break
for item in company:
print(item)
结尾:相信经过几个例子的介绍,大家对魔法函数,不会那么陌生了。有什么疑问在评论区留言。 |