坏蛋Dan
知乎@坏蛋Dan
发布时间:2024.1.4

前言

昨天我们了解了下python的基础知识

坏蛋Dan:python简单入门--day1:基础知识

今天我们来了解下运算符和表达式

写这篇文章是面向有编程基础的(最好是JavaScript,很多地方都一样),所以会跳过一些(很多)知识。


运算符(Operators)

表达式(expressions)在python中和其他语言的都一样,这个是一种编程的概念,不是编程语言的,比如2 + 3就是一个表达式,可以简单的理解为是由运算符(operators) + 运算对象(operands)组成。

大部分运算符的作用都是一样的,以下几个是比较特殊、不常用的(并不一定是其它语言没有的)

  • **:表示power,也就是幂。比如3**4 == 81,也就是3 * 3 * 3 * 3js中也可以这么写。
  • //:表示除并且向下取整。比如13 // 3 == 4, -13 // 3 == -5
  • <<:进位运算,往左进几位。比如2 << 2 == 82在二进制中是10,而往左进2为变成1000,对应十进制的8
  • >>:进位运算,往右移几位。比如11 >> 1 == 511在二进制中表示1011,而网右移1位之后变成101,对应十进制的5
  • &:位运算与,只有每一位对应的数都是1的时候才为1,比如5 & 3 == 1,因为101011对应下来变成了001
  • |:位运算或,对应的位只要有一个是1,那么就是1,比如5 | 3 == 7,因为101011对应下来变成111,对应十进制的7
  • ^:位运算异或,当对应的位都是1或者0的时候都会变成0,一个为1一个为0才会变成1。比如5 ^ 3 == 6,因为101011对应下来变成110对应十进制的6
  • ~:位运算非,就是字面上的意思,将1变成0,将0变成1。比如~5变成-6,实际上00000101取非之后变成11111010,而这正好是-6的二进制表示,至于如何得到-6,实际上是先把00000110变成11111001也就是取反码,然后再给它加1取补码变成11111010。然后再加1,即0101变成1101加一。你可以用一个简单的记法:-(x+1)
  • ==:对标后端语言的==,这个不会做类型转换,相当于前端的===,比如'str' == 'stR'会返回False
  • not:类似位运算非,如果xTrue,那么not x就是False
  • and:类似位运算与,x = False, y = True,那么x and yFalse
  • or:类似位运算或,x = False, y = True,那么x or yFalse。这三个都是boolean的。

至于运算的优先级这里就不说了,和其它语言的一样。


控制流(control flow)

python中的控制流有以下几种:if、for、while

你可能会问switch呢?不好意思,没有

if语句

一般也叫if-block也就是if块。

用法直接看例子

python中不需要()包裹if的表达式,也不需要{}来包裹对应的block,通过缩进 + :来替代。

另外else ifpython中写作elif

input函数是内置函数,用于获取用户输入的内容,int方法也是内置的,但它不是函数而是一个class,将内容转换为整数。


while语句

和别的语言有所不同,python里可以while + else搭配

直接来看下例子

另外python中的boolean字面量都是大写的:FalseTrue。 也可以用10来表示。


for语句

它同样也可以接else语句

直接来看例子

img_for_control_flow

range也是内置函数,用来创建一个range对象,它是一个序列(*sequence*),可迭代的。

现在我们暂时不知道序列是什么,后面会接触到。

range还可以接受第三个参数,表示范围步进的基数。比如range(1, 5, 2)表示每次进2,那么打印出来的结果应该是1, 3

另外如果你想立即获取这个range,那么你需要搭配list内置函数。后面我们会接触到。

至于continuebreak的使用和其它语言一样,就不多说了。


函数

函数定义

python中保持着不用{}的理念,所以函数的定义中也不会有{}的存在,我们直接来看下一个例子

这就是一个python中普通的函数定义,关键字def,然后加上函数名(下划线或者驼峰都没问题,甚至不下划线不驼峰都行) 然后接上:表示下面的内容是函数的主体,这里也是严格按照缩进来判断。


函数参数

参数定义不需要定义类型,直接来看下例子

不用多说,不需要类型定义。

后面我们还会接触到一些简单的方式用来获取参数,毕竟总不能写一大串参数吧。。即使是Rust也是有办法实现动态参数的。


作用域

这一点和其它语言差不多,都是内部可以访问外部全局变量,但是外部无法访问内部参数,同名变量优先获取最近一个作用域的。

直接来看下例子


global

如果在局部作用域中想要访问全局的变量,那么就需要用到关键字global,比如

一般用在上级作用域中存在同名变量的情况,不然一般情况下不需要使用。

当然,全局变量一般命名都是很谨慎的,比如__a__这样。


参数默认数据

js一样,如果想要给一个参数默认数据,那么在定义的地方加上=xx即可。

这里挺有意思的,*符号可以用于重复一段字符串。


关键字参数

上面我们说过,python里有几种可以方便传参的方式,这里我们来先说一个关键字参数。

先直接来看下例子

关键点就在于我们在给func传参的时候指定了变量的名字。这样我们就可以只传某个参数了。

不用考虑参数的顺序,类似map的匹配而不是下标。

不过这里有一个问题,就是我们需要事先知道这个函数的参数命名,一般都没啥问题。


可变参数

上面的方法虽然方便,但是仅针对于传参,我们还想在定义的时候可以方便点。那么就可以用到可变参数这种方式,我们直接来看下例子

有些不直观了。。

这里***的意思是不一样的

  • *表示将所有的非关键字传参的数据组成一个元组(tuple)
  • **表示将所有的关键字传参的数据组成一个字典(dictionary)

我们再来看个例子

可以看到这里报错了,因为我们传递数据的时候并没有连续,在关键字传参的后面我们又给加上了几个无关键字的参数。

这样是不可以的,关键字传参和其它方式传参都必须是连续即中间不能出现别的方式传参。

不过这样理论上也够用了。

至于什么是字典,后面我们会遇到,而元组相信大家都很熟悉了,这里就不多说了。


return

没啥好说的,只有下面几个小点需要注意。

python里函数默认都隐式(implicitly)实现了return None,如果你不写return语句,那么就会返回默认的None

另外你还可以使用pass关键字来在一个block中不写任何的东西,这话有点不明不白,简单地说就是定义了一个block你必须写点东西,不然就会报错。

但是现在我们可以使用pass关键字来绕过这个问题。

返回值也是None

不过理论上不会用到这个pass关键字,如果真要用到,那么建议是优化你的逻辑,很大概率上是你的逻辑有一些问题,还可以用跟好的方式去实现。


文档字符串(DocStrings)

DocStringsdocument strings的缩写。

看名字就是和注释有关的。

不多废话,先看例子

可以看到我们用'''三重单引号包裹的内容在__doc__之后就打印了出来,有些类似函数的私有属性。

这里就体现了python的其中一个理念:所有的东西都可以作为object,当然也包括了函数,这个__doc__是函数的attribute

实际上'''三重单引号包裹的内容是函数的document,也就是函数的文档,你可以用来描述这个函数。

这个DocStrings同样适用于modules[1]classes[2]

这里我们也可以使用help这个内置函数来打印这个函数的document strings

不过需要注意的是需要按下q键来exit它。


模块(modules)

python中,要定义一个模块可以有好几种方法,但是最简单的还是定义一个.py文件。文件天然就是一个模块。

另外你也可以使用实现python编译器的语言比如C[3]来写模块,这样在python代码被编译的时候就可以直接使用这个模块。

一个模块可以通过import的方式被其它模块使用。这就是为什么我们可以直接使用标准库里的函数的原因。

比如sys模块


中间码(Byte-compiled) .pyc 文件

模块的import是一件非常昂贵的事情,也就是慢,毕竟涉及到文件操作,所以python中做了一些优化来让它变得快一些。

其中一种方式就是创建.pyc的中间码文件,它是一种中间形式,即字节码,python是一种解析型语言,和javascript一样,理论上都不需要compiler。具体可以看这:About Python · HonKit (swaroopch.com)

同一个.pyc文件在下次调用的使用会快很多,因为里面有些逻辑已经执行了。

另外.pyc文件是独立于平台的(platform-independent)。

需要注意的一点:.pyc文件一般都是根据.py文件在同一个文件目录下创建,如果你没有给python写的权限,那么.pyc文件创建就会失败。


from ... import

这个不多说,是js中的import from倒装,不过用法一样。

直接看例子


name

每一个模块都有自己的__name__,这对于我们用来判断一个模块是单独的运行还是被其它模块引入来说是非常方便的。

直接来看下例子

我们随便搞个新文件test.py,然后把代码copy到里面

然后在我们之前的文件中引入并使用

可以看到__name__不指向__main__

然后我们稍微修改下test.py模块

可以看到当我们运行python test.py的时候__name__就指向了__main__

__main__表示当前模块是独立运行的,而不是别的模块运行的。


创建一个模块

我们上面创建的test.py实际上就是一个模块了,不过我们还需要补充点内容。

然后我们直接在另一个模块中导入整个test模块

然后执行下

可以看到__version__test函数是作为test模块的attributemethod,是可以直接访问的,就和前面遇到的sys.path一样。

然后我们还可以只导入我们想要的东西

效果也是一样的。

另外你还可以直接用*表示导入所有方法,*表示导入所有的东西。

可以看到报错了,因为我们用的*会过滤掉__双下划线开头的属性。

另外需要注意尽量不要使用*,因为一方面不便于阅读理解,另一方面如果存在多个模块的import *,那么这个时候就可能存在覆盖的问题。


dir函数

dir内置函数可以返回一个Object的一组字段名,简单地说,就是类似前端的Object.keys[4]

如果这个对象是一个模块,那么它也会获取定义在这个模块中的函数、类和属性(变量)。

直接来看下例子

不加参数指向当前调用这个方法的模块。

这里还有一个类似的方法vars[5],它也是返回一组属性,但不是所有情况下都可以使用。


Packages

packages就是一个存放modules的文件夹,不过它有一个特殊的文件__init__.py。这个文件指明这个文件夹是一个packages,里面包含modules

标准库中就有很多例子,我们后面会接触到的。


总结

绕过了一些东西,因为是大同小异的,到时候写代码自然会接触到。

参考

  1. ^modules https://python.swaroopch.com/modules.html#modules
  2. ^classes https://python.swaroopch.com/oop.html#oop
  3. ^C language http://docs.python.org/3/extending/
  4. ^Object.keys https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
  5. ^vars http://docs.python.org/3/library/functions.html#vars

发布于 2023-04-02 17:53・IP 属地广东