|
马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
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)
复制代码
结尾:相信经过几个例子的介绍,大家对魔法函数,不会那么陌生了。有什么疑问在评论区留言。 |
|