鱼C论坛

 找回密码
 立即注册
查看: 2198|回复: 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’文件,这里抽出顶部一些做讲解:
__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)

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

本帖被以下淘专辑推荐:

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

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

占楼备用
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

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

从Reversible的父类Iterable知道我们需要实现的魔法方法__reversed__
从Container的父类Sized知道我们需要实现的魔法方法__len__
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

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

因为上面提到的魔法函数,因为使用@abstractmethod装饰函数将会限制,要求其子类,派生类,在继承是,必须进行重构,否则会报错
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-11-14 15:32:23 | 显示全部楼层
写的很长,记号一下,有时候再来细细品味。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

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

本版积分规则

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

GMT+8, 2024-11-24 13:05

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

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