Python的精华——dict

最近身边很多同事、朋友打算学习Python,其实学了几个月的Python,我思之再三,到底这门语言的吸引力在哪?很多的工具包?很多文档?很通俗的语言?

后来我觉得,说语法,是看低了大家的水平,Python之所以成为Python,我觉得很大程度取决于它的精华之一——dict。dict为Python代码的简洁贡献了很多力量,因此,我们编写Python代码的时候,便要多去想想,怎样用dict使得代码看起来更简洁。

记得当初初学Python的时候,我为了一个功能苦苦寻找Switch Case,结果显而易见,被告知Python是没有Switch Case的,为什么?因为Python有很强大的一个数据结构——dict,当我们需要使用多重条件选择的时候,用一个dict通俗、简洁、高效,大家可以看看这段伪代码:

switch(fruit)
{
case 'apple':print('apple is good for health')
....
case 'banana':print('John loves banana')
}
然后在看看Python可以做什么:

fruit = {'apple': 'apple is good for health', 'banana': 'John loves banana'}

print(fruit['apple'])
my_fruit = input()
try:
    print(fruit[my_fruit])
except KeyError:
    pass
而dict还有一个强大的地方,就是调用函数:

class A:
    def __init__(self):
        pass

    @staticmethod
    def func1():
        print('func1')

    def func2(self):
        print('func2')

    def func3(self, *args):
        print('func3', args)

a = A()
order_dict = {
    'cmd1': A.func1,
    'cmd2': a.func2,
    'cmd3': a.func3,
    'exit': sys.exit
}

order_dict['cmd1']()
order_dict['cmd2']()
order_dict['cmd3']('hello', 1, 0.1)
order_dict['exit']()

大家发现这个功能有什么用吗?对了,就是用在controller里面,接收用户输入的一段命令,然后自动查找业务逻辑执行操作。

基于Python的动态类型,dict还能用来保存全局变量:

hyper_para = {
    'learning_rate': 0.01,
    'batch_size': 128,
    'train_data': r'd:\data'
}
看到此处,大家有什么感想?dict很强大?但如果我们只把dict用来简单的查找,便太狭隘,事实上,当我们多去看看各种工具包的文档、源码,我们会发现dict被用来实现很多的设计模式:工厂模式、单例模式等等,这些工具包大多自己用字典维护了一张大表,保存我们通过工厂方法创建的各种实例,并且保证了每个命名对应实例的全局唯一性,然而网络上大多数的工具包的使用案例并没有很好地领悟到工具包设计者的意图,总是使用本地变量以及构造函数来完成代码,并不是说这样不可以,但代码的可读性以及迁移性就差很多,也很可能因为命名的不规范发生无法预知的问题。

这里我用logging来举个例子,大部分人用logging是这样用的:

import logging
my_logger = logging.Logger('logger1')

这样用有什么问题?我们看看这段代码:

import logging
my_logger1 = logging.Logger('logger1')
my_logger2 = logging.Logger('logger1')
print(my_logger1 == my_logger2)
# False
logger1 = logging.getLogger('logger2')
logger2 = logging.getLogger('logger2')
print(logger1 == logger2)
# True
我们可以看到,如果是用构造函数生成的logger,两次构造同名的logger也是不同的实例,我们在不同函数或者不同module共用logger的时候,基本就取决于我们的变量名,除了保持全局唯一还得全局共享,这给编程带来了很多麻烦。

而getLogger()返回的logger,是全局唯一的,其引用保存在logging.manager的一个字典里面,当我们第一次get的时候,manager会新建一个logger并把名字-引用保存在字典里面,当我们在同一进程(logging是线程安全的)再次get的时候,返回的便是之前新建的logger,保证了全局唯一。源码如下:

        try:
            if name in self.loggerDict:
                rv = self.loggerDict[name]
                if isinstance(rv, PlaceHolder):
                    ph = rv
                    rv = (self.loggerClass or _loggerClass)(name)
                    rv.manager = self
                    self.loggerDict[name] = rv
                    self._fixupChildren(ph, rv)
                    self._fixupParents(rv)
            else:
                rv = (self.loggerClass or _loggerClass)(name)
                rv.manager = self
                self.loggerDict[name] = rv
                self._fixupParents(rv)
        finally:
            _releaseLock()
        return rv
我们看到,字典的使用大大简化了维护表格的难度,同时我们应该注意一点,那就是要看到字典里面是否有这个“key”的时候,请多用my_key in dict的做法,这样的写法更通俗、更简洁、更Python!

这样做还有什么好处呢?大家可以看看下面这一行代码:

logging.getLogger(__name__).info(MyInit.message_about(__file__, 'done_train'))

大家可以看到我把“__name__”作为了logger的变量名,这也是logging的文档里面推荐的方式,这样这段代码就可以直接复制粘贴在不同的文件,而保证了每个模块都共享自己模块对应的logger,无论是代码的编写还是维护都非常方便。同时我们在logger输出的时候,还可以定义把logger的name输出,这样我们在看日志的时候还可以清晰看到这段信息是在哪个模块发出来。

除了在logging模块是通过字典实现了工厂方法和单例模式以外,我们可以看到Tensorflow里面的变量也使用了该方法:

sample = tf.nn.xw_plus_b(sample, tf.get_variable(
                    'weight', [n_in, n_out], initializer=tf.random_normal_initializer), tf.get_variable(
                    'biases', [n_out], initializer=tf.zeros_initializer), name='fc')
因此,当我们需要写一些全局有效的模块的时候,便应该思考,如果用dict维护变量的全局唯一性以及提供方便的工厂方法。

说了那么多dict的方法,大家可能觉得有一点不方便的地方,那就是用['key']的方式来选取属性在编程过程中不方便,毕竟IDE是不会判断你字典里面有什么的。为此,collection模块里面有很多高级dict的class,例如nametuple,不要误会,nametuple真不是tuple,它是继承了dict并且实现了用类属性访问字典的方法,但需要先定义:

checkpoint_fixed_parameters = namedtuple('CheckpointFixedParameters', ['conv_pool_layers', fully_connected_layers',
                                                                           'length0', 'n_class', ])
self.fixed_para = self.checkpoint_fixed_parameters(conv_pool_layers, fully_connected_layers, length0, n_class)

然后我们就可以这样使用:

for layer_index, n_out in enumerate(self.fixed_para.fully_connected_layers, start=1):
这样做,在编写代码的时候IDE就会提示字典里的key,这样的做法只适用于我们实现已知字典里面所有的key,例如全局变量表,又或者有多个具有相同结构的字典,我们便简化了赋值时候的步骤,只需要按顺序输入value便可。

collection里面还有另一个比较多用的高级字典就是defaultdict,其中一个特例就是Counter,它们主要是做了一件事情,那就是当my_key not in dict的时候使用dict[key]的时候会报错,但用defaultdict会实例化一个default类型作为value,例如Counter的int,代码很简单,可以参考官方文档里的例子,这里不再详述。

总而言之,Python的精髓就是简,因为简单可以避免出错,也方便其他人理解代码,易于维护,而实现简单又不失高效,dict是一个有力的工具。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值