最后
🍅 硬核资料:关注即可领取PPT模板、简历模板、行业经典书籍PDF。
🍅 技术互助:技术群大佬指点迷津,你的问题可能不是问题,求资源在群里喊一声。
🍅 面试题库:由技术群里的小伙伴们共同投稿,热乎的大厂面试真题,持续更新中。
🍅 知识体系:含编程语言、算法、大数据生态圈组件(Mysql、Hive、Spark、Flink)、数据仓库、Python、前端等等。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
>>> # 我们来尝试一下
>>> SET = BLUE | RE
D
>>> has_option(SET, BLUE)
True
>>> has_option(SET, WHITE)
False
在创建这样一组新的常量时,应避免对它们使用共同的前缀,除非模块中有多组常量。模块名称本身就是一个共同的前缀。另一种解决方法是使用内置enum
模块的Enum
类,并且依赖于set
集合类型而不是二进制运算符。不幸的是,Enum
类在面向旧Python版本的代码中应用有限,因为enum
模块由Python 3.4版提供。
在Python中,使用二进制按位运算来合并选项是很常见的。使用OR(
|
)运算符可以将多个选项合并到一个整数中,而使用AND(&
)运算符则可以检查该选项是否在整数中(参见has_option
函数)。
3.公有和私有变量
对于可变的且可以通过导入自由访问的全局变量,如果它们需要被保护,那么应该使用带一个下划线的小写字母。但这种变量不经常使用,因为如果它们需要被保护,模块通常会提供getter和setter来处理。在这种情况下,一个前缀下划线可以将变量标记为包的私有元素,如下所示:
_observers = []
def add_observer(observer):
_observers.append(observer)
def get_observers():
""确保_observers不能被修改。"""
return tuple(_observers)
位于函数和方法中的变量遵循相同的规则,并且永远不会被标记为私有,因为它们对上下文来说是局部变量。
对于类或实例变量而言,只在将变量作为公有签名的一部分不会带来任何有用信息或者冗余的情况下,才必须使用私有标记符(前缀下划线)。
换句话说,如果变量在方法内部使用,用来提供公有功能,并且只具有这个功能,那么最好将其设为私有。
例如,支持property
的属性是很好的私有成员,如下所示:
class Citizen(object):
def __init__(self):
self._message = 'Rosebud...'
def _get_message(self):
return self._message
kane = property(_get_message)
另一个例子是用来记录内部状态的变量。这个值对其他代码没有用处,但却参与了类的行为,如下所示:
class UnforgivingElephant(object):
def __init__(self, name):
self.name = name
self._people_to_stomp_on = []
def get_slapped_by(self, name):
self._people_to_stomp_on.append(name)
print('Ouch!')
def revenge(self):
print('10 years later...')
for person in self._people_to_stomp_on:
print('%s stomps on %s' % (self.name, person))
下面是在交互式会话中的运行结果:
>>> joe = UnforgivingElephant('Joe')
>>> joe.get_slapped_by('Tarek')
Ouch!
>>> joe.get_slapped_by('Bill')
Ouch!
>>> joe.revenge()
10 years later...
Joe stomps on Tarek
Joe stomps on Bill
4.函数和方法
函数和方法的名称应该使用小写加下划线。但在旧的标准库模块中并不总是这样。Python 3对标准库做了大量重组,所以大多数函数和方法都有一致的大小写。不过对于某些模块(例如threading
)而言,你可以访问使用混合大小写(mixedCase)的旧的函数名称(例如currentThread
)。留着它们是为了更容易向后兼容,但如果你不需要在旧版Python中运行代码,那么应该避免使用这些旧的名称。
这种方法的写法在小写范式成为标准之前很常见,一些框架(例如Zope和Twisted)对方法也使用混合大小写。使用它的开发者社区仍然相当多。因此,选择混合大小写还是小写加下划线,这取决于你所使用的库。
作为Zope的开发人员,保持一致性并不容易,因为构建一个混合纯Python模块和导入了Zope代码的模块的应用程序很困难。在Zope中,有一些类混用了两种约定,因为代码库仍在发展,而Zope开发人员想要采用多数人都接受的常用约定。
在这种类型的程序库环境中,得体的做法是只对暴露到框架中的元素使用混合大小写,而其他代码保持遵守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
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210511152217670.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaWd1aWd1,size_16,color_FFFFFF,t_70)
**感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的:**
① 2000多本Python电子书(主流和经典的书籍应该都有了)
② Python标准库资料(最全中文版)
③ 项目源码(四五十个有趣且经典的练手项目及源码)
④ Python基础入门、爬虫、web开发、大数据分析方面的视频(适合小白学习)
⑤ Python学习路线图(告别不入流的学习)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618317507)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**