鱼C论坛

 找回密码
 立即注册
查看: 2511|回复: 4

[技术交流] Python魔法方法详解与应用

[复制链接]
发表于 2019-10-27 23:30:11 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 Stubborn 于 2019-10-28 18:31 编辑



前言:说起来这个魔法函数,刚刚学到对象的魔法函数,大多数都是懵懵懂懂,我就是,反正学完之后,我对魔法函数的应用还只停留在__init__初始化上面,到前些天,我在学习Python核心编程上,才有新的理解,可能看完Python基础课程的[扩展阅读]Python 魔法方法详解我们都会知道,魔法函数可以这么用,但是具体它的应用场景在哪里,应该怎么用?心里面肯定是一团浆糊,以至于到现在,我基本都是用__init__,没有用过其他的魔法函数,好了前言废话说到这里,开始正文。


其实相对魔法函数详细介绍,我更愿意用Python自带的abc模块下的collections模块来介绍,我想会比较好理解,魔法函数可以怎么用,应该这样用。在Python的lib目录下面,可以看到一个‘_collections_abc.py’文件,这里抽出顶部一些做讲解:

  1. __all__ = ["Awaitable", "Coroutine",
  2.            "AsyncIterable", "AsyncIterator", "AsyncGenerator",
  3.            "Hashable", "Iterable", "Iterator", "Generator", "Reversible",
  4.            "Sized", "Container", "Callable", "Collection",
  5.            "Set", "MutableSet",
  6.            "Mapping", "MutableMapping",
  7.            "MappingView", "KeysView", "ItemsView", "ValuesView",
  8.            "Sequence", "MutableSequence",
  9.            "ByteString",
  10.            ]
复制代码


其中__all__包括了所有的抽象基类,我更愿意称呼他们协议,由于这里是讲解魔法函数,这里就不过多讲解,这里就抽取Sequence例子,其他更多的源码,有兴趣的朋友,可以自己去看源码。

[b]点击进Sequence的源码:Ps.不喜欢看源码,可以跳过,直接看下面实例讲解[/b]
  1. class Sequence(Reversible, Collection):

  2.     """All the operations on a read-only sequence.

  3.     Concrete subclasses must override __new__ or __init__,
  4.     __getitem__, and __len__.
  5.     """

  6.     __slots__ = ()

  7.     @abstractmethod
  8.     def __getitem__(self, index):
  9.         raise IndexError

  10.     def __iter__(self):
  11.         i = 0
  12.         try:
  13.             while True:
  14.                 v = self[i]
  15.                 yield v
  16.                 i += 1
  17.         except IndexError:
  18.             return

  19.     def __contains__(self, value):
  20.         for v in self:
  21.             if v is value or v == value:
  22.                 return True
  23.         return False

  24.     def __reversed__(self):
  25.         for i in reversed(range(len(self))):
  26.             yield self[i]

  27.     def index(self, value, start=0, stop=None):
  28.         '''S.index(value, [start, [stop]]) -> integer -- return first index of value.
  29.            Raises ValueError if the value is not present.

  30.            Supporting start and stop arguments is optional, but
  31.            recommended.
  32.         '''
  33.         if start is not None and start < 0:
  34.             start = max(len(self) + start, 0)
  35.         if stop is not None and stop < 0:
  36.             stop += len(self)

  37.         i = start
  38.         while stop is None or i < stop:
  39.             try:
  40.                 v = self[i]
  41.                 if v is value or v == value:
  42.                     return i
  43.             except IndexError:
  44.                 break
  45.             i += 1
  46.         raise ValueError

  47.     def count(self, value):
  48.         'S.count(value) -> integer -- return number of occurrences of value'
  49.         return sum(1 for v in self if v is value or v == value)

  50. Sequence.register(tuple)
  51. Sequence.register(str)
  52. Sequence.register(range)
  53. Sequence.register(memoryview)
复制代码


其父类Reversible源码:
  1. class Reversible(Iterable):

  2.     __slots__ = ()

  3.     @abstractmethod
  4.     def __reversed__(self):
  5.         while False:
  6.             yield None

  7.     @classmethod
  8.     def __subclasshook__(cls, C):
  9.         if cls is Reversible:
  10.             return _check_methods(C, "__reversed__", "__iter__")
复制代码


其Reversible父类Iterable源码:
  1. class Iterable(metaclass=ABCMeta):

  2.     __slots__ = ()

  3.     @abstractmethod
  4.     def __iter__(self):
  5.         while False:
  6.             yield None

  7.     @classmethod
  8.     def __subclasshook__(cls, C):
  9.         if cls is Iterable:
  10.             return _check_methods(C, "__iter__")
  11.         return NotImplemented
复制代码


其父类Collection源码:
  1. class Collection(Sized, Iterable, Container):

  2.     __slots__ = ()

  3.     @classmethod
  4.     def __subclasshook__(cls, C):
  5.         if cls is Collection:
  6.             return _check_methods(C,  "__len__", "__iter__", "__contains__")
  7.         return NotImplemented
复制代码


其Collection父类Sized源码:
  1. class Sized(metaclass=ABCMeta):

  2.     __slots__ = ()

  3.     @abstractmethod
  4.     def __len__(self):
  5.         return 0

  6.     @classmethod
  7.     def __subclasshook__(cls, C):
  8.         if cls is Sized:
  9.             return _check_methods(C, "__len__")
  10.         return NotImplemented
复制代码


其Collection父类Container源码:
  1. class Container(metaclass=ABCMeta):

  2.     __slots__ = ()

  3.     @abstractmethod
  4.     def __contains__(self, x):
  5.         return False

  6.     @classmethod
  7.     def __subclasshook__(cls, C):
  8.         if cls is Container:
  9.             return _check_methods(C, "__contains__")
  10.         return NotImplemented
复制代码


应用场景:假如我们需要一个类,可支持切片,索引,求长度,元素判断in,以及支持元素反转的“容器”类,知道一个怎么设计吗?如上面源码:其实Sequence就是一个序列类。如果我们自己实现一个,需要实现什么代码,才可以把这个类叫做序列类呢?(Ps:鸭子类型:当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子,在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。)我们应该实现什么方法,才能让这只“鸟”(即将实现的类)看起来会像一只“鸭子”(序列类)

现在反转到上面,看下Sequence的源码,我们设计一个序列类的鸭子,需要实现什么方法才能让这只鸟看起来比较想鸭子!

Sequence源码看出我们需要实现的魔法方法__getitem__
从父类Reversible知道我们需要实现的魔法方法__iter__
从父类Container知道我们需要实现的魔法方法__contains__

从Reversible的父类Iterable知道我们需要实现的魔法方法__reversed__
从Container的父类Sized知道我们需要实现的魔法方法__len__

当我们的鸟类实现了以上的魔法方法,它将会看起来像一只鸭子类(序列类)

因为上面提到的魔法函数,因为使用@abstractmethod装饰函数将会限制,要求其子类,派生类,在继承是,必须进行重构,否则会报错

  1. class Group(object):
  2.     """支持切片操作,实现这个协议,需要实现一下魔法函数"""
  3.     def __init__(self, name, company, staffs):
  4.         self.name = name
  5.         self.company = company
  6.         self.staffs = staffs

  7.     def __reversed__(self):
  8.         self.staffs.reverse()

  9.     def __getitem__(self, item):
  10.         """
  11.         注意,如果是切片,item则是一个slice,如果是取值,item是一个int
  12.         在对实例化对象进行索引,切片在这里实现对应的逻辑
  13.         """
  14.         import numbers
  15.         cls = type(self)
  16.         if isinstance(item, slice):
  17.             return cls(name=self.name, company=self.company, staffs=self.staffs[item])
  18.         elif isinstance(item, numbers.Integral):
  19.             return cls(name=self.name, company=self.company, staffs=[self.staffs[item]])

  20.     def __len__(self):
  21.         """在对实例对象求长度"""
  22.         return len(self.staffs)

  23.     def __iter__(self):
  24.         """返回一个迭代器,支持被遍历"""
  25.         return iter(self.staffs)

  26.     def __contains__(self, item):
  27.         """实例对象支持in操作,"""
  28.         if item in self.staffs:
  29.             return True
  30.         return False
复制代码


如上,我们对我们的【鸟】类,实现了一些方法,让它跑起来,叫起来,像一只【鸭子】,那么这个【鸟】类,就可以被叫做【鸭子类】【Ps:序列类】
实现如上的魔法函数后,我们可以对实例对象,进行切片,索引,迭代,in。但是这个实例不支持一些运行操作,因为我们未实现关于运算的魔法方法。有兴趣的伙伴,可以自己进行实现哦

上面我们实现了一个序列类,我更喜欢叫它序列协议,下面再抽两个出来,讲解下有关于上下文管理协议,以及迭代器协议


上下文管理协议(with 语句)
前言:
        我们学到文件管理的时候,应该都会接触到这一句,with open(path,'w') as f: 知道他可以用来写文件。可能你一般不知道,为什么这个可以写文件,为什么要用这个写文件,怎么设计一个类似的函数,或者类,可以实现这样的功能,这里就涉及到我们的上下文管理协议了。

正文:
        其实如果你接触之后,就会知道,其实,with语句从根本上来说它就是try excepe finally的简化语句,一个协议都会有对应的魔法函数去支持这个协议,而对上下文管理协议的魔法函数则是这两个__enter__,__exit__,当我们实现这两个魔法函数,可以使我们的【鸟】变的像一只【鸭子】
  1. class Open(object):
  2.     """上下文管理器协议,实现enter,exit魔法函数,就可以使用with语句"""

  3.     def write(self):
  4.         print("this is write")

  5.     def __enter__(self):
  6.         print('__enter__\t 获取资源')
  7.         return self

  8.     def __exit__(self, exc_type, exc_val, exc_tb):
  9.         print('__exit__\t释放资源')
  10.         print(exc_type)
  11.         print(exc_val)
  12.         print(exc_tb)
  13.         print("-----end-----")
  14.         """返回True表示吞掉异常,False抛出异常"""
  15.         return True
复制代码

看,我们让鸟变成了鸭子(上下文管理协议),现在这个鸭子可以支持with Open() as op: 操作了。Ps.实际上,python为我们提供更简单的方法,函数装饰器,即可实现上下文管理器协议
  1. # 使用contextlib简化上下文管理器
  2. import contextlib

  3. @contextlib.contextmanager
  4. def file_open(file, method):
  5.     print(f"This is file:{file}")
  6.     # __enter__
  7.     yield {"file_open":"fp"}
  8.     print(f"this is method:{method}")
  9.     # __exit__


  10. with file_open("test.txt", "a") as fp:
  11.     print(f"this is fp:{fp}")
复制代码



迭代协议
前言:
        说起迭代,可能很多人都会知道,例如,列表->lsit,字符串->srt,元祖->tuple,他们都是可以迭代的对象,有想过,他们为什么可以进行迭代的操作?因为它们实现了Iterable(可迭代)和Iterator(迭代器),实现了迭代协议,进而支持迭代。迭代器是什么?迭代器是访问集合内元素的一种方式,一般用来遍历数据,迭代器和以下标的访问器不一样,迭代器是不能返回的,迭代器提供了一种惰性方式访问数据

正文:
        听起来很迷糊,真正接触后,你会觉得,其他它们并不困难,现在我们需要实现一个迭代协议,在此之前,用例子简介下,Iterable(可迭代)和Iterator(迭代器)的不同
  1. from collections.abc import Iterable,Iterator
  2. a = [1,2]
  3. print(isinstance(a, Iterator))
  4. print(isinstance(a, Iterable))

  5. iter_rator = iter(a)
  6. print(isinstance(iter_rator, Iterator))
复制代码



自己实现的迭代器类
  1. from collections.abc import Iterable,Iterator
  2. class Company:

  3.     def __init__(self, var):
  4.         self.var = var

  5.     def __iter__(self):
  6.         """优先考虑"""
  7.         return Iterators(self.var)


  8. class Iterators(Iterator):
  9.     """迭代器"""
  10.     def __init__(self, var):
  11.         self.var = var
  12.         self._index = 0

  13.     def __next__(self):
  14.         # 真正返回迭代值的逻辑
  15.         try:
  16.             result = self.var[self._index]
  17.         except IndexError:
  18.             raise StopIteration
  19.         self._index += 1
  20.         return result

  21. if __name__ == '__main__':
  22.     company = Company([1,2,3,4,5])
  23.     # for 循环做的事
  24.     my_itor = iter(company)
  25.     while True:
  26.         try:
  27.             print(next(my_itor))
  28.         except StopIteration:
  29.             break

  30.     for item in company:
  31.         print(item)
复制代码


结尾:相信经过几个例子的介绍,大家对魔法函数,不会那么陌生了。有什么疑问在评论区留言。

本帖被以下淘专辑推荐:

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2019-10-27 23:52:56 | 显示全部楼层
本帖最后由 Stubborn 于 2019-10-28 18:32 编辑

占楼备用
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-11-6 14:59:38 | 显示全部楼层
请问能详细解释下如何知道要选下面的魔法方法,用了之后里面内容是否要重写
从Sequence源码看出我们需要实现的魔法方法__getitem__
从父类Reversible知道我们需要实现的魔法方法__iter__
从父类Container知道我们需要实现的魔法方法__contains__

从Reversible的父类Iterable知道我们需要实现的魔法方法__reversed__
从Container的父类Sized知道我们需要实现的魔法方法__len__
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2019-11-6 16:10:12 | 显示全部楼层
cs_sunsky 发表于 2019-11-6 14:59
请问能详细解释下如何知道要选下面的魔法方法,用了之后里面内容是否要重写
从Sequence源码看出我们需要实 ...

因为上面提到的魔法函数,因为使用@abstractmethod装饰函数将会限制,要求其子类,派生类,在继承是,必须进行重构,否则会报错
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-11-14 15:32:23 | 显示全部楼层
写的很长,记号一下,有时候再来细细品味。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-5-26 06:07

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表