对于所有有似曾相识感觉的habrach居民:文章“ Python简介”提示我写这篇文章,并对此发表评论。 不幸的是,这种“介绍”技术的质量……让我们不要谈论悲伤的事情。 但是在评论中观察到争吵甚至更可悲,例如“ C ++比Python更快”,“ Rust甚至比C ++更快”,“不需要Python”等类别。 他们不记得Ruby真是太神奇了!
正如Bjarn Stroustrup所说,
“只有两种类型的编程语言:人们一直在发誓的语言和没人使用的语言。”
欢迎来到每个想熟悉Python而又不因肮脏的诅咒而死的人!
东高加索山脉的早晨有哭声。 两个年轻人坐在一块大石头上,积极地讨论着一些东西,积极地打手势。 一分钟后,他们开始互相推挤,然后抓钩并从一块巨石上掉进了一个荨麻灌木丛中。 显然,灌木丛在那里生长是有原因的-他立即安抚了争吵者,并为他们无法解决的纠纷停火了。 您可能猜到了,我是辩论者之一,另一个是我最好的朋友(您好,Quaker_t!),但是我们的闲聊主题是Visual Basicvs。 德尔福 !
你认识你自己吗? 有时,我们会将自己喜欢的编程语言变成一种狂热分子,并准备将其捍卫到最后! 但是随着时间的流逝,当争执的主题从“ A vs. B”发展为“我更愿意与A一起工作,但是如果有必要,我将学习如何与B,C,D,E 以及通常一起使用任何东西一起工作 。” 只有当我们遇到新的编程语言时,旧的习惯和文化可能不会让我们走很长时间。
我想向您介绍Python,并帮助您将经验转移到一个新的方向。 像任何技术一样,它也有其优点和缺点。 像C ++,Rust,Ruby,JS以及其他所有人一样,Python是一种工具。 说明附在任何仪器上,您必须学习正确使用任何仪器。
“作者,没有头脑,您打算向我们介绍Python吗?” 让我们结识!
Python是一种动态的高级通用编程语言。 Python是具有丰富生态系统和传统的成熟编程语言。 尽管该语言于1991年发布,但它的现代外观在2000年代初开始成形。 Python是一种收费的语言,在其标准库中有很多解决方案。 Python是一种流行的编程语言:Dropbox,Reddit,Instagram,Disqus,YouTube,Netflix,该死,甚至Eve Online以及许多其他积极使用Python的语言。
如此受欢迎的原因是什么? 经过您的许可,我将介绍我自己的版本。
Python是一种简单的编程语言。 动态输入。 垃圾收集器。 高阶函数。 用于处理字典,集合,元组和列表的简单语法(包括获取切片)。 Python非常适合初学者:它使从过程编程开始,缓慢地切换到OOP以及品尝函数式编程成为可能。 但是这种简单性就像冰山一角。 当您遇到Python的哲学-Zen Of Python时,值得深入研究。 深入探讨-您会发现自己掌握了一套清晰的代码设计规则-Python代码样式指南 。 潜水时,程序员逐渐研究“ Python方式”或“ Pythonic”的概念。 在语言学习的这个惊人的阶段,您开始理解为什么用这种方式编写好的Python程序,而不是其他方式。 为什么语言朝着这个方向发展,而不是朝着另一个方向发展。 Python没有成功。 但是他成功地完成了我们工作中最重要的方面-可读性。 “为人而不是为汽车编写代码”-这是Python基础的基础。
好的Python代码看起来很漂亮。 并编写漂亮的代码-什么不是愉快的职业?
提示0: 在进一步阅读之前,请先看一下Zen Python角落。 语言基于这些假设,如果您熟悉它们,我们的交流将更加愉快。
哪个聪明的人想到了缩进?
对于那些从未使用过Python的代码的人来说,首先感到震惊的是指令体的缩进:
def main(): ins = input('Please say something') for w in ins.split(' '): if w == 'hello': print('world!')
我记得在圣彼得堡理工学院的宿舍里的晚上,我的邻居VlK眼睛发亮 ,告诉我他发掘了Python中的一些新东西。 “压痕体?认真吗?” -是我的反应。 确实,对于一个通过C,C ++和Java从Visual Basic( if ... end if
)到C#(大括号)的人来说,这种方法似乎有些奇怪。 “您是否使用缩进来格式化代码?” VlK问。 我当然格式化了。 更准确地说,螺旋Studio Visual为我做到了。 她做得很好。 我从没考虑过格式和缩进-它们是自己出现在代码中的,似乎有些普通和熟悉。 但是没有什么可隐藏的-代码始终采用缩进格式。 “那么,如果说明主体在任何情况下都向右移动,那么为什么需要花括号?”
那天晚上,我坐下了Python。 回顾过去,我可以肯定地说是什么有助于快速吸收新材料。 这是一个代码编辑器。 受同一个VlK的影响,在发生上述事件之前不久,我从Windows切换到Ubuntu和Emacs担任编辑器(在2007年,转而使用PyCharm,Atom,VS Code等)。 “好吧,现在Emacs将PR ...”-您说。 只是一点点:)传统上,Emacs中的<tab>
键不添加标签,而是根据此模式的规则来对齐行。 按下<tab>
-并将代码行移至下一个合适的位置:

这样,您就不必考虑代码是否正确对齐了。
提示1: 当您了解Python时,请使用负责缩进的编辑器。
您知道所有这些耻辱有什么副作用吗? 程序员试图避免冗长的构造。 一旦函数的大小超出了屏幕的垂直边界,就很难区分给定代码块属于哪个设计。 而且投资越多,难度就越大。 结果,您尝试尽可能简洁地编写代码,以破坏函数,循环,条件转换等冗长的主体。
哦,你的动态打字
哦,这种讨论几乎一直存在,只要存在“编程”的概念! 动态类型输入既不好也不好。 动态类型输入也是我们的工具。 在Python中,动态类型提供了极大的操作自由度。 在行动自由度更高的地方,更有可能枪杀自己。
值得说明的是,在Python中键入是严格的 ,并且不能在字符串中添加数字:
1 + '1' >>> TypeError: unsupported operand type(s) for +: 'int' and 'str'
当调用函数时,Python还会检查该函数的签名,如果调用签名不正确,则会抛出异常:
def sum(x, y): return x + y sum(10, 20, 30) >>> TypeError: sum() takes 2 positional arguments but 3 were given
但是在加载脚本时,Python不会告诉您该函数需要一个数字,而不是传递给它的字符串。 而且您只能在运行时了解它:
def sum(x, y): return x + y sum(10, '10') >>> TypeError: can only concatenate str (not "int") to str
程序员面临的挑战越来越大 , 尤其是在编写大型项目时 。 现代Python通过注释引擎和类型库解决了这一难题,并且社区已经开发了执行静态类型检查的程序 。 结果,程序员在执行程序之前了解了以下错误:
尽管Python将注释存储在__annotations__
属性中,但它并不重视注释。 唯一的条件是,注释在语言方面必须是有效值。 自从它们出现在3.0版中(距今已经十多年了!)以来,社区的努力开始使用注释对变量和参数进行类型化标记。
技巧2: 在实践中,大多数动态类型会在读取和调试代码时引起问题。 尤其是如果此代码编写时没有注释,并且您必须花费大量时间弄清楚变量的类型。 您不必指示和记录所有内容的类型,但是花在对公共接口和代码的最关键部分进行详细描述上的时间将得到一百倍的奖励!
! 鸭打字
有时,Python爱好者装作很神秘,并谈论“鸭子打字”。
鸭子输入是鸭子测试在编程中的使用:
如果一个物体像鸭子一样嘎嘎叫,像鸭子一样飞翔,像鸭子一样走路,那么它很可能就是鸭子。
考虑一个例子:
class RpgCharacter: def __init__(self, weapon) self.weapon = weapon def battle(self): self.weapon.attack()
这是经典的依赖注入。 RpgCharacter
类在构造函数中接收weapon
对象,随后在weapon.attack()
方法中调用weapon.attack()
。 但是RpgCharacter
并不依赖于该weapon
的具体实现。 它可以是剑,BFG 9000或带有花盆的鲸鱼,随时可以降落在敌人的头上。 重要的是对象必须具有attack()
方法,Python对其他所有内容都不感兴趣。
严格来说,鸭的打字不是唯一的。 它以实现OOP的所有(熟悉的)动态语言存在。
这是如何在动态类型世界中仔细编程的另一个示例。 方法名称不正确? 不确定地命名为变量? 大约半年后,您的同事或您自己将很乐意提出这样的代码:)
如果使用条件Java会发生什么? interface IWeapon { void attack(); } public class Sword implements IWeapon { public void attack() {
并且会有一个经典的静态类型,在编译阶段进行类型检查。 IWeapon
无法使用具有IWeapon
attack()
方法的对象,但未明确实现IWeapon
接口。
提示3 : 如果愿意,可以通过使用方法和属性构建自己的抽象类来描述接口。 更好的是,花时间为您自己和您的代码用户彻底测试和编写文档。
程序方法和__特殊方法__()
Python是一种面向对象的语言,而object
类是继承层次结构的根:
isinstance('abc', object) >>> True isinstance(10, object) >>> True
但是在Java和C#中使用obj.ToString()
地方,将在Python中调用str(obj)
。 例如,Python将使用len(my_list)
代替myList.length
。 语言的创建者Guido van Rossum对此进行了如下解释:
当我读到len(x)
的代码时,我知道请求的长度。 这立即告诉我结果将是一个整数,并且参数是某种容器。 相反,在读取x.len()
,我需要知道x
是某种实现特定接口或从具有len()
方法的类继承的容器。 [来源]
然而,在函数内部,函数len()
, str()
和其他一些函数将调用对象的某些方法:
class User: def __init__(self, name, last_name): self.name = name self.last_name = last_name def __str__(self): return f"Honourable {self.name} {self.last_name}" u = User('Alex', 'Black') label = str(u) print(label) >>> Honourable Alex Black
语言运算符,包括数学运算符和布尔运算符,以及for ... in ...
循环运算符, with
上下文运算符,索引运算符[]
等,也使用特殊方法。
例如,迭代器协议包含两种方法: __iter__()
和__next__()
:
好吧,让我们说一些特殊的方法。 但是为什么它们看起来如此扭曲? Guido通过这样的事实来解释这一点,即他们具有惯用的名字而不会强调,程序员本身不会至少早晚重新定义它们。 即 ____()
是对傻瓜的一种保护。 正如时间所显示-保护是有效的:)
提示4: 仔细研究内置函数和特殊对象方法 。 它们是语言不可或缺的一部分,没有它,就不可能完全说出来。
封装在哪里? 我的私人在哪里? 我的童话在哪里?
Python没有类属性的访问修饰符。 物体的内部无障碍开放。 但是,有一个约定,将前缀_
视为私有属性,例如:
import os class MyFile:
怎么了
Python中没有什么私有的。 该类及其实例都不会向您隐藏内部的内容(这是由于可以进行最深入的内省)。 Python信任您。 他有点说:“伙计,如果您想在黑暗的角落闲逛-没问题。我相信这是有充分的理由的,我希望您不要破坏任何东西。
最后,我们都是成年人。
-Karl Fast [来源] 。
但是如何避免在继承过程中发生名称冲突?Python有一种特殊的机制来处理以双下划线开头但不以双下划线结尾的属性名称( __my_attr
)! 这样做是为了避免在继承期间发生名称冲突。 为了在体外调用类方法,Python添加了___
前缀。 但是对于内部访问,没有任何变化:
class C: def __init__(self): self.__x = 10 def get_x(self): return self.__x c = C() c.__x >>> 'C' object has no attribute '__x' print(c.get_x()) >>> 10 print(c._C__x) >>> 10
让我们看一个实际的应用程序。 例如,对于要从本地文件系统读取文件的File
类,我们要添加缓存功能。 为此,我们的同事设法编写了一个mixin类。 但是为了将方法和属性与潜在的冲突区__
来,一位同事在名称中添加了__
前缀:
class BaseFile: def __init__(self, path): self.path = path class LocalMixin: def read_from_local(self): with open(self.path) as f: return f.read() class CachedMixin: class CacheMissError(Exception): pass def __init__(self):
如果您有兴趣在CPython中查看此机制的实现,请在Python / compile.c中
最后,由于语言中存在属性,因此以Java风格编写getter和setter毫无意义: getX(), setX()
。 例如,在最初编写的“ Coordinates
类中,
class Coordinates: def __init__(self, x, y): self.x = x self.y = y c = Coordinates(10, 10) print(cx, cy) >>> (10, 10)
我需要控制对x
属性的访问。 正确的方法是将其替换为property
,从而与外界保持合同。
class Coordinates: _x = 0 def __init__(self, x, y): self.x = x self.y = y @property def x(self): return self._x @x.setter def x(self, val): if val > 10: self._x = val else: raise ValueError('x should be greater than 10') c = Coordinates(20, 10) cx = 5 >>> ValueError: x should be greater than 10
提示5: 与Python一样,私有字段和类方法的概念基于已建立的约定。 如果“一切都停止了工作”,请不要因库的作者而冒犯,因为您已经积极使用了它们类的私有字段。 最后,我们都是成年人:) 。
关于异常的一点
Python文化对异常有独特的处理方法。 除了通常的拦截和处理la C ++ / Java之外,您还将在上下文中遇到异常的使用
“寻求宽恕比获得许可更容易-EAFP。”
解释一下- if
在大多数情况下,执行将在此分支上进行,请不要写太多。 相反,将逻辑包装在try..except
。
示例:想象一个在条件数据库中创建用户的POST请求处理程序。 在函数的输入处是键值类型的字典(字典):
def create_user_handler(data: Dict[str, str]): try: database.user.persist( username=data['username'], password=data['password'] ) except KeyError: print('There was a missing field in data passed for user creation')
我们没有通过检查“数据中是否包含username
或password
”来污染代码。 我们希望他们很可能会在那里。 我们不要求“许可”使用这些字段,而是在下一个kulhacker发布缺少数据的表单时“道歉”。
只是不要把它带到荒谬的地步!例如,您想检查数据中是否存在用户的姓氏,以及是否未将其设置为空值。 if
这里更合适:
def create_user_handler(data): if 'last_name' not in data: data['last_name'] = '' try: database.user.persist( username=data['username'], password=data['password'], last_name=data['last_name'] ) except KeyError: print('There was a missing field in data passed for user creation')
错误绝不能默默传递。 -不要忽略例外! 现代Python raise from
构造上有了惊人的raise from
它使您可以维护异常链的上下文。 例如:
class MyProductError(Exception): def __init__(self): super().__init__('There has been a terrible product error') def calculate(x): try: return 10 / x except ZeroDivisionError as e: raise MyProductError() from e
如果没有raise from e
异常链将在MyProductError
中断,并且我们无法找出导致此错误的确切原因。 通过raise from X
引发,引发异常的原因(即X
)存储在__cause__
属性中:
try: calculate(0) except MyProductError as e: print(e.__cause__) >>> division by zero
但是在迭代的情况下有一点细微差别:StopIteration对于迭代,抛出StopIteration异常是信号表明迭代器已完成的正式方法。
class PositiveIntegers: def __init__(self, limit): self.counter = 0 self.limit = limit def __iter__(self): return self def __next__(self): self.counter += 1 if self.counter == self.limit:
提示6: 我们仅在特殊情况下为异常处理付费。 不要忽视他们!
以前的方法应该只有一种,最好只有一种。
switch
或模式匹配? -使用if
和字典。 do-
? -为此,有一段while
。 goto
? 我想你自己猜到了。 这同样适用于其他语言似乎理所当然的某些设计技术和模式。 最令人惊讶的是,对其实施没有技术限制,只是“我们没有这种方式”。
例如,在Python中,您通常不会看到“ Builder”模式。 相反,它使用了将名称参数传递并显式请求给函数的功能。 相反
human = HumanBuilder.withName("Alex").withLastName("Black").ofAge(20).withHobbies(['tennis', 'programming']).build()
将是
human = Human( name="Alex" last_name="Black" age=20 hobbies=['tennis', 'programming'] )
标准库不使用方法链来处理集合 。 我记得一位来自Kotlin的同事如何向我展示以下含义的代码(摘自Kotlin的官方文档 ):
val shortGreetings = people .filter { it.name.length < 10 } .map { "Hello, ${it.name}!" }
在Python中, map()
, filter()
和其他许多功能都是函数,而不是集合方法。 逐一重写此代码,我们得到:
short_greetings = map(lambda h: f"Hello, {h.name}", filter(lambda h: len(h.name) < 10, people))
我认为它看起来很糟糕。 因此,对于诸如.takewhile().filter().map().reduce()
之类的长束,最好使用所谓的 包容 (理解)或良好的旧周期。 顺便说一下,以相应的列表理解的形式给出了关于Kotlin的相同示例。 在Python上看起来像这样:
short_greetings = [ f"Hello {h.name}" for h in people if len(h.name) < 10 ]
在方法链比标准工具更有效的地方使用方法链。 例如,在Django Web框架中,链被用来构建数据库查询对象:
query = User.objects \ .filter(last_visited__gte='2019-05-01') \ .order_by('username') \ .values('username', 'last_visited') \ [:5]
提示7: 在您根据过去的经验做过非常熟悉但对Python不熟悉的事情之前,请问自己有经验的python专家会做出什么决定?
Python慢
是的
是的,与静态类型和编译语言相比,执行速度更高。
但是您似乎想要详细的答案吗?
Python参考实现(CPython)远非其最有效的实现。 重要原因之一是开发人员希望不使其复杂化。 逻辑是可以理解的-不太深刻的代码意味着更少的错误,更好的机会进行更改,最终,更多的人想要阅读,理解和补充此代码。
杰克·范德普拉斯(Jake VanderPlas)在他的博客中分析了当添加两个包含整数值的变量时,CPython幕后的情况 :
a = 1 b = 2 c = a + b
即使我们不深入CPython的丛林,我们也可以说要存储变量a
, b
和c
,解释器将不得不在堆上创建三个对象,其中将存储类型和(指针)值; 在加法操作期间重新确定类型和值,以调用诸如binary_add<int, int>(a->val, b->val)
; 将结果写入c
。
与类似的C程序相比,这是非常低效的。
CPython的另一个麻烦是所谓的 全局解释器锁定(GIL)。 该机制本质上是一个由互斥锁括起来的布尔值,用于同步字节码的执行。 GIL简化了在多线程环境中运行的代码的开发:CPython无需考虑同步对变量或死锁的访问。 您必须为此付出代价,因为只有一个线程可以访问并在给定的时间执行字节码 :
UPD:但这并不意味着Python上的程序可以在多线程环境中神奇地工作! Python上的代码不会一一传输到字节码,并且不能保证版本之间字节码的兼容性! 因此,您仍然必须同步代码中的线程。 幸运的是,这里的Python具有丰富的工具集,例如,允许您在多线程和多进程执行模型之间进行切换。
?
- . ( CFFI ) . API (extensions) C/C++. , Rust, Go Kotlin Native !
- , :
8: , . , IO (, , ) , , , , :)
? Linux MacOS, 95% . , 3., 2.7. Windows . : Docker, Windows Subsystem for Linux, Cygwin, , .
9: . , — - .
"Hello world" ? 太好了! machine learning- - Python Package Index (PyPI).
(packages), .. (virtual environments). , . - . pip
. pip
. , pipenv
poetry
— npm, bundler, cargo ..
0xA: — pip
virtualenv
. — , , . , — sys.path
— , .
?
? . :
Dive into python...
, . , , :)
, !