分析一套源代码的代码规范和风格并讨论如何改进优化代码

  我的工程实践选题是《针对领域知识的中文知识图谱自动化构建》,我将选取用于知识图谱的分布式表示计算框架OpenKE,代码目录如下图所示:

                              

 

  我们可以看到源代码目录中包括多个文件夹,其中主要为:

base:基础操作代码,包括文件读写,获取样本数,设置工作目录等

benchmarks:一些用于评测算法性能的数据集

config:设置运行所需的参数

examples:将本框架运用于不同数据集上的代码示例

models:本框架中包含的一些算法模型

release:由base打包得到的动态库

res:程序运行得到的一些结果

  除此之外我们可以看到在主目录里还有一些文件,包括许可证,框架使用样例,编译文件,说明文件等。可以看出整个工程的组织比较清晰,文件夹命名比较规范。

  下面我们来看一段代码:

 1 #coding:utf-8
 2 import numpy as np
 3 import tensorflow as tf
 4 from .Model import Model
 5 
 6 if tf.__version__ > '0.12.1':
 7     matmul_func = tf.matmul
 8 else:
 9     matmul_func = tf.batch_matmul
10 
11 class TransR(Model):
12     r'''
13     TransR first projects entities from entity space to corresponding relation space 
14     and then builds translations between projected entities. 
15     '''
16     def _transfer(self, transfer_matrix, embeddings):
17         return matmul_func(embeddings, transfer_matrix)
18 
19     def _calc(self, h, t, r):
20         h = tf.nn.l2_normalize(h, -1)
21         t = tf.nn.l2_normalize(t, -1)
22         r = tf.nn.l2_normalize(r, -1)
23         return abs(h + r - t)
24 
25     def embedding_def(self):
26         #Obtaining the initial configuration of the model
27         config = self.get_config()
28         #Defining required parameters of the model, including embeddings of entities and relations, and mapping matrices
29         self.ent_embeddings = tf.get_variable(name = "ent_embeddings", shape = [config.entTotal, config.ent_size], initializer = tf.contrib.layers.xavier_initializer(uniform = False))
30         self.rel_embeddings = tf.get_variable(name = "rel_embeddings", shape = [config.relTotal, config.rel_size], initializer = tf.contrib.layers.xavier_initializer(uniform = False))
31         self.transfer_matrix = tf.get_variable(name = "transfer_matrix", shape = [config.relTotal, config.ent_size * config.rel_size], initializer = tf.contrib.layers.xavier_initializer(uniform = False))
32         self.parameter_lists = {"ent_embeddings":self.ent_embeddings, \
33                                 "rel_embeddings":self.rel_embeddings, \
34                                 "transfer_matrix":self.transfer_matrix}
35 
36     def loss_def(self):
37         #Obtaining the initial configuration of the model
38         config = self.get_config()
39         #To get positive triples and negative triples for training
40         #The shapes of pos_h, pos_t, pos_r are (batch_size, 1)
41         #The shapes of neg_h, neg_t, neg_r are (batch_size, negative_ent + negative_rel)
42         pos_h, pos_t, pos_r = self.get_positive_instance(in_batch = True)
43         neg_h, neg_t, neg_r = self.get_negative_instance(in_batch = True)
44         #Embedding entities and relations of triples, e.g. pos_h_e, pos_t_e and pos_r_e are embeddings for positive triples
45         pos_h_e = tf.nn.embedding_lookup(self.ent_embeddings, pos_h)
46         pos_t_e = tf.nn.embedding_lookup(self.ent_embeddings, pos_t)
47         pos_r_e = tf.nn.embedding_lookup(self.rel_embeddings, pos_r)
48         neg_h_e = tf.nn.embedding_lookup(self.ent_embeddings, neg_h)
49         neg_t_e = tf.nn.embedding_lookup(self.ent_embeddings, neg_t)
50         neg_r_e = tf.nn.embedding_lookup(self.rel_embeddings, neg_r)
51         #Getting the required mapping matrices
52         pos_matrix = tf.reshape(tf.nn.embedding_lookup(self.transfer_matrix, pos_r), [-1, config.ent_size, config.rel_size])
53         #Calculating score functions for all positive triples and negative triples
54         p_h = self._transfer(pos_matrix, pos_h_e)
55         p_t = self._transfer(pos_matrix, pos_t_e)
56         p_r = pos_r_e
57         if config.negative_rel == 0:
58             n_h = self._transfer(pos_matrix, neg_h_e)
59             n_t = self._transfer(pos_matrix, neg_t_e)
60             n_r = neg_r_e
61         else:
62             neg_matrix = tf.reshape(tf.nn.embedding_lookup(self.transfer_matrix, neg_r), [-1, config.ent_size, config.rel_size])
63             n_h = self._transfer(neg_matrix, neg_h_e)
64             n_t = self._transfer(neg_matrix, neg_t_e)
65             n_r = neg_r_e
66         #The shape of _p_score is (batch_size, 1, hidden_size)
67         #The shape of _n_score is (batch_size, negative_ent + negative_rel, hidden_size)
68         _p_score = self._calc(p_h, p_t, p_r)
69         _n_score = self._calc(n_h, n_t, n_r)
70         #The shape of p_score is (batch_size, 1, 1)
71         #The shape of n_score is (batch_size, negative_ent + negative_rel, 1)
72         p_score =  tf.reduce_sum(_p_score, -1, keep_dims = True)
73         n_score =  tf.reduce_sum(_n_score, -1, keep_dims = True)
74         #Calculating loss to get what the framework will optimize
75         self.loss = tf.reduce_mean(tf.maximum(p_score - n_score + config.margin, 0))
76 
77     def predict_def(self):
78         config = self.get_config()
79         predict_h, predict_t, predict_r = self.get_predict_instance()
80         predict_h_e = tf.reshape(tf.nn.embedding_lookup(self.ent_embeddings, predict_h), [1, -1, config.ent_size])
81         predict_t_e = tf.reshape(tf.nn.embedding_lookup(self.ent_embeddings, predict_t), [1, -1, config.ent_size])
82         predict_r_e = tf.reshape(tf.nn.embedding_lookup(self.rel_embeddings, predict_r), [1, -1, config.rel_size])
83         predict_matrix = tf.reshape(tf.nn.embedding_lookup(self.transfer_matrix, predict_r[0]), [1, config.ent_size, config.rel_size])
84         h_e = tf.reshape(self._transfer(predict_matrix, predict_h_e), [-1, config.rel_size])
85         t_e = tf.reshape(self._transfer(predict_matrix, predict_t_e), [-1, config.rel_size])
86         r_e = predict_r_e
87         self.predict = tf.reduce_sum(self._calc(h_e, t_e, r_e), -1, keep_dims = True)

  我们可以看到这是一个类的定义,整体比较规整,变量名和函数命名都和实际意义清晰对应,并且还有一定量的英文注释,这有助于世界各地的开发者使用和修改这个代码框架。

  从整个代码框架的实现来看,OpenKE项目的代码实现层次清晰,注释明了,代码中命名规范,让阅读者很容易理解其代表的实际含义。除此之外,代码的缩进以及函数间空行是比较规范的,使得代码块之间有清楚的界限。对于一些比较长的语句能够做到合理的换行,不至于在阅读时不能完全显示。

  但是代码仍有一些不足之处:①部分代码注释不够详尽,没有说明一些命名较简单的变量名代表的实际含义 ②整个项目缺乏对源代码组织以及代码编写理论的介绍,使得初学者不易理清代码和理论之间的对应关系。

  下面我将列举一些Python代码风格规范,资料来源于谷歌开源项目风格指南

分号

不要在行尾加分号, 也不要用分号将两条命令放在同一行。

行长度

每行不超过80个字符

例外:

  1. 长的导入模块语句
  2. 注释里的URL

不要使用反斜杠连接行。

Python会将圆括号,中括号和花括号中的行隐式的连接起来 ,你可以利用这个特点。 如果需要,你可以在表达式外围增加一对额外的圆括号。如果一个文本字符串在一行放不下, 可以使用圆括号来实现隐式行连接。在注释中,如果必要,将长的URL放在一行上。

括号

除非是用于实现行连接,否则不要在返回语句或条件语句中使用括号,不过在元组两边使用括号是可以的。

缩进

用4个空格来缩进代码,绝对不要用tab,也不要tab和空格混用。 对于行连接的情况,你应该要么垂直对齐换行的元素, 或者使用4空格的悬挂式缩进(这时第一行不应该有参数)。

空行

按照标准的排版规范来使用标点两边的空格,括号内不要有空格。不要在逗号、分号、冒号前面加空格,但应该在它们后面加(除了在行尾)。参数列表,、索引或切片的左括号前不应加空格。在二元操作符两边都加上一个空格,比如赋值(=), 比较(==, <, >, !=, <>, <=, >=, in, not in, is, is not), 布尔(and, or, not)。至于算术操作符两边的空格该如何使用, 需要你自己好好判断。当’=’用于指示关键字参数或默认参数值时, 不要在其两侧使用空格。不要用空格来垂直对齐多行间的标记,因为这会成为维护的负担(适用于:, #, =等)。

语句

通常每个语句应该独占一行,不过, 如果测试结果与测试语句在一行放得下,你也可以将它们放在同一行。 如果是if语句, 只有在没有else时才能这样做。特别地, 绝不要对 try/except 这样做, 因为try和except不能放在同一行。

命名

命名约定

  1. 所谓”内部(Internal)”表示仅模块内可用, 或者, 在类内是保护或私有的.
  2. 用单下划线(_)开头表示模块变量或函数是protected的(使用from module import *时不会包含).
  3. 用双下划线(__)开头的实例变量或方法表示类内私有.
  4. 将相关的类和顶级函数放在同一个模块里. 不像Java, 没必要限制一个类一个模块.
  5. 对类名使用大写字母开头的单词(如CapWords, 即Pascal风格), 但是模块名应该用小写加下划线的方式(如lower_with_under.py). 尽管已经有很多现存的模块使用类似于CapWords.py这样的命名, 但现在已经不鼓励这样做, 因为如果模块名碰巧和类名一致, 这会让人困扰.

应该避免的名称

  1. 单字符名称, 除了计数器和迭代器.
  2. 包/模块名中的连字符(-)
  3. 双下划线开头并结尾的名称(Python保留, 例如__init__)
posted @ 2019-10-13 21:11  Xpeng2333  阅读(238)  评论(0编辑  收藏  举报