这酸爽才够味!别样的Python编码|Python 主题月

863 阅读6分钟

本文正在参加「Python主题月」,详情查看 活动链接

开篇声明

我掘金活跃的版块主要集中在App端,学习Python完全是个人自动化需要(滑稽),在我为数不多的Python文章都是写简单的爬虫,没错就是那种不需要cookie、不需要登录、不要认证的爬虫。

所以在Python技术上,我确实没什么可说,但是通过写爬虫以及之前iOS编程的积累,我对Python这门语言还是有收获和思考的东西。

我的整体编码风格是偏前端化,Swift化的。

这篇文章你可以认为是站在门外的人,看看Python的一点思考。

另外我想说的是,每个人都有每个人的编码风格,虽然Python有Python的规范,不过我自己确实是一套风格走到底。大家不喜勿喷。

注意:本文的代码基于Python3.7,编写例子的IDE是VSCode。

定义一个变量赋值

Python中赋值异常的简单,你不要声明变量的类型,亦不要什么修饰符来修饰变量,简简单单就完事了:

a = 1

如果想要改变a的值,直接改变就可以了:

a = "Hello, Python"

a = True

a可变,可以保存不同的数据类型。

Python是一门弱语言,弱语言的优势就是灵活,以至于可能在一个函数中,有非常长的逻辑实现,对变量多次赋值,然后根本就不知道变量是什么类型。

孰不知其实Python在定义变量的时候,是可以声明变量的类型的,只是我很少看见有人这么写:

  • Python的书写方式
a: str = "aa"
b: int = 1

定义了变量的类型,在使用的变量的时候IDE也会提示变量是什么类型喔:

image.png

这样一些就有那么点Swifty了:

  • Swift的书写方式

/// 考虑Python中所有的变量都是可变的,所以这里用var修饰,
var a: String = "aa"

/// Swift有自动推断类型的机制,这里写为 var b = 1,b也会被推断为Int类型
var b: Int = 1

怎么样,是不是有内味了?

不过需要注意的是,就算在Python中显示的声明变量的类型,变量的类型并没有被固定,还是可以依旧想接什么类型就接什么类型。


a: str = "aa"

a = 2

print(a)

虽然a被定义成了str类型,但是这里还是可以对a赋值2,并且不会报错,调用print函数也没什么异常。

我觉得,虽然变量可以接各种类型是一件很方便的事情,但是如果处于安全亦或者其他人的阅读习惯,变量一开始被赋予了什么样的类型,最好能一以贯之。减少对类型的判断,虽然isinstance (a,int)用起来也很方便。

给类型取别名

我们可以对已有类型或者自定义类型取一个别名,方便书写与理解。

比如我们希望通过x,y两个值来定义一个位置的坐标点,我们可以这样:

  • Python的书写方式
Location = (float, float)

Location代表一个元组,里面包含两个float类型的变量。

  • Swift的书写方式
typealias Location = (Float, Float)

你或许会觉得这么写有什么用,那么通过下一节的说明,或许你会发现其中的价值。

函数的入参与出参定义

这里有这么一个需求:有一个函数需要这样一个功能,输入两个坐标,求得两点的距离。

你一定会说这有何难?Python小白都知道该怎么写?

def distanceBetweenPoint1(point, toPoint):
    x1, y1 = toPoint
    x2, y2 = point
    dx = x1 - x2
    dy = y1 - y2
    distance = math.sqrt(dx * dx + dy * dy)
    return distance

调用:

distance1 = distanceBetweenPoint1((0, 2), (5, 7))

那么下面就是我灵魂拷问的时间了: 请问一般人会知道point、toPoint是什么类型吗?

也许你会说: 我写注释,告诉用户这个函数的入参是什么类型就可以,甚至写个小例子不就好了。

没错,可以写一段注释进行说明,而且写注释也是好习惯,但是如果我们可以通过入参类型明确化、语义化,我们可以少写点注释,多摸鱼一下。

下面就是我进行的稍许改造:

Location = (float, float)

def distanceBetweenPoint2(point: Location, toPoint: Location):
    x1, y1 = toPoint
    x2, y2 = point
    dx = x1 - x2
    dy = y1 - y2
    distance = math.sqrt(dx * dx + dy * dy)
    return distance

调用:

distance2 = distanceBetweenPoint2(point=(0, 2), toPoint=(5, 7))
print(distance2)

就算不写注释,函数想要表达的意思也非常明确了呢?

但是你会发现,这个函数没有明确返回值类型,我们甚至可能不知道这个函数有返回值。

那么,下面是这个这个函数的最终表达方式

Location = (float, float)

Distance = float

def distanceBetweenPoint3(point: Location, toPoint: Location) -> Distance:
    x1, y1 = toPoint
    x2, y2 = point
    dx = x1 - x2
    dy = y1 - y2
    distance = math.sqrt(dx * dx + dy * dy)
    return distance

调用:

distance3 = distanceBetweenPoint3(point=(0, 2), toPoint=(5, 7))
print(distance3)

我取了两个别名Location和Distance,分别表达位置与距离,在函数的入参与出参中都定义了类型,这样看起来是不是一目了然?

而且你在调用这个函数的时候,会有明确的类型提示,既一目了然又安全:

image.png

和对变量赋值一样,就算函数中明确了类型,你依旧可以写出这样的函数入参distanceBetweenPoint3(point="haha", toPoint="hehe"),我将两个元组入参都传入的str类型,IDE不会提示你出错了,直到运行代码才抛出异常,值得大家注意。

至于Swift怎么写的,这里我就写个简单点的,具体实现就不写了:

typealias Location = (Float, Float)

typealias Distance = Float

func distanceBetweenPoint(point: Location, toPoint: Location) -> Distance {
    /// 逻辑和Python的实现类似
}

总结

本篇整体都围绕着对Python变量类型的明确进行,文章更多的笔墨是花在如何通过更好的编码风格去保证类型的安全性,可能你作为一个Python老手觉得我写的代码有些可笑,有些冗余,有些不可思议。

不过就我站在开发的角度而言,变量类型的确定是让人安心的一件事情,我iOS开发中恰好也有两种编程语言,Objective-C和Swift,分别是弱语言和强语言,正是吃过亏才有痛的领悟。

弱语言将变量的类型的明确推到运行时,灵活的代价就是你必须时刻警醒:数据是什么类型

而强语言在编译的时候就明确了数据的类型,用灵活换回了安全性。

没有谁好,没有谁不好,存在即合理。

想想JavaScript流行了这么多年,为啥微软还是要搞个TypeScript?