什么样才算是Pythonic的代码?

      如果说Python最迷人的特性是什么,我的回答一定是“一致性”。什么是“一致性”?举个例子,Python中“万物皆对象”,没有Java那种“原生类型”和“封装类型”所造成的割裂感。这种一致性体现在了Python的方方面面,是Python的设计哲学。个人认为,能保持这种一致性的代码,就能称为“Pythonic”。那么怎样才能写出这样的代码呢?一个方式就是采用“魔术方法”(magic method)。

      所谓魔术方法,表现形式上,就是以双下划线开头和结尾的方法,最常见的就是__init__() 方法。魔术方法是供解释器调用的,我们一般不应该直接使用魔术方法,也不应该自创一个非标准的魔术方法;也就是说,我们写出的代码中,不应该有类似于ClassA.__foo__() 的使用方式。

     下面我们来看一下有哪些常见的魔术方法,以及如何使用这些方法,使代码更加 “Pythonic”

1. __len__

求一个对象的长度,Python提供了一个内建的方法:len() 。无论是字符串对象,还是list 对象,都可以用len(obj)来获取长度(作为对比,Java中求数组的长度用lengh属性,求String对象的长度则为length()方法,显得很“割裂”)。其实在使用len()时,解释器会调用对象中的__len()__方法。所以为保持一致性,最好也能用len()求自定义对象的长度,我们可以在类的内部,实现__len()__方法。

Class Train:
    def __init__(self):
        self.length = 50
        
    def __len__(self):
        return self.length

我们定义了一个 Trian 类,并实现了__len()__方法,那么就可以用len()来求对象的长度:

>>> train = Train()
>>> print(len(train))
>>> 50

这样的结果是,对于求长度这个操作,自定义的类的行为和内置类型(str, list等)完全一样,使用者不用了解是用train.get_size() 还是 train.length 或者其他什么方法、属性,只需调用len()这个通用的方法。当然,如果你非要实现类似于 get_size() 的方法,也可以获取对象的长度,但远没有使用len()简洁优雅。这就是上文所说的”一致性“。

2. __getitem__ 与 __setitem__

对字典对象的取值或修改操作,都会用到[]符号,这背后实际上就是__getitem____setitem__方法,所以我们可以自行实现这两个方法,以获得和字典对象类似的行为。

class Box:
    def __setitem__(self, key, value):
        setattr(self, key, value)
        
    def __getitem__(self, key):
        try:
            return getattr(self, key)
        except AttributeError:
            raise KeyError(key)
 
 >>> b=Box()
 >>> b["name"]="box"
 >>> b["name"]
 'box'

例子中,Box对象实现了__getitem____setitem__方法,用中括号操作符时,表现行为和内置的字典类型一致。

3. __enter__ 与 __exit__

这一对魔术方法用于实现上下文管理器。比如我们打开一个文件时,为了防止忘记关闭,往往用上下文管理器的方式:

with open("test.txt") as f:
    pass

对于自己创建的对象,可以实现__enter____exit__方法,其中在with语句开始时,__enter__方法被调用,退出with语句块时,__exit__方法被调用。我们来看一个实例,来自Python的requests库源码:

# requests.sessions
class Session(SessionRedirectMixin):
...
    def __enter__(self):
        return self
       
    def __exit__(self,*args):
    	self.close()
...

Session类的__enter__方法返回自身实例,__exit__方法调用关闭方法,确保退出时能正确关闭会话,所以用with语法调用时,无需关注会话关闭的问题。使用方法同open函数:

>>> with Session() as s:
>>>     s.get('https://httpbin.org/get')
4. __call__

在Python中,只要是可调用对象,就能像函数一样调用该对象,其行为跟普通函数没有区别,其实函数本身就是一个可调用对象。如果自己想构建一个可调用对象,实现__call__方法即可。我们看一下来自 Werkzeug 库的 Response 对象:

class Response(object): 
    ...
    def __call__(self, environ, start_response):
        app_iter, status, headers = self.get_wsgi_response(environ)
	start_response(status, headers)
	return app_iter

该类实现了__call__方法,当调用该类的对象时,就像调用一个普通函数,传入__call__方法定义的参数:

>>> response = Response()
# 像函数一样调用 response:
>>> response(environ,start_response)
5. __iter__ 与 __next__

这一对魔术方法与迭代器可迭代对象密切相关。简单来说,实现了__iter__方法的即是可迭代对象,实现了__next__方法的即为迭代器。我们可以利用 for 循环来遍历可迭代对象,在实际使用中更加方便:

>>> for i in MyIterable():
...    print(i)
...

关于迭代器与可迭代对象,可以参考我的另一篇文章Python中的迭代器与可迭代对象,这里不再赘述。

6. 小结

我们在编写代码的时候,应该考虑到语言本身的特性,尽量贴近这种特性。对于Python,我们应该多利用魔术方法,写出更加优雅、一致的代码,提供符合Python习惯的接口。魔术方法还有很多,比如跟运算符相关的就有一大串,本文仅仅是列出了常见的几个,仅供参考。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值