在这种类型的程序库环境中,得体的做法是只对暴露到框架中的元素使用混合大小写,而其他代码保持遵守PEP 8风格。
还值得注意的是,Twisted项目的开发人员采用一种完全不同的方法来解决这个问题。与Zope一样,Twisted项目早于PEP 8文档。项目启动时没有任何代码风格的官方指南,所以它有自己的风格指南。关于缩进、文档字符串、每行长度等风格规则可以很容易被采用。另一方面,修改所有代码以匹配PEP 8的命名约定,可能会完全破坏向后兼容。对于像Twisted这样的大型项目而言,这么做并不可行。因此Twisted尽可能遵守PEP 8,并将其余内容(例如变量、函数和方面的混合大小写)作为它自己的编码标准的一部分。这与PEP 8的建议完全兼容,因为它特别强调,在项目内的一致性比遵守PEP 8风格指南要更加重要。
5.关于私有元素的争论
对于私有方法和函数,惯例是添加一个前缀下划线。考虑到Python中的名称修饰(name-mangling)特性,这条规则是相当有争议的。如果一个方法有两个前缀下划线,它会在运行时被解释器重命名,以避免与任何子类中的方法产生命名冲突。
因此,有些人倾向于对私有属性使用双前缀下划线,以避免子类中的命名冲突:
class Base(object):
def __secret(self):
print("don't tell")
def public(self):
self.__secret()
class Derived(Base):
def __secret(self):
print("never ever")
你将会看到以下内容:
>>> Base.__secret
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: type object 'Base' has no attribute '__secret'
>>> dir(Base)
['_Base__secret', ..., 'public']
>>> Derived().public()
don't tell
Python中名称修饰的最初目的不是提供类似C++的私有花招(gimmick),而是用来确保某些基类隐式地避免子类中的冲突,特别是在多重继承的上下文中。但将其用于每个属性则会使私有代码含义变得模糊,这一点也不Pythonic。
因此,有些人认为应该始终使用显式的名称修饰:
class Base:
def _Base_secret(self): # 不要这么做!!!
print("you told it ?")
这样会在所有代码中重复类名,所以应该首选__
。
但正如BDFL(Guido,the Benevolent Dictator For Life,参见http://en.wikipedia.org/wiki/ BDFL)所说,最佳做法是在编写子类中的方法之前查看该类的__mro__
(方法解析顺序)值,从而避免使用名称修饰。修改基类的私有方法一定要小心。
关于这个主题的更多信息,许多年前在Python-Dev邮件列表中出现过一个有趣的讨论,人们争论名称修饰的实用性以及它在这门语言中的命运。你可以访问地网址:http://mail.python.org/ pipermail/python-dev/2005-December/058555.html查看。
6.特殊方法
特殊方法(https://docs.python.org/3/reference/datamodel.html#special-method-names)以双下划线开始和结束,常规的方法不应该使用这种约定。有些开发者曾经将其称为dunder方法,作为双下划线(double-underscore)的合成词。它们可用于运算符重载、容器定义等方面。为了保证可读性,它们应该集中放在类定义的开头:
class WeirdInt(int):
def __add__(self, other):
return int.__add__(self, other) + 1
def __repr__(self):
return '<weirdo %d>' % self
# 公共API
def do_this(self):
print('this')
def do_that(self):
print('that')
对于常规方法而言,你永远不应该使用这种名称。所以不要为方法创建这样的名称:
class BadHabits:
def __my_method__(self):
print('ok')
7.参数
参数名称使用小写,如果需要的话可以加下划线。它们遵循与变量相同的命名规则。
8.property
property
的名称使用小写或小写加下划线。大部分时候,它们表示一个对象的状态,可以是名词或形容词,如果需要的话也可以是如下简短的短语:
class Connection:
_connected = []
def connect(self, user):
self._connected.append(user)
@property
def connected_people(self):
return ', '.join(self._connected)
在交互式会话中运行的结果如下所示:
>>> connection = Connection()
>>> connection.connect('Tarek')
>>> connection.connect('Shannon')
>>> print(connection.connected_people)
Tarek, Shannon
9.类
类名称始终采用驼峰式命名法,如果它们是模块的私有类,还可能有一个前缀下划线。
类和实例变量通常是名词短语,与用动词短语命名的方法名称构成使用逻辑:
class Database:
def open(self):
pass
class User:
pass
下面是在交互式会话中的使用示例:
>>> user = User()
>>> db = Database()
>>> db.open()
10.模块和包
除了特殊模块__init__
之外,模块名称都使用小写,不带下划线。
下面是标准库中的一些例子:
os
;sys
;shutil
。
如果模块是包的私有模块,则添加一个前缀下划线。编译过的C或C++模块名称通常带有一个下划线,并在纯Python模块中导入。
包名称遵循同样的规则,因为它的表现就像是更加结构化的模块。
3 命名指南
一组常用的命名规则可以被应用于变量、方法、函数和property
。类和模块的名称也在命名空间的构建中具有重要的作用,从而也影响代码可读性。本迷你指南为挑选名称提供了常见的模式和反模式。
3.1 用“has”或“is”前缀命名布尔元素
如果一个元素保存的是布尔值,is
和has
前缀提供一种自然的方式,使其在命名空间中的可读性更强,代码如下:
class DB:
is_connected = False
has_cache = False
3.2 用复数形式命名集合变量
如果一个元素保存的是集合变量,那么使用复数形式是一个好主意。有些映射在暴露为序列时也可以从中受益:
class DB:
connected_users = ['Tarek']
tables = {
'Customer': ['id', 'first_name', 'last_name']
}
3.3 用显式名称命名字典
如果一个变量保存的是映射,那么你应该尽可能使用显式名称。例如,如果一个字典保存的是个人地址,那么可以将其命名为persons_addresses
,代码如下:
persons_addresses = {'Bill': '6565 Monty Road',
'Pamela': '45 Python street'}
persons_addresses['Pamela']
'45 Python street'
3.4 避免通用名称
如果你的代码不是在构建一种新的抽象数据类型,那么使用类似list
、dict
、sequence
或elements
等专用名词是有害的,即使对于局部变量也一样。它使得代码难以阅读、理解和使用。还应该避免使用内置名称,以避免在当前命名空间中将其屏蔽(shadowing)。还应该避免使用通用的动词,除非它们在该命名空间中有意义。
相反,应该使用领域特定的术语,如下所示:
def compute(data): # 太过通用
for element in data:
yield element ** 2
def squares(numbers): # 更好一些
for number in numbers:
yield number ** 2
还有一系列前缀和后缀,虽然在编程中非常常见,但事实上应该避免出现在函数和类名称中:
- manager;
- object;
- do、handle或perform。
这样做的原因是它们的含义模糊、模棱两可,且没有向实际名称中添加任何信息。Jeff Atwood(Discourse 和Stack Overflow的联合创始人)关于这个话题写过一篇非常好的文章,你可以在他的博客上找到这篇文章:http://blog.codinghorror.com/i-shall-call-it-somethingmanager/。
还有许多包的名称应该避免。任何没有对其内容给出任何信息的名称,从长远来看都对项目有很大害处。诸如misc
、tools
、utils
、common
或core
的名称有很大可能会变成一大堆不相关的、质量非常差的代码片段,其大小呈指数增长。在大多数情况下,这种模块的存在是懒惰或缺乏足够设计经验的迹象。热衷于这种模块名称的人可以预见未来,并将其重命名为trash
(垃圾箱)或dumpster
(垃圾桶),因为这正是他们的队友最终对这些模块的处理方式。
在大多数情况下,更多的小模块几乎总是更好,即使内容很少,但名称可以很好地反映其内容。说实话,类似utils
和common
之类的名称没有本质错误,你可以负责任地使用它们。但现实表明,在许多情况下,它们都会变为危险的结构反模式,并且迅速增长。而且如果你的行动不够快,你可能永远无法摆脱它们。因此,最好的方法就是避免这样有风险的组织模式,如果项目其他人引入的话则将其扼杀在萌芽状态。
3.5 避免现有名称
使用上下文中已经存在的名称是不好的做法,因为它会导致阅读代码时——特别是调试时——非常混乱,例如以下代码:
>>> def bad_citizen():
... os = 1
... import pdb; pdb.set_trace()
... return
os
...
>>> bad_citi
zen()
><stdin>(4)bad_cit
izen()
(Pdb) os1
(Pdb)
import os
(Pdb) c
<module 'os' from '/Library/Frameworks/Python.framework/Versio
ns/2.5/lib/
python2.5/os.pyc'>
在这个例子中,os
名称被代码屏蔽。内置函数名称和来自标准库的模块名称都应该避免。
尽量使用原创的名称,即使是上下文的局部名称。对于关键字而言,后缀下划线是一种避免冲突的方法:
def xapian_query(terms, or_=True):
"""if or_ is true, terms are combined with the OR clause"""
...
注意,class
通常被替换为klass
或cls
:
def factory(klass, *args, **kwargs):
return klass(*args, **kwargs)
4 参数的最佳实践
函数和方法的签名是代码完整性的保证,它们驱动函数和方法的使用并构建其API。除了我们之前看到的命名规则之外,对参数也要特别小心。这可以通过3个简单的规则来实现。
- 通过迭代设计构建参数。
- 信任参数和测试。
- 小心使用魔法参数
*args
和**kwargs
。
4.1 通过迭代设计构建参数
如果每个函数都有一个固定的、定义明确的参数列表,那么代码的鲁棒性会更好。但这在第一个版本中无法完成,所以参数必须通过迭代设计来构建。它们应该反映创建该元素所针对的使用场景,并相应地逐渐发展。
例如,如果添加了一些参数,它们应该尽可能有默认值,以避免任何退化:
class Service: # 版本1
def _query(self, query, type)
:
print('done
')
def execute(self, que
ry):
self._query(query, 'EXECUTE')
>>> Service().execute('my query')
done
impo
rt logging
class Service(obj
ect): # 版本2
def _query(self, query, t
ype, logger):
logger('done')
def execute(self, query, logge
r=logging.info):
self._query(query, '
EXECUTE', logger)
>>> Service().execute('m
y query') # 旧式调用
>>> Service().execute('my quer
y', logging.warning)
WARNING:root:done
如果一个公共元素的参数必须被修改,那么将使用一个deprecation进程,本节稍后将对此进行说明。
4.2 信任参数和测试
考虑到Python的动态类型特性,有些开发人员在函数和方法的顶部使用断言(assertion)来确保参数具有正确的内容,代码如下:
def division(dividend, divisor):
assert isinstance(dividend, (int, float)
)
assert isinstance(divisor, (int, float
))
return dividend / divisor
>>> division(
2, 4)
0.5
>>> division(2, None)
Traceback (most recent cal
l last):
File "<input>", line 1, in
<module>
File "<input>", line 3, in division
AssertionError
这通常是那些习惯于静态类型、并且感觉Python中缺少点什么的开发者的做法。
这种检查参数的方法是契约式设计(Design by Contract,DbC,参见http://en.wikipedia.org/ wiki/DesignByContract)编程风格的一部分。在这种设计中,在代码实际运行之间会检查先决条件。
这种方法有两个主要问题。
- DbC的代码对应该如何使用它进行解释,导致其可读性降低。
- 这可能使代码速度变慢,因为每次调用都要进行断言。
后者可以通过解释器的"-O"
选项来避免。在这种情况下,在创建字节码之前,所有断言都将从代码中删除,这样检查也就会丢失。
在任何情况下,断言都必须小心进行,并且不应该用于使Python变成一种静态类型语言。唯一的使用场景就是保护代码不被无意义地调用。
在大多数情况下,健康的测试驱动开发(TDD)风格可以提供鲁棒性很好的基础代码。在这里,功能测试和单元测试验证了创建代码所针对的所有使用场景。
如果库中的代码被外部元素使用,那么进行断言可能是有用的,因为传入的数据可能会导致程序结束甚至造成破坏。这在处理数据库或文件系统的代码中可能发生。
另一种方法是模糊测试(fuzz testing,参见https://en.wikipedia.org/wiki/Fuzzing),它通过向程序发送随机的数据块来检测其弱点。如果发现了新的缺陷,代码会被修复以解决这一缺陷,并添加一次新的测试。
让我们来关注一个遵循TDD方法的代码库,它向正确的方向发展,每当出现新的缺陷时都会对其进行调整,从而鲁棒性越来越好。当它以正确的方式完成时,测试中的断言列表在某种程度上变得类似于先决条件列表。
4.3 小心使用*args
和**kwargs
魔法参数
*args
和**kwargs
参数可能会破坏函数或方法的鲁棒性。它们会使签名变得模糊,而且代码常常在不应该出现的地方构建小型的参数解析器,如下所示:
def fuzzy_thing(**kwargs):
if 'do_this' in kwarg
s:
print('ok i di
d')
if 'do_that' in kw
args:
print('that is
done')
print('errr
... ok')
>>> fuzzy_thing(do_this=1)
ok i did
errr... ok
>>> fuzzy_thi
ng(do_that=1)
that is done
errr... ok
>>> fuzzy
_thing(hahaha=1)
errr... ok
如果参数列表变得很长而且很复杂,那么添加魔法参数是很吸引人的。但这更表示它是一个脆弱的函数或方法,应该被分解或重构。
如果*args
被用于处理元素序列(在函数中以相同方式处理),那么要求传入唯一的容器参数(例如iterator
)会更好些,如下所示:
def sum(*args): # 可行
total = 0
for arg in args:
total += arg
return total
def sum(sequence): # 更好!
total = 0
for arg in sequence:
total += arg
return total
**kwargs
适用于同样的规则。最好固定命名参数,使方法签名更有意义,如下所示:
def make_sentence(**kwargs):
noun = kwargs.get('noun', 'Bill')
verb = kwargs.get('verb', 'is')
adj = kwargs.get('adjective', 'happy')
return '%s %s %s' % (noun, verb, adj)
def make_sentence(noun='Bill', verb='is', adjective='happy'):
return '%s %s %s' % (noun, verb, adjective)
另一种有趣的方法是创建一个容器类,将多个相关参数分组以提供执行上下文。这种结构与*args
或**kwargs
不同,因为它可以提供能够操作数值并且能够独立发展的内部构件(internals)。使用它作为参数的代码将不必处理其内部构件。
例如,传入函数的Web请求通常由一个类实例表示。这个类负责保存Web服务器传入的数据,代码如下:
def log_request(request): # 版本1
print(request.get('HTTP_REFERER', 'No referer'))
def log_request(request): # 版本2
print(request.get('HTTP_REFERER', 'No referer'))
print(request.get('HTTP_HOST', 'No host'))
魔法参数有时是无法避免的,特别是在元编程中。例如,想要创建能够处理任何类型签名的函数的装饰器,它是不可或缺的。更普遍地说,在处理对函数进行遍历的未知数据时,魔法参数都很好用,代码如下:
import logging
def log(**context):
logging.info('Context is:\n%s\n' % str(context))
5 类的名称
类的名称必须简明、精确,并足以使人理解类的作用。常见的做法是使用后缀来表示其类型或特性。例如:
- SQLEngine;
- MimeTypes;
- StringWidget;
- TestCase。
对于基类或抽象类,可以使用一个Base或Abstract前缀,如下所示:
- BaseCookie;
- AbstractFormatter。
最重要的是要和类属性保持一致。例如,尽量避免类及其属性名称之间的冗余:
>>> SMTP.smtp_send() # 命名空间中存在冗余信息
>>> SMTP.send() #
如果你也是看准了Python,想自学Python,在这里为大家准备了丰厚的免费学习大礼包,带大家一起学习,给大家剖析Python兼职、就业行情前景的这些事儿。
一、Python所有方向的学习路线
Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
二、学习软件
工欲善其必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。
三、全套PDF电子书
书籍的好处就在于权威和体系健全,刚开始学习的时候你可以只看视频或者听某个人讲课,但等你学完之后,你觉得你掌握了,这时候建议还是得去看一下书籍,看权威技术书籍也是每个程序员必经之路。
四、入门学习视频
我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。
四、实战案例
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
五、面试资料
我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
成为一个Python程序员专家或许需要花费数年时间,但是打下坚实的基础只要几周就可以,如果你按照我提供的学习路线以及资料有意识地去实践,你就有很大可能成功!
最后祝你好运!!!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!