整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

PEP8(二),命名风格

PEP8(二),命名风格

一种编程语言,都有以下概念:

  • 变量;
  • 函数或者方法;
  • 类;
  • 模块;

都有许多需要程序员命名的地方。

不同的语言,命名规范各不相同。

对Python来说,PEP8推荐的命名规范如下(此处基于Effective Python书中列举的条目整理,PEP8标准中会更详细些,笔者建议大家可以简单过一遍PEP8文档):

1、函数名、变量名、属性名

用小写字母配下划线,例如lowercase_underscore

2、类对象的属性

类对象的属性(attributes,笔者会更习惯使用“成员变量”这个词)和保护变量由变量名前方的下划线数量决定,一个下划线是保护变量(_protect_value),两个下划线是私有变量(__private_value)。

笔者在此处有一个惭愧测试:我印象中Python类对象的属性即便用双下划线开头,也是可以直接访问的。

写本篇博客时在Python3.10.2和2.7.18中测试,发现自己的记忆是错误的:直接访问双下划线变量,会抛出AttributeError异常,Python是有做私有变量的访问限制的。

# tmp.py
class Xx(object):
    def __init__(self):
        self._x=9
        self.__xx=10

    def getXX(self):
        return self.__xx


if __name__=='__main__':
    x=Xx()
    print(x._x)
    print(x.getXX())
    print(x.__xx)
# 在Python2中的测试(Python3当中是一样的)
> py -2 tmp.py
9
10
Traceback (most recent call last):
  File "tmp.py", line 30, in <module>
    print(x.__xx)
AttributeError: 'Xx' object has no attribute '__xx'

笔者近一年的工作中,写Python很少(这也是笔者去年放弃录制视频的原因之一),写C++较多。当笔者写本篇笔记时,好奇C++是否有相同的规范供借鉴,便搜一下C++的命名规范,其中排名最靠前的,是谷歌版本的C++代码规范。

Google C++ Style Guide:

https://google.github.io/styleguide/cppguide.html

由此推理,Python肯定也有谷歌版本的编码风格存在的。

Google Python Style Guide:

https://google.github.io/styleguide/pyguide.html

笔者看了下谷歌的命名规范,发现谷歌的程序员们并不推荐使用双下划线作为变量名称,即便Python自己做了“访问限制”。

由此,笔者再发现一个之前从未听过的概念:name mangling。(待去搜一下,发现不知道的只是名词“name mangling”,它的另一个名字是“名字装饰”,笔者的理解是:编译器为防止代码中的变量重复,会在编译时将这些变量加一些额外内容以做差异化。)

维基百科对name mangling的解释:

https://zh.m.wikipedia.org/wiki/%E5%90%8D%E5%AD%97%E4%BF%AE%E9%A5%B0

笔者有对Python中的name mangling做一下测试:

class Demo:
    any_name='any_name'
    _any_name='_any_name'
    __any_name='__any_name'

    def __init__(self):
        self.__any_x='__any_x'
        self._any_y='_any_y'

class Demo2:
    any_name='any_name'
    _any_name='_any_name'
    __any_name='__any_name'


# 测试一下多继承(注:Python虽然支持多继承,但并不推荐)
class Child(Demo, Demo2):
    pass


if __name__=='__main__':
    for n in dir(Demo):
        if('any' in n):
            print('cls ->', n)

    print('-------------------------')

    demo=Demo()
    for n in dir(demo):
        if('any' in n):
            print('object ->', n)

    print('Python并不能真正的做到成员私有:', demo._Demo__any_x)

    print('-------------------------')

    for n in dir(Child):
        if('any' in n):
            print('child ->', n)
            

测试结果如下:

> python3 tmp.py
# name mangling,加双下划线的变量名加上了类名前缀
cls -> _Demo__any_name
cls -> _any_name
cls -> any_name
-------------------------
object -> _Demo__any_name
object -> _Demo__any_x  # 对象中的name mangling
object -> _any_name
object -> _any_y
object -> any_name
Python并不能真正的做到成员私有: __any_x
-------------------------
# 多继承在这里,被区分出来
child -> _Demo2__any_name
child -> _Demo__any_name
# 但是重复的变量名,最后只剩一个(笔者未来的更新中会和大家一起讨论这个问题)
child -> _any_name
child -> any_name

由以上测试,我理解了为什么谷歌文档中说“没有真正实现私有”:双下划线变量,改一下名字就可以访问了。

3、类名

类名应该用首字母大写的驼峰模式:

class CapitalizedWord:
    pass

4、模块化常量

模块化常量使用全部大写形式,单词间用下划线分开。

THIS_IS_A_CONST_VALUE=10

5、类实例函数

类实例的第一个参数应该用self开头。

class MyTmpCls(object):
    def __init__(self, val):
        self._value=val

6、类方法

类方法的第一个参数,使用cls,表示这是类本身。

笔者曾经在某一次面试当中被要求手写Python版本的单例,当时没写出来。笔者的借口是:过去单例的使用,都是从网上抄录下来的。

笔者于此处抄一个简单单例在此处,供大家参考也供笔者自己记录。它来自于Stack Overflow:

class Singleton(object):
    _instance=None

    def __init__(self, *args, **kwargs):
        print('this is the init func.', id(self))

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance=super().__new__(cls, *args, **kwargs)
        return cls._instance


a=Singleton()
b=Singleton()
print(a is b)  # True

笔者最喜欢的程序员社区网站是Stack Overflow,它专业、全面且干净,笔者在遇到搞不定问题时,最先想到的便是到Stack Overflow去看看是否有前人遇见过相似问题。

绝大部分情况下,是有前人遇见过相似问题的。笔者整理本篇博客时,又去该网站上看了下前人对Python当中命名规范的讨论,我找到一个算是很火的帖子,这帖子中主要关注的关键字有两个:PEP8谷歌标准

由此,本篇博客以对谷歌标准的摘抄作为结束:

The Google Python Style Guide has the following convention:

module_name, package_name, ClassName, method_name, ExceptionName, function_name, GLOBAL_CONSTANT_NAME, global_var_name, instance_var_name, function_parameter_name, local_var_name.

谷歌推荐的Python编码标准原文链接如下:

英文:https://google.github.io/styleguide/pyguide.html#316-naming

中文:https://zh-google-styleguide.readthedocs.io/en/latest/google-python-styleguide/python_style_rules.html#id15

笔者写本篇时,再次感觉到之前已经总结过的感受:每一项技能,如果深入进去,是都有许多内容可以输出的。

本篇博客,对命名规范的讨论肯定并不全面。不过笔者的观点依然是:我们写代码,在让机器正确执行前提下,是需要考虑可读性的。

如何提升可读性?保持统一的命名风格,会有帮助。

SS标准化设计命名:

1、类class的命名规范示例

头:header

内容:content/container

尾:footer

导航:nav 

侧栏:sidebar

栏目:column  

页面外围控制整体布局宽度:wrapper  

左右中:left right center  

登录条:loginbar  

标志:logo  

广告:banner  

页面主体:main  

热点:hot  

新闻:news  

下载:download  

子导航:subnav  

菜单:menu  

子菜单:submenu  

搜索:search  

友情链接:friendlink  

页脚:footer  

版权:copyright  

滚动:scroll  

内容:content  

标签页:tab  

文章列表:list  

提示信息:msg  

小技巧:tips  

栏目标题:title  

加入:joinus  

指南:guild  

服务:service  

注册:regsiter  

状态:status  

投票:vote  

合作伙伴:partner

2、注释的写法  

/ Footer /  

内容区  

/ End Footer /

3、id的命名规范示例

(1)页面结构

  容器: container

  页头:header

  内容:content/container

  页面主体:main

  页尾:footer

  导航:nav

  侧栏:sidebar

  栏目:column

  页面外围控制整体布局宽度:wrappe

左右中:left right center

(2)导航

  导航:nav

  主导航:mainbav

  子导航:subnav

  顶导航:topnav

  边导航:sidebar

  左导航:leftsidebar

  右导航:rightsidebar

  菜单:menu

  子菜单:submenu

  标题: title

  摘要: summary 

 (3)功能

  标志:logo

  广告:banner

  登陆:login

  登录条:loginbar

  注册:regsiter

  搜索:search

  功能区:shop

  标题:title

  加入:joinus

  状态:status

  按钮:btn

  滚动:scroll

  标签页:tab

  文章列表:list

  提示信息:msg

  当前的: current

  小技巧:tips

  图标: icon

  注释:note

  指南:guild

  服务:service

  热点:hot

  新闻:news

  下载:download

  投票:vote

  合作伙伴:partner

  友情链接:link

  版权:copyright

4、类class的书写规范示例

  (1)颜色:使用颜色的名称或者16进制代码,如

  .red { color: red; }

  .f60 { color: #f60; }

  .ff8600 { color: #ff8600; }

 (2)字体大小,直接使用"font+字体大小"作为名称,如

  .font12px { font-size: 12px; }

  .font9pt {font-size: 9pt; }

 (3)对齐样式,使用对齐目标的英文名称,如

  .left { float:left; }

  .bottom { float:bottom; }

 (4)标题栏样式,使用"类别+功能"的方式命名,如

  .barnews { }

  .barproduct { }

5、CSS文件命名示例

  主要的 master.css

  模块 module.css

  基本共用 base.css

  布局,版面 layout.css

  主题 themes.css

  专栏 columns.css

  文字 font.css

  表单 forms.css

  补丁 mend.css

  打印 print.css6、注意事项

  (1)一律小写;

  (2)尽量用英文;

  (3)不加中杠和下划线;

  (4)尽量不缩写,除非一看就明白的单词。

击右上方红色按钮关注“web秀”,让你真正秀起来

前言

以往我们只是习惯于通过数组下标来访问正则匹配到的分组,但分组达到4、5个时,标识起来就会非常麻烦。V8早已实现了正则命名分组提案,只是我们很少使用,本文将介绍JS的正则命名分组。

JavaScript 正则命名分组

过去

假设要使用正则匹配一个日期的年月日,以往我们会这样做:

const RE_DATE=/(\d{4})-(\d{2})-(\d{2})/;

const matchObj=RE_DATE.exec('1999-12-31');
const year=matchObj[1]; // 1999
const month=matchObj[2]; // 12
const day=matchObj[3]; // 31

这里有几个缺点:

  • 要找到一个分组的位置,你必须要去数括号的位置,有时嵌套起来会更令人头疼。
  • 后面维护代码的同学阅读起来,还要根据下标找到正则里面对应的括号,并且要再次阅读括号里面的正则才知道含义。
  • 当你调整正则捕获分组的数量、顺序或嵌套时,你必要还要对下面的代码做调整。

所有这些问题,都可以通过正则命名分组来解决。

现在

现在你只需要给分组里面一个命名标识即可:

(?<year>\d{4})

这里,我们用变量year标记了上一个捕获组#1。 该名称必须是合法的JavaScript标识符。 匹配后,您可以通过matchObj.groups.year访问捕获的字符串。

让我们通过命名分组重写前面的代码:

const RE_DATE=/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;

const matchObj=RE_DATE.exec('1999-12-31');
const year=matchObj.groups.year; // 1999
const month=matchObj.groups.month; // 12
const day=matchObj.groups.day; // 31

如果正则里面有了命名分组,那么匹配结果会多了一个groups 的属性,这个属性中包含了一切命名分组的捕获结果。配合上解构大法使用又是一股清流:

const {groups: {day, year}}=RE_DATE.exec('1999-12-31');
console.log(year); // 1999
console.log(day); // 31

当然,即使你使用了命名分组,那么返回的结果还可以通过以往的数组下标方式访问:

const year2=matchObj[1]; // 1999
const month2=matchObj[2]; // 12
const day2=matchObj[3]; // 31

命名分组具有以下优点:

  • 找到分组的“ID”更容易。
  • 匹配的代码变得自描述性,因为分组的ID描述了捕获的内容。
  • 如果更改分组的顺序,则不必更改匹配的代码。
  • 分组的名称也使正则表达式更易于理解,因为您可以直接看到每个组的用途。

反向引用

反向引用命名分组\k<name> 看下面这个匹配重复单词的例子:

const RE_TWICE=/^(?<word>[a-z]+)!\k<word>$/;
RE_TWICE.test('abc!abc'); // true
RE_TWICE.test('abc!ab'); // false

同时也可以使用以往的反向引用方式:

const RE_TWICE=/^(?<word>[a-z]+)!\1$/;
RE_TWICE.test('abc!abc'); // true
RE_TWICE.test('abc!ab'); // false

replace( )

字符串方法replace()以两种方式支持命名分组:

方式一

const RE_DATE=/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
console.log('1999-12-31'.replace(RE_DATE, '$<month>/$<day>/$<year>'));
// 12/31/1999

如果replace不一定是直接返回新的拼接字符串,那么可以看看下面的办法:

方式二

const RE_DATE=/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
console.log('1999-12-31'.replace(
 RE_DATE,
 (g, y, m, d, offset, input, {year, month, day})=>
 month+'/'+day+'/'+year));
 // 12/31/1999

看看这replace的callback形参密密麻麻看得心慌慌,很多都用不上,那么我们看看更简单的写法:

console.log('1999-12-31'.replace(RE_DATE,
 (...args)=> {
 const {year, month, day}=args.slice(-1)[0];
 return month+'/'+day+'/'+year;
 }));
 // 12/31/1999

这里配合上spread operator直取最后一个参数,再接上一个解构大法,结果又是一股清流。

命名分组没有匹配结果?

如果可选的命名组不被匹配,则其属性值被设置为undefined,但key是仍存在:

const RE_OPT_A=/^(?<as>a+)?$/;
const matchObj=RE_OPT_A.exec('');

// We have a match:
console.log(matchObj[0]===''); // true

// Group <as> didn’t match anything:
console.log(matchObj.groups.as===undefined); // true

// But property as exists:
console.log('as' in matchObj.groups); // true

异常情况

分组名不能有重复项:

/(?<foo>a)(?<foo>b)/ // SyntaxError: Duplicate capture group name

反向引用一个不存在的分组名:

/\k<foo>/u // SyntaxError: Invalid named capture referenced
/\k<foo>/.test("k<foo>") // true, 非 Unicode 下为了向后兼容,k 前面的 \ 会被丢弃

在 reaplce() 方法的替换字符串中引用一个不存在的分组:

"abc".replace(/(?<foo>.*)/, "$<bar>") // SyntaxError: Invalid replacement string
"abc".replace(/(.*)/, "$<bar>") // "$<bar>",不包含命名分组时会向后兼容

说明

Chrome60 已支持命名分组 通过babel插件处理兼容问题 babel-plugin-transform-modern-regexp

公告

喜欢小编的点击关注,了解更多知识!