一些小技巧

基本的高阶语法

一般来说,使用 Python 写代码,大部分时候逃不开 for、else、if、elif、while、and、or、continue、break 这些关键字,以及加减乘除、比大小。
但如果稍微用用 @property@cached_property (这个修饰器的实现前面章节有具体的代码)、hasattrgetattr;处理函数传入参数时,如有必要使用动态参数 *args**kwargs;场景合适,把一个函数也当做一个参数传入到另外函数。那么,这些虽然也是基本的语法,但对于初学者来说,已经是高阶的应用了。

灵活产生函数

当我们将函数作为参数传入给另外的函数时,就已经是 函数式编程了。然而,引入任何一个新的,并且是大的技术性关键词,还是蛮麻烦的事情。就如面向对象编程一样,我们可以压根不谈这个名词,就已经在实际中使用了。况且,仅把函数当参数对待,还远不算 函数式编程,它应该是更学院派的,当然,这仅是我的个人意见。

Python 内置的模块 functools 有个 partial 的函数,我们可以固定某些参数的值,然后使用 partial 生成新的函数。这在实际场景中,是很常用的方法!

参考代码:

>>> from functools import partial
>>> def hello(w1='default word 1', w2='default word 2'):
...     print(w1)
...     print(w2)
...
>>> new_hello = partial(hello, w2='fixed w2')
>>>
>>> new_hello(w1='new w1')
new w1
fixed w2

灵活地继承

我们介绍了 Magic Methods、Monkey Patch,它们也不算复杂,却能让我们在写代码的过程中,『为所欲为』的能力大幅提高。但是 Python 为了避免写代码的人过于『为非作歹』,有些地方还是额外设定了限制,比如一些原生的数据类型上就会有这样的限制。

我们希望一个字符串也可以设定属性,那么一个变量在程序中流转的时候,就可以根据这个属性来做额外的对应了,同时也不会改变数据原来的类型 (还是字符串)。于是,我们尝试这样的代码,只是报错了:

>>> s = u'hello'
>>> s.a = 'my word'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'unicode' object has no attribute 'a'

但只要从 unicode 继承过来,构建一个新的 Class,并实例化之后,就又没有这个问题了:

>>> class UnicodeWithAttr(unicode):
...     pass
...
>>> s = UnicodeWithAttr('hello')
>>> s.a = 'my word'
>>> print(s.a)
my word

简而言之,如果你在写代码的过程中,产生了一个想法,而且这个想法是自然的、能提高效率的,就尝试去实现它,有些时候可能会遇到限制,那么打破它就好了。
如果自己不能主动产生这个想法,那么在写代码的过程中,也偶尔去看看别人的代码(比如说引入到当前项目中其他人的 module),也会逐渐增加这方面的见识。

注意编码

虽然已经特别介绍过 编码 了,但对于新手而言,编码的问题,是必然会遇到的。
除了普通的文本编码之外,还有特定格式的编码,JSON 之类的格式反倒还好一些,出错了就是出错了,很容易发现,而一些人们一直在使用的场景 (比如说 URL),很少注意到其编码的特殊性,反倒容易出现错误。
但且不用特别管它吧,肯定会遇到,也肯定会解决的。
对了,我们其实也可以参考一些既有成熟项目的 utils (可以理解为 通用工具 的意思),比如 Django,参考它的源码 https://github.com/django/django。Django 的 utils.encoding 中有个 smart_str 的函数,还是蛮有用的,在 Django 早先的版本 (Python 2 还大规模流行的时候) 中是叫 smart_unicode 的名字。

Lazy

面对用户,我们谈用户体验。而在技术层面,通常是没有多少用户体验可言的。
写代码时,注定我们成不了懒人,因为要不断思考。而对 『懒』的理解,仁智各见,写代码本身要实现很多思维逻辑上的复用,而 复用 本身就是最大的 偷懒 了。
我个人认为,在实际代码过程中,有什么偷懒的办法,就想尽办法去偷懒,只要别舍了写代码的初心就好了,此时的偷懒,其实都是提高效率的一种方式。万物难两全,偷懒也会有副作用,对这些副作用做一个权衡就好,毕竟,有些场景的限制,副作用也没有多大发挥的空间,也就算不上什么副作用了。

我们先看示例代码吧,再说说是怎么偷懒的:

def get_value_from_data(data, attr, default=None):
    if isinstance(data, dict) and attr in data:
        return data[attr]
    try:
        attrs = attr.split('.')[:25]  # 最多允许25层遍历
        for attr in attrs:
            attr = attr.strip()
            if isinstance(data, dict):
                data = data.get(attr, None)
            else:
                try:
                    data = getattr(data, attr, None)
                except:
                    return data
            if data is None:  # 到头了
                if default is not None:
                    return default
                else:
                    return None
    except RuntimeError:  # 一般是在外部调用 g/request (Flask)
        return None
    return data

在一个项目中,我把数据的调用放在外部的 Web 页面模板中,一个数据可能是一个字典类型,也可能是其它的 Python 对象,dict 要获得其 key 对应的值,可以用 the_dict.get(key, default_value) 的方式,而其它数据对象一般是 the_object.key 或者 getattr(the_object, 'key', default_value),但在 API 的调用中,统一使用了 obj.key 的方式,这样的体验会好很多。
另外的一种情况,是 obj.sub_obj.sub_sub_obj.key 这种连续的调用,有时候比如 sub_obj 已经是 None 了,后面的 sub_sub_obj 肯定就要报错了。
所以,就有了上面这个 get_value_from_data 的函数, 它支持 get_value_from_data(obj, 'sub_obj.sub_sub_obj.key') 这样的调用方式,不管 obj 是一般的 object 还是 dict,同样也不管后面的 sub_obj 是什么类型的对象。
因为这个函数的存在,一些只是 获取数据 的地方,代码量就会急剧减少,不需要做什么额外的判断,只要调用 get_value_from_data 就可以了。当然,上面的 get_value_from_data 的示例代码并不是我提到的项目中的完整代码,完整的逻辑中,会增加一些其它的规则判断。

注释中的 Todo

在 Python 中的注释语法,是以 # 开头的一行内容。
如果在 PyCharm 中,一个注释语法是类似 #todo xxxx 的, PyCharm 中有 Todo 一栏,会自动把当前项目的所有注释内容,转为一个 Todo 列表。所以,在写代码的过程中,有些需要后续再处理的,可以直接使用 Todo 进行注释,后面再完善就可以了。
但也有一个小小的经验,能不留 Todo 的就尽量不要留,但凡注释为 Todo 的,很有可能到最后都不会处理……

PyCharm 右键中的 Refactor

Editor 主区域中,选一个变量,右键点击,试试 Refactor 中选择 Rename。
除了 Refactor 之外,右键点击,另外很有用的是 Find Usages
这些都自己试试,就能看到效果如何,以及会发现效率的巨大提升。还有更多的小技巧,自己在使用 PyCharm 的时候,随着时间累积,慢慢也都会发现。