python拾遗

这篇博客主要记录一些深入的python知识点
主要参考文章:
python官方文档
https://tenthousandmeters.com/blog/python-behind-the-scenes-11-how-the-python-import-system-works/

python的import系统

什么是module与module object

module是python的一个类型,在types模块中有定义,为types.ModuleType
几种文件可以作为module:

  • .py文件
  • .so文件
  • .pyc文件
  • .pyd文件
  • .zip文件
    (可以从sys.path查看有哪些路径包含这些文件)

package与submodule

module可以拥有submodule,拥有submodule的module称为package
所以package是一种特殊的module,它含有__path__属性,指向一个list,list中的元素是str,表示package的搜索路径

-m switch
solution and explanation
https://stackoverflow.com/questions/16981921/relative-imports-in-python-3

import的过程

import是对__import__函数的封装,__import__函数的参数是一个str,表示module的名字
? 为什么__import__是一个python函数,而不是一个c函数
__import__的具体过程:

  1. resolve the module name

可以echo “import math” | python -m dis
可以查看__import__的docstring看看它的参数是什么

circular import的原因和解决方法

python的local与global变量

在Doc的programming FAQ中提到

In Python, variables that are only referenced inside a function are implicitly
global. If a variable is assigned a value anywhere within the function’s body,
it’s assumed to be a local unless explicitly declared as global.
已经非常明确说明了如果函数内部有对该变量的赋值操作,那么该变量就是local变量,否则就默认global变量

与闭包的联系

由于闭包里可能会对外部变量进行引用,这时候就会涉及到local与global变量的问题有以下例子:

squares = []
for i in range(5):
    squares.append(lambda: i ** 2)
print(squares[0]()) # 16
print(squares[1]()) # 16

无论调用squares中的哪个函数,都会返回16,这是因为闭包中的i是对外部变量i的引用,当闭包被调用时才去访问这个变量,而此时外部变量i已经变成了4
如果想要得到正确的结果,可以使用默认参数的方式:

for i in range(5):
    squares.append(lambda x=i: x ** 2)

这样每次调用闭包时,都会创建一个新的变量x,而x的值是在闭包创建时就已经确定了

如何在多个module中共享变量

在python中,module是一个namespace,不同的module之间是相互独立的,如果想要在多个module中共享变量,可以使用import来实现,但是这样会导致循环import的问题,解决方法是将变量放在一个单独的module中,然后在需要使用的module中import这个module,一般这个module会被命名为config或者settings

# config.py
x = 1
y = 2
# module1.py
import config
config.x = 2
# main.py
import module1
import config
print(config.x)

python中的数字与字符串

向下取整

-22 // 10 = -3这与C语言不同,C语言中-22 / 10 = -2。这么做的原因是python中i % j的结果正负号与j相同,而C语言中i % j的结果正负号与i相同。因为python认为j取负数的情况很少,取正数的情况很多(比如现在是10点,问190个小时之前是几点,-190%12返回2会比返回-10好,表示从当前开始继续向正方向走两个小时)。如果要满足i%j的结果与j正负相同,且i == (i//j)*j+(i%j)那i//j的结果就必须向下取整,也就与C不一样

字符串与数字互转

python的int函数可以将字符串转为10进制数字,默认传入的字符串是10进制的,如果传入的字符串是16进制的,需要指定base参数,python中的16进制以0x开头,八进制以0o开头,二进制以0b开头

将数字转为字符串用f-string可以很方便的实现,f-string是python3.6引入的新特性,可以在字符串前加f来使用,里面可以使用{}来引用变量,也可以在{}中使用表达式来计算以及格式化输出

字符串是不可变对象

在python中字符串是不可变对象,主要原因有以下几点:

  1. 很多时候字符串会作为hash的key,如果字符串是可变的,那么hash值也会变化,这样就不能作为字典的key
  2. 字符串是不可变的,可以在多个线程中共享,不用担心线程安全问题
  3. 性能优化,由于字符串不可变,可以在内存中共享,减少内存占用

如果想要原地修改字符串可以使用io.StringIO,它是一个类文件对象,可以像文件一样读写,但是它是在内存中的,不会写入磁盘。也可以使用array.array,它是一个数组对象,可以存储任意类型的数据,但是它的元素必须是同一种类型,例子如下:

import array
# 'u'表示array的元素是unicode字符
a = array.array('u', 'hello')
a[0] = 'H'
print(a) # array('u', 'Hello')
a.tounicode() # 'Hello'

但常见的方法是将字符串转为list,然后修改,再转回字符串

a = list('hello')
a[0] = 'H'
''.join(a) # 'Hello'

性能

两个分析性能的工具是cProfile和timeit,cProfile是一个分析器,可以查看函数调用的次数和时间,timeit是一个计时器,可以查看代码的运行时间

序列(tuple与list)

tuple(seq)会将可迭代对象转化为一个tuple,如果seq本身就是一个tuple,那么就会返回seq本身,如果seq是一个字符串,那么就会返回一个包含字符串中每个字符的tuple。list(seq)类似.

删除重复元素

比较常见的方法是使用set,但是这样会改变元素的顺序,如果想要保持原来的顺序,可以使用OrderedDict

from collections import OrderedDict
def remove_duplicates(seq):
    return list(OrderedDict.fromkeys(seq))

多维数组

一般推荐使用numpy构建多维数组,如果想使用list的嵌套实现多维数组需要注意列表的深浅拷贝问题,下面是一个经典的例子:

a = [[0]*3]*3
# a = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
a[0][0] = 1
# a = [[1, 0, 0], [1, 0, 0], [1, 0, 0]]

这是因为[0]*3会创建一个包含3个0的list,然后[[0]*3]*3会创建一个包含3个指向同一个list的list,也就是说list与*的运算是浅拷贝,正确的方法是使用列表生成式

a = [[0]*3 for _ in range(3)]

序列的+=操作

在python中+=会调用对象的__iadd__方法,如果对象没有实现__iadd__方法,那么就会调用__add__方法,然后将结果赋值给原对象。

a_tuple = ([],1)
a_tuple[0] += [1]
# TypeError: 'tuple' object does not support item assignment
# print(a_tuple) ([1], 1)

上面的错误是因为+=被解释为a_tuple[0] = a_tuple[0]._iadd([1]),而tuple是不可变对象所以a_tuple[0]被赋值时会报错,但a_tuple[0]._iadd([1])是可以执行的,所以虽然会报错,但是a_tuple的值已经被修改了

类与对象


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 2128099421@qq.com

×

喜欢就点赞,疼爱就打赏