您当前的位置: 首页 >  Python

使用“特殊方法“构建更加Pythonic的代码

蔚1 发布时间:2019-09-01 23:30:46 ,浏览量:2

熟悉 Python 的朋友都知道,Python 的语法非常的灵活和简洁,如:

  • 判断一个列表的长度直接使用 len(list)
  • 判断一个元素是否在对象中,可以用 item in object
  • 遍历一个列表,可以使用 for item in list

然而,很多人只知道对 str list dict 等 Python 内建的类型可以这样操作,不知道对于我们自定义的类型,其实也可以这样操作。

能够让我们如此操作自定义类型的秘密武器就是 Python 的特殊方法,通过实现特殊方法,我们自定义的数据类型可以表现得跟内置类型一样,从而让我们写出更具表达力的代码,或者说,更 Pythonic 的代码。

熟悉 python 的朋友都知道,python 的语法非常的灵活和简洁,如

  • 判断一个列表的长度直接使用len(list)
  • 判断一个元素是否在对象中,可以用item in object
  • 遍历一个列表,可以使用for item in list

然而,很多人只知道对str list dict等 python 内建的类型可以这样操作,不知道对于我们自定义的类型,其实也可以这样操作。

能够让我们如此操作自定义类型的秘密武器就是 python 的特殊方法,通过实现特殊方法,我们自定义的数据类型可以表现得跟内置类型一样,从而让我们写出更具表达力的代码,或者说,更 Pythonic 的代码。

Python 常用特殊方法 __init__

构造函数,相信这个方法大家一定很熟悉,当我们实例化一个类的时候,构造函数会被自动执行。因此我们可以在构造函数中进行一些实例变量的声明和初始化。

  class MyObject:      def __init__(self):          print("实例化类的时候,会自动执行")  >>> mo = MyObject()  实例化类的时候,会自动执行
__del__

析构函数,这个方法可能有些人不太熟悉,与__init__对应的,当一个实例结束的时候会执行这个函数,因此可以在这个函数中执行一些结束的工作,比如关掉网络请求的会话等。

除了在实例被回收的时候会被自动执行,也可以通过del方法调用,主动释放实例。

  class MyObject:      def __init__(self):          print("实例化类的时候,会自动执行")      def __del__(self):          print("释放类的时候,会自动执行")  >>> mo = MyObject()  实例化类的时候,会自动执行  >>> del mo  释放类的时候,会自动执行

说到实例被释放,你知道 python 是怎样自动进行垃圾回收的吗?欢迎在留言区进行讨论。

__str____repr__

Python 对象的一个基本要求就是它得有合理的字符串表示形式,我们可以通过 __repr____str__ 来满足这个要求。前者方便我们调试和记录日志,后者则是给终端用户看的。这就是数据模型中存在特殊方法 __repr____str__ 的原因。

那么这两个方法有什么异同之处呢,我们通过代码来分析一下:

  # 没有实现__repr__ 和 __str__两个方法  class ReprAndStr:      def __init__(self):          pass  >>> r = ReprAndStr()  >>> r    >>> print(r)    # 直接调用实例和 print 实例,打印的内容可能不太适合人类阅读
  # 实现 __str__,没有实现__repr__  class ReprAndStr:      def __init__(self):          pass      def __str__(self):          return "I am a class for you"  >>> r = ReprAndStr()  >>> r    >>> print(r)  I am a class for you  # 直接调用实例没有任何变化  # print 实例,调用的是__str__方法
  # 实现 __repr__,没有实现__str__  class ReprAndStr:      def __init__(self):          pass      def __repr__(self):          return "I am a class"  >>> r = ReprAndStr()  >>> r  I am a class  >>> print(r)  I am a class  # 直接调用实例,调用的是__repr__方法  # print 实例,调用的也是__repr__方法
  # 同时实现 __repr__ 和 __str__  class ReprAndStr:      def __init__(self):          pass      def __repr__(self):          return "I am a class"      def __str__(self):          return "I am a class for you"  >>> r = ReprAndStr()  >>> r  I am a class  >>> print(r)  I am a class for you  # 直接调用实例,调用的是__repr__方法  # print 实例,调用的是__str__方法

通过上面四组代码的对比,应该比较清晰地得出结论:

  • 实例化一个类后, 如果直接调用这个实例,那么调用的是__repr__方法
  • print 这个实例,调用的是__str__方法,如果__str__方法没有实现,那么会去调用__repr__方法

因此,如果上面两个方法,只实现一个的话,尽量选择__repr__方法

__iter____next__

如果想让自己定义的类可迭代,实现for item in class这样的调用,就需要用到__iter____next__这两个特殊方法。

定义__iter__方法表示这个类是一个迭代器。但是它只在迭代开始的时候运行一次。返回的是对象本身。接下来就是循环调用 __next__,直到遇到 raise StopIteration 为止。

下面使用斐波那契数列的实现来举例说明

  class Fib:      def __init__(self, max):          self.a = 0          self.b = 1          self.max = max      def __iter__(self):          return self      def __next__(self):          fib = self.a          if fib > self.max:              raise StopIteration          self.a, self.b = self.b, self.a + self.b          return fib  >>> fib = Fib(10)  >>> for f in fib:            print(f)  0  1  1  2  3  5  8
__len__

通过在对象中实现__len__方法,即可像list一样使用len()方法调用自定义对象

  class MyList:      def __init__(self):          self.size = 10      def __len__(self):          return self.size  >>> ml = MyList()  >>> len(ml)  10
__contains__

通过实现__contains__方法可以使用in操作符判断一个元素是否在对象中

  class MyIn:      def __init__(self):          self.list = [1, 2, 3]      def __contains__(self, item):          return item in self.list  >>> mi = MyIn()  >>> 1 in mi  True  >>> 4 in mi  False

Tips:如果不实现__contains__方法,只要对象实现了可迭代,也可以使用in操作, 此时会顺序迭代一遍对象进行判断。

在上面的斐波那契数列中,并没有实现__contains__方法,但是它可迭代,因此也可以使用in

  >>> fib = Fib(10)  >>> 3 in fib  True

注意:这里有会一个问题,如果你先实例化了一个fib=Fib(),并且执行了for f in fib的操作,再判断3 in fib,此时结果将是False. 只有重新实例化后,判断结果才是True

  >>> fib = Fib(10)  >>> for f in fib:            print(f)  0  1  1  2  3  5  8  >>> 3 in fib  False  >>> 3 in Fib(10)  True

你知道这个是什么原因吗?欢迎在留言区进行讨论。

__setitem___getitem____delitem_

通过实现__setitem__方法,可以像字典一样给对象赋值,obj[key]= value

通过实现__getitem__方法,可以像字典一样通过[]取值,obj[key]

通过实现__delitem__方法,可以像字典一样使用del删除元素,del obj[key]

  class MyDict:      def __init__(self):          self.dict = dict()      def __setitem__(self, key, value):          self.dict[key] = value      def __getitem__(self, item):          return self.dict[item]      def __delitem__(self, key):          del self.dict[key]      def __contains__(self, item):          return item in self.dict      def __len__(self):          return len(self.dict)  >>> md = MyDict()  >>> md['a'] = 'aaa'  >>> md['a']  aaa  >>> md['b'] = 'bbb'  >>> len(md)  2  >>> 'b' in md  True  >>> del md['b']  >>> len(md)  1  >>> 'b' in md  False

能够像调用内建类型一样调用自己定义的对象,是不是感觉很高大上,像上面这样的特殊方法还有很多,比如:

  • 通过实现__add__特殊方法,可以支持加法运算符+
  • 通过实现__bool__特殊方法,可以支持bool()调用

更多特殊方法的使用请参考官方文档的 Data model.

自定义数据类型与内置类型的差别

虽然我们可以使用 python 的这些特殊方式调用自定义类型,但是他们的执行方式还是有所不同的。

比如,在 python 中,使用len()方法调用内置类型str ,list 等,Cpython 解释器并不会去调用__len__方法,而是直接从一个 C 结构体里读取对象的长度,这样的操作更加高效,而调用自定义类型时,就是去调用自定义对象内的__len__方法。因此,同样的方法,调用内建类型和自定义类型时的执行方式是不一样的。Cpython 解释器对 Python 的内建类型进行了优化。

虽然len()方法可以让 Python 自建的类型走后门,但还是可以让自定义类型也能用此方法调用,这种处理方式也算是在保持内置类型的效率和保证语言的一致性之间找到了一个平衡点。

你还知道哪些对 python 内置类型做了优化的方法吗?欢迎在留言区讨论

本文首发于 GitChat,未经授权不得转载,转载需与 GitChat 联系。

阅读全文: http://gitbook.cn/gitchat/activity/5d6a99621e6dd75f4f43034f

您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

FtooAtPSkEJwnW-9xkCLqSTRpBKX

关注
打赏
1688896170
查看更多评论

蔚1

暂无认证

  • 2浏览

    0关注

    4645博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.0583s