从前,在我的学生时代,我被一条蟒蛇咬了,尽管潜伏期被推迟了,结果我成为了一名珍珠程序员。
但是,在某个时候,珍珠已经精疲力尽了,我决定使用python,首先我做了一些事情,弄清楚了这项任务需要什么,然后我意识到我需要某种系统的知识并读了几本书:
- Bill Lyubanovich“简单的Python。 现代编程风格”
- Dan Bader“纯Python。 专业人士编程的精妙之处»
- 布雷特·斯拉特金(Brett Slatkin)“ Python的秘密:编写有效代码的59个技巧”
在我看来,这似乎很适合理解该语言的基本精妙之处,尽管我不记得在其中提及插槽 ,但是我不确定这是否是真正必要的功能-如果我从内存中按一下,那么这种方法很可能是不够的,但是当然这一切都取决于情况。
结果,我积累了一些有关python功能的注释,在我看来,这对于想要从其他语言移植到python的人很有用。
我注意到,在python访谈中,他们经常问一些与实际开发无关的问题,例如字典的键(或x = yield y
含义),伙计,在现实生活中,键可能是仅是数字或字符串,如果不是这样,在特殊情况下,您可以阅读文档并弄清楚为什么问这个? 寻找被访者不知道的东西? 因此,最后,每个人都会记住该特定问题的答案,它将停止工作。
我认为高于3.5的python版本是有意义的( 是时候忘记第二个python了) 这是稳定的debian中的版本,这意味着在所有其他地方都有更新的版本)
由于我根本不是Python专家,所以如果我突然冻结某种愚蠢,希望他们在评论中纠正我。
打字
Python是一种动态类型化的语言,即 它在运行时检查类型匹配,例如:
cat type.py a=5 b='5' print(a+b)
执行:
python3 type.py ... TypeError: unsupported operand type(s) for +: 'int' and 'str'
但是,如果您的项目已经成熟到需要静态类型,那么python也可以通过使用mypy
静态分析器来提供这样的机会:
mypy type.py type.py:3: error: Unsupported operand types for + ("int" and "str")
是的,不是所有的错误都是通过这种方式捕获的:
cat type2.py def greeting(name): return 'Hello ' + name greeting(5)
mypy在这里不会发誓,但是在执行过程中会发生错误,因此当前版本的python支持用于指定函数参数类型的特殊语法:
cat type3.py def greeting(name: str) -> str: return 'Hello ' + name greeting(5)
现在:
mypy type3.py type3.py:4: error: Argument 1 to "greeting" has incompatible type "int"; expected "str"
变量和数据
python中的变量不存储数据,而仅引用它们,并且数据可以是可变的(可变的)和不可变的(不可变的)。
在几乎相同的情况下,这取决于数据类型导致不同的行为,例如,这样的代码:
x = 1 y = x x = 2 print(y)
导致以下事实:变量x
和y
引用了不同的数据,并且:
x = [1, 2, 3] y = x x[0] = 7 print(y)
否, x
和y
仍链接到同一列表(尽管如注释中所述,示例并不十分成功,但我还没有提出更好的方法),通过python可以检查is
运算符(我确定Java的创建者永远失去了良好的睡眠)当我在python中发现有关此运算符时感到羞耻。
尽管行看起来像一个列表,但是它们是不可变的数据类型,这意味着字符串本身不能更改,您只能生成一个新的字符串,但是可以为变量分配一个不同的值,尽管原始数据不会更改:
>>> mystr = 'sss' >>> newstr = mystr # >>> mystr[0] = 'a' ... TypeError: 'str' object does not support item assignment >>> mystr = 'ssa' # >>> newstr # 'sss'
说起字符串,由于它们的免疫力,通过在循环中添加或追加来连接很大的字符串列表可能不是很有效(取决于特定编译器/版本的实现),通常建议在这种情况下使用join方法有点意外:
>>> str_list = ['ss', 'dd', 'gg'] >>> 'XXX'.join(str_list) 'ssXXXddXXXgg' >>> str = 'hello' >>> 'XXX'.join(str) 'hXXXeXXXlXXXlXXXo'
首先,调用该方法的行成为分隔符,而不是人们可能认为的新行的开头;其次,您需要传递一个列表(一个可迭代的对象),而不是一个单独的行,因为它也是一个可迭代的对象并且将被符号化。
由于变量是链接,因此要复制对象以免破坏原始对象是很正常的,但是有一个陷阱- 复制函数只能复制一个级别,这显然不是使用该名称的函数所期望的,因此请使用deepcopy
。
当集合乘以标量时,可能会发生类似的复制问题,如此处所述。
适用范围
作用域主题可能值得单独写一篇文章,但是SO有一个很好的答案 。
简而言之,范围是词法 ,有六个可见性区域-函数主体中的变量,闭包中,模块中,类主体中的内置python函数和列表内的变量以及其他包含项。
有一个微妙的地方-默认变量在词法嵌套的名称空间中是可读的,但是修改需要使用特殊的关键字nonlocal
和global
来分别将变量修改一级或全局可见性。
例如,如下代码:
x = 7 print(id(x)) def func(): print(id(x)) return x print(func())
它使用一个全局变量,而这个变量:
x = 7 print(id(x)) def func(): x = 1 print(id(x)) return x print(func()) print(x)
已经产生了一个本地的
从我的角度来看,这不是很好,原则上,在函数中对非局部变量的任何使用都是函数的公共接口(即其签名)的一部分,这意味着应在函数开始时对其进行显式声明和可见。 另外,关键字不是很有用-听起来像是全局函数的定义,但实际上意味着use global
。
在python中,没有强制启动程序的入口,就像在许多语言中一样,只是按顺序执行在模块级别编写的所有内容,但是,由于模块级别的变量是全局变量,从我的角度来看,这应该是一种好习惯将主代码塞入main()
函数,然后在文件末尾调用它:
if __name__ == '__main__': main()
如果文件被称为脚本而不是作为模块导入,则此条件将起作用。
功能参数
Python为定义函数参数(位置参数,命名参数及其组合)提供了别致的机会。
但是您需要了解如何传递参数-因为 在python中,所有变量都是到数据的链接,然后您可以猜测传输是通过引用进行的,但是有一个特殊之处-链接本身是通过值传递的,即 您可以通过参考修改可变值:
def add_element(mylist): mylist.append(3) mylist = [1,2] add_element(mylist) print(mylist)
执行:
python3 arg_modify.py [1, 2, 3]
但是,您不能覆盖函数中的原始链接:
def try_del(mylist): mylist = [] return mylist mylist = [1,2] try_del(mylist) print(mylist)
源链接仍然有效并且可以正常工作:
python3 arg_kill.py [1, 2]
您还可以设置参数的默认值,但是要记住一件事:定义函数时,默认值只计算一次,如果将不变的数据作为默认值传递,并且如果传递了默认值,则不会产生任何问题。可变数据或动态值,结果将有些出乎意料:
可变数据:
cat arg_list.py def func(arg = []): arg.append('x') return arg print(func()) print(func()) print(func())
结果:
python3 arg_list.py ['x'] ['x', 'x'] ['x', 'x', 'x']
动态值:
cat arg_now.py from datetime import datetime def func(arg = datetime.now()): return arg print(func()) print(func()) print(func())
我们得到:
python3 arg_now.py 2018-09-28 10:28:40.771879 2018-09-28 10:28:40.771879 2018-09-28 10:28:40.771879
面向对象
python中的OOP做得非常有趣(某些属性值得),这是一个很大的话题,但是熟悉OOP的智商可以用谷歌搜索他想要的所有内容(或在hub上找到它),因此没有必要重复,尽管值得指出的是python应该有点不同的哲学-是一个程序员更聪明的机器,而不是威胁(UPD: 更多 ),所以python的默认是没有通常为其他语言的访问修饰符:通过添加一个双下划线(更改方法名的运行时实现的私有方法是不OAPC 主题机会使用它),并且保护一个下划线(即没有做任何事情,它只是一个命名约定)。
那些错过常规功能的人可以尝试为python带来这种机会,我用谷歌搜索了两个选项( lang , python-access ),但是我没有进行测试或研究。
标准类的唯一缺点是任何Dunder方法中的样板代码,我个人很喜欢attrs库,它更像python。
值得一提的是,由于在Python中,所有对象(包括函数和类)都可以通过类型函数动态创建类(无需使用eval
)。
还值得一读有关元类 ( 在Habr上 )和描述符 ( Habr )的信息。
值得记住的一个特点是,类和对象的属性不是同一件事,对于不可变的属性,这不会引起问题,因为属性是“影子”的;具有相同名称的对象的属性是自动创建的,但是在可变属性的情况下,您可以得到的不完全是预期的:
cat class_attr.py class MyClass: storage = [7,] def __init__(self, number): self.number = number obj = MyClass(1) obj2 = MyClass(2) obj.number = 5 obj.storage.append(8) print(obj2.storage, obj2.number)
我们得到:
python3 class_attr.py [7, 8] 2
如您所见-它们更改了obj
,并且storage
更改了obj2
。 此属性(与number
不同)不属于实例,而是属于类。
常数
与使用访问修饰符一样,python不会尝试限制开发人员,因此,不可能以标准方式定义一个标量变量,以防止其受到修改,仅存在一个共识,即以大写字母命名的变量应视为常量。
另一方面,Python具有不可变的数据结构(例如元组),因此,如果您希望使某些全局结构(如配置)不可变并且不希望其他依赖项,那么namedtuple是一个不错的选择,尽管描述类型需要更多的工作,因此我喜欢使用点符号-Box的不可变结构的替代实现(请参见Frozen_box参数)。
好吧,如果您想要标量常量,那么可以在“编译”阶段实现访问控制,即 检查mypy, 示例和详细信息 。
.sort()与sorted()
有两种方法可以在python中对列表进行排序。 第一个是.sort()
方法,该方法会修改原始列表,并且不返回任何内容(无),即 无法做到这一点:
my_list = my_list.sort()
第二个是sorted()
函数,它产生一个新列表并可以与所有可迭代对象一起使用。 谁想要更多信息,应从SO开始。
标准库
通常,标准python库包含针对常见问题的出色解决方案,但值得一提的是,因为有很多奇怪的地方。 没错,乍看起来似乎很奇怪的事实是最好的解决方案,您只需要了解所有条件(有关范围,请参见下文),但是仍然有一些奇怪之处。
例如,套件附带的unittest单元模块与python和Java无关,因此,正如python的作者所说 :“ Eveybody正在使用py.test ...”。 尽管很有趣,尽管不一定总是合适,但doctest模块是标准配置。
所提供的urllib模块没有第三方请求模块这样漂亮的界面。
与用于解析命令行参数的模块相同的故事- 捆绑的argparse演示了大脑的OOP,而docopt模块似乎只是一个聪明的解决方案-最终的自我记录! 尽管有传言说,尽管采用了docopt和click,但仍然存在一个利基市场。
调试器也是如此-据我了解,很少有人使用软件包中包含的pdb ,但有很多替代方案,但是似乎大多数开发人员都使用ipdb ,从我的角度来看,通过调试包装器模块使用起来最方便。
它允许代替import ipdb;ipdb.set_trace()
简单地编写import debug
,它还添加了see模块,以方便检查对象。
顺便说一下,要代替标准的序列化模块,请把pickle做成莳萝 ,要记住,这些模块不适合在外部系统中交换数据, 恢复从不受控制的源接收到的任意对象是不安全的,在这种情况下,存在json(对于REST)和gRPC (对于RPC)。
要替换标准的正则表达式处理模块,请使regex模块具有各种其他功能,例如字符类ala \p{Cyrillic}
。
顺便说一句,对于python来说并没有遇到什么有趣的调试器,类似于类似于大麦的正则表达式。
这是另一个示例-一个人制作了自己的就地模块,以在文件编辑的就地部分中修复标准文件输入模块的API的曲率和不完整性。
好吧,由于我碰到的不止一个,所以我经常想到这样的情况,所以要小心并且不要忘记看各种有用的很棒的清单,我认为一个好的营养学家会对解决方案的合理性有所了解,顺便说一下,这是另一个讨论的话题-根据我的感觉(当然,没有关于这个主题的统计数据,显然不可能),在python世界中,专家的水平高于平均水平,因为通常好软件都是用python编写的,请在评论中写下您对此的看法。
并发与竞争
Python为并行和竞争性编程提供了充足的机会,但并非没有功能。
如果您需要并行处理,而这在您的任务需要计算时发生,那么您应该注意多处理模块。
而且,如果您的任务对IO有很高的期望,那么python提供了很多选项供您选择,从线程和gevent到asyncio 。
所有这些选项看起来都非常适合使用(尽管线程需要更多的资源),但是感觉asyncio会慢慢挤出其余部分,这要归功于uvloop之类的各种好东西 。
如果有人没有注意到-在python中,线程与并行性无关,那么我就没有足够的能力来谈论GIL ,但是关于该主题的材料足够多,因此没有这种需要,主要要记住的是python中的线程(更确切地说,在CPython中)它们的行为不同于其他编程语言-它们仅在一个内核上运行,这意味着它们不适合需要真正的并行性的情况,但是,线程执行在等待输入/输出时会暂停,因此可以使用它们 竞争。
其他怪异
在python中, a = a + b
并不总是等同于a += b
:
a = [1] a = a + (2,3) TypeError: can only concatenate list (not "tuple") to list a += (2,3) a [1, 2, 3]
我将其发送给SO以获得详细信息,直到我有时间找出原因,从某种意义上说,他们这样做的原因,就像这又是关于可变性的。
不是奇数的奇数
乍一看,我对范围类型不包括右边界感到奇怪,但是随后一个好心的人告诉我忽略了我需要学习的地方 ,结果证明一切都很合乎逻辑。
一个单独的大话题是四舍五入(尽管这个问题对于几乎所有编程语言都是常见的),除了您喜欢使用四舍五入之外,除了每个人都在学校课程中学习过数学之外,因为表示浮点数的问题仍然叠加在上面,我指的是详细的文章 。
粗略地说,不是使用学校数学中常用的半圆算法,而是使用了半偶数算法,该算法减少了统计分析中失真的可能性,因此是IEEE 754标准推荐的算法。
另外,我不明白为什么-22//10=-3
,然后另一位善良的人指出 ,这不可避免地遵循数学定义本身,根据该定义,余数不能为负,从而导致这种异常行为。负数。
阿雄! 现在,这又是一件奇怪的事情,我什么都不懂,请参见此主题 。
正则表达式调试
事实证明,在python世界中,没有类似于优秀的Pearl模块Regexp :: Debugger ( 视频演示 )的工具来交互式地调试正则表达式,当然有很多在线工具,有某种Windows专有的解决方案,但对我来说不是,也许值得使用Pearl Bar工具,因为python rexes与Pearl Bar差别不大,所以我将为那些不拥有Pearl Bar的人编写说明:
sudo apt install cpanminus cpanm Regexp::Debugger perl -I ~/perl5/lib/perl5/ -E "use Regexp::Debugger; 'ababc' =~ /(a|b) b+ c/x"
我认为,即使不熟悉珍珠的人也可以理解在哪里需要输入行,在哪里是正则表达式, x
是类似于python re.VERBOSE的标志。
按s
并逐步浏览正则表达式,即文档中可用命令的详细说明。
该文件
python中有一个帮助函数,它允许您获取任何已加载函数的帮助(取自其文档字符串),该函数的名称作为参数传递:
$ python3 >>> help(help)
但这并不总是一种方便的方法,使用pydoc实用程序通常更方便:
pydoc3 urllib.parse.urlparse
该实用程序允许您按关键字搜索,甚至可以使用html文档启动本地服务器,但是我尚未对其进行测试。