0x03. 高级内容
这一部分将会介绍Python更加高级的内容。如果你不准备使用Python的复杂功能,可以根据需求选择性阅读或调整阅读顺序,或者是在阅读其他人代码遇到困难时回来继续阅读。这里建议阅读类、文件和异常处理,他们既重要又不难。
6.类
类的定义与使用
# 定义一个类
class ABC:
pass
# 实例化一个类对象
abc = ABC()
与函数相同,类必须先声明才能使用,比如上面的abc = ABC()
不能写在class ABC:
的上边。
每个类与其他代码之间应间隔两个空行。
类中的函数
类中的函数必须使用self
作为第一个参数,但在调用函数时并不需要传参给self(事实上self
能够改为其他名称,但约定俗成把名称定为self
,不建议修改)。
class Animal:
name = ''
# 构造函数
def __init__(self, name, age):
self.name = name
self.age = age
# 构析函数
def __del__(self):
pass
def get_name(self):
return self.name
调用函数:
animal = Animal('Jenny', 2)
name = animal.get_name()
一般情况下我们不需要像C++那样在构析函数内释放内存,因为Python会自动进行垃圾回收,在使用Python时不需要考虑这种问题。
PEP 8:类与类之间要间隔两行,类中的函数与函数之间要间隔一行。
不像C++、Java那样类中的数据成员需要声明,Python的类数据不需要声明,在第一次赋值时就可以产生:
animal.extra_variable = 2
print(animal.extra_variable)
上面这行代码可以看到,类Animal
中本无extra_variable
这个变量,但你也可以创建它并赋值。
与函数中的默认值类似,如果在类中声明可变类型变量,则不同实例会共享该变量并产生累积作用:
class Animal:
foods = []
def add_food(self, food):
self.foods.append(food)
cat = Animal()
dog = Animal()
cat.add_food('Fish')
dog.add_food('Bone')
print(cat.foods)
print(dog.foods)
运行结果
[‘Fish’, ‘Bone’]
[‘Fish’, ‘Bone’]
解决的方法是,不声明foods,在构造函数中创建实例变量:
class Animal:
def __init__(self):
self.foods = []
def add_food(self, food):
self.foods.append(food)
cat = Animal()
dog = Animal()
cat.add_food('Fish')
dog.add_food('Bone')
print(cat.foods)
print(dog.foods)
运行结果
[‘Fish’]
[‘Bone’]
类的继承
class Cat(Animal):
...
当然,如果类在另一个模块里:
import my_module
class Cat(my_module.Animal):
def __init__(self, name, age):
super().__init__(name, age)
(对java程序员的提示:别忘了super后面的括号)
派生类会重写其基类的方法,比如
class Parent:
def f(self):
print('1')
class Child(Parent):
def f(self):
print('2')
c = Child()
c.f()
运行结果
2
(对 C++ 程序员的提示:Python 中所有的方法实际上都是 virtual 方法。)
如果要在Child中运行父类的f()
,可以这样做:
c = Child()
Parent.f(c)
Python的类支持多重继承:
class Parent(Base1, Base2, Base3):
...
如果某个属性(变量、函数等)在Parent中没有找到,那么Python会先在Base1中找,然后再到Base2中找,以此类推,是按照顺序找的。同样的,super()
也是按顺序找。
id()、type()和isinstance()
id()
用来获取数据所在的内存地址,通过id(obj1) == id(obj2)
来判断两个变量是否为同一个对象,相当于obj1 is obj2
。
type()
用来获取数据的类型,如type(2.5) == float
判断是否为浮点型;type(vec) == Vector2D
判断变量vec
是否为Vector2D
的类型。
假设类Dog
继承与类Animal
,dog = Dog()
,我们就不能使用type(dog) == Animal
来判断dog
是否为Animal
类型了。
此时我们可以使用isinstance(dog, Animal)
,类似于Java的instanceof
。
事实上,Python并没有严格意义的多态,不像C++和Java的多态,当一个类形参被声明为某个类型时,只能传入该类型或该类型的子类,而Python的变量可以随时被赋值为任何类型,没有这种限制。
访问权限(下划线命名)
如果在一个模块中的变量或函数用单个下划线开头命名,如def _func()
,那么它应该被视为非公有部分(即我们不应该访问它)。在PyCharm中调用另一个模块用单下划线命名的方法时,则会有警告。但这并不能阻止用户访问它。
如果在一个类中的变量或方法使用两个下划线开头命名,则应该把它视为类私有的变量或方法,子类不应该访问它。Python会自动把这些变量改名,比如__name
会改为_classname__name
,所以我们实际上还是可以通过_classname__name
来访问它:
class Animal:
def __do_not_call_me(self):
...
animal = Animal()
animal._Animal__do_not_call_me()
用空类来储存数据
class Datas:
pass
data = Datas()
data.name = 'Peter'
data.result = 5
迭代器
重写__iter__
和__next__
方法即可实现迭代器。
class IteratorDemo:
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
self.index -= 1
if self.index == -1:
raise StopIteration
return self.data[self.index]
ite = IteratorDemo([1, 'a', 3.14])
for i in ite:
print(i)
运行结果
3.14
a
1
使用next()
函数:
ite = IteratorDemo([1, 'a', 3.14])
print(next(ite))
print(next(ite))
print(next(ite))
运行结果
3.14
a
1
生成器与yield
生成器
仔细看这两行代码的区别:
list1 = [i for i in range(9999999)]
list2 = (i for i in range(9999999))
第一行就是上面学过的列表推导(List Comprehensions),而第二行的园括号…难道是元组推导?其实并不是。看这段代码运行时消耗的时间
import time
start = time.time()
list1 = [i for i in range(99999999)]
end = time.time()
print('list1消耗: '+str(end - start)+'s')
start = time.time()
list2 = (i for i in range(99999999))
end = time.time()
print('list2消耗: '+str(end - start)+'s')
运行结果
list1消耗: 5.164059162139893s
list2消耗: 0.0s
可以看到list1需要消耗长达5s的时间,而list2由于太快几乎瞬间就完成了。
这是由于,list1是真的生成了一个长度99999999的列表,它需要占用非常大的内存空间;而list2是一个生成器,它记录的是一个算法,用来生成这些数字,实际上这些数字还没有被生成,所以它占用的内存空间很小。
为了获取list2的内容,我们需要使用next()
:
>>> list2 = (i for i in range(99999999))
>>> next(list2)
0
>>> next(list2)
1
>>> next(list2)
2
它同时也是迭代器,可以使用for循环获取内容:
list2 = (i for i in range(10))
for i in list2:
print(i)
这就是生成器,在生成有一定规律的大量数据时,使用它可以节省时间和内存空间。
yield
在函数中,return
的作用是返回一定的数据并结束函数的运行。而在函数中使用yield
关键词可以让一个函数变成生成器:
def func():
print('a')
yield
print('b')
yield
print('c')
yield
print('d')
yield
f = func()
next(f)
next(f)
next(f)
next(f)
next(f)
运行结果
a
b
c
d
Traceback (most recent call last):
File "\<stdin>", line 1, in \<module>
StopIteration
我们可以给yield添加返回值:
def func():
yield 1
yield 'a'
yield 3.14
for i in func():
print(i)
运行结果
1
a
3.14
yield一般搭配for循环一起使用,比如按顺序生成自然数的平方:
def gen(n):
for i in range(n + 1):
yield i ** 2
for i in gen(5):
print(i)
运行结果:
0
1
4
16
25
__str__和__repr__
当我们创建一个类并创建它的实例时,使用str()
会发现输出的是它所在的内存地址:
>>> class A:
... pass
...
>>> a = A()
>>> str(a)
<__main__.A object at 0x0000028D440D36D0>
为了修改输出的内容,我们可以重写__str__()
:
class A:
def __str__(self):
return 'I am A'
a = A()
print(str(a))
运行结果:
I am A
当如果你把A放容器里再使用str(容器)
时,会发现它又变成了地址:
print(str([a]))
运行结果
[<__main__.A object at 0x0000028D440D36D0>]
此时就需要重写__repr__()
,且我们可以通过repr()
来获取它的内容。
值得注意的是,如果只重写了__repr__()
,那么str()
和repr()
获取的都是__repr__()
返回的结果。
一般情况下,__str__()
返回的是给用户看的,而__repr__()
返回的是用于调试的内容,所以__repr__()
需要返回尽量详细的内容,比如:
class Student:
def __init__(self, name, gender, age):
self.name = name
self.gender = gender
self.age = age
def __str__(self):
return f'一个叫{self.name}的学生'
def __repr__(self):
return f'一个性别{self.gender},年龄{self.age}岁,名为{self.name}的学生,该变量在内存中的地址(16进制):{hex(id(self))}'
stu = Student('小明', '男', 18)
print(f'我们有{stu}')
print(f'debug: {repr(stu)}')
运行结果
我们有一个叫小明的学生
debug: 一个性别男,年龄18岁,名为小明的学生,该变量在内存中的地址(16进制):0x28d44712550
运算符重载
Python的运算符也能重载,不过不是所有运算符都能重载,比如is
、or
、and
、not
。(请注意区分逻辑运算or
、and
和逻辑位运算|
、&
)
比如编写一个向量类,并重写加号(+)和等号(==):
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector2D(self.x + other.x, self.y + other.y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __repr__(self):
return f'({self.x}, {self.y})'
vec1 = Vector2D(1, 2)
vec2 = Vector2D(1, 2)
print(vec1 + vec2)
print(vec1 == vec2)
运行结果
(2, 4)
True
我们同样可以通过重写__mul__
方法来实现vec * 2
,但是,如果我们想要2 * vec
呢?此时可以重写__rmul__
:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __rmul__(self, other):
return Vector2D(self.x * other, self.y * other)
vec = Vector2D(1, 2)
print(2 * vec)
这里可以看到,__mul__
和__rmul__
的功能是一样的,把他们都单独实现一遍会造成代码重复,此时我们可以:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __mul__(self, other):
return Vector2D(self.x * other, self.y * other)
__rmul__ = __mul__ # <---
当然,如果在a * b
,a有实现__add__
方法,则优先运行a
的__add__
方法,即便b
实现了__radd__
。
此外,对于vec *= 2
,可以通过重写__imul__
实现,但它也可以被理解为vec = vec * 2
。
对于vec *= 2
,如果__imul__
没有被重写,它就会尝试去运行__mul__
,如果__mul__
也没有被重写,就会报错。
下面通过运算符重载来模仿c++的cout效果(仅模仿效果,不模仿原理):
# cpp_cout.py
Class CppCout:
def __lshift__(self, other):
print(other, end='')
return self
cout = CppCout()
endl = '\n'
# cpp_cout_test.py
from cpp_cout import cout, endl
if __name__ == '__main__':
cout << 'Hello' << 'World' << endl
运行结果
HelloWorld
这些以两个下划线起始和两个下划线结尾的方法被称为魔法方法,魔法方法非常多,你可以点击这里查看重要的魔法方法。
7.装饰器
装饰器(Decorator)是Python的一种语法糖,它和Java的注解长的有点像,可以让代码变得更加简洁优雅。
比如这里有一个函数:
def func():
lst = [i for i in range(99999999)]
我们想给这个函数添加一个统计运行时间的功能,直接修改这个函数:
import time
def func():
start = time.time()
lst = [i for i in range(99999999)]
end = time.time()
print('该函数运行用时:'+(end - start)+'秒')
但这样修改了函数的代码,让这个函数的代码变得更复杂,也不便于去除该功能,与是我们这样做:
import time
def func():
lst = [i for i in range(99999999)]
def cal_time(f):
start = time.time()
f()
end = time.time()
print('该函数运行用时:'+(end - start)+'秒')
但这样需要把原先运行func()
改为cal_time(func)
,改变了运行的逻辑。
这时,装饰器便起了作用:
def cal_time(f):
def wrapper():
start = time.time()
f()
end = time.time()
print('该函数运行用时:'+(end - start)+'秒')
return wrapper
@cal_time
def func():
lst = [i for i in range(99999999)]
这样,当我们运行func()
时,实际上是运行了:
w = cal_time(func)
w()
但我们会发现,如果函数内有参数,包括各种各样的位置参数和关键词参数时,上面的方法就失效了,因此,我们要这样写装饰器:
def cal_time(f):
def wrapper(*args, **kwargs):
start = time.time()
f(*args, **kwargs)
end = time.time()
print('该函数运行用时:'+(end - start)+'秒')
return wrapper
@cal_time
def func(n):
lst = [i for i in range(n)]
这样,我们便可以把函数的参数一并交给装饰器了。
当我们不需要这个功能时,只需把@cal_time
去掉即可。
既然我们可以拿到参数,也可以在装饰器内给参数做校验,比如:
def not_none(f):
def wrapper(*args):
for index, ele in enumerate(args, 1):
if ele is None:
raise ValueError(f'第{index}个参数为None!该函数接受的参数不能为None。')
f(*args)
return wrapper
@not_none
def func(n1, n2, /):
...
如果我们要给函数添加多个功能,那就多加几个装饰器:
@cal_time
@not_none
@more_decorators
def func(n1, n2):
...
可见,当我们想给一个函数添加一个新功能,比如统计该函数运行的时间,但我们不能修改它,这时使用装饰器就可以在不修改函数代码的情况下给它添加功能,也可以轻松的去掉新功能。
给装饰器添加参数
装饰器也能有参数。
在能让用户输入的地方,都要做各种检测来避免用户输入非法的数据。输入长度检测是十分常见的检测,比如,用户名长度不能超过16,密码长度不能超过32,发表的评论长度不能超过500等,不同地方对输入长度的限制不同,我们总不能写很多个装饰器给不同地方的检测使用吧,因此就需要一个参数来告诉装饰器这个长度是多少。
def check_length(length_limit):
def check(f):
def wrapper(info):
length = len(info)
if length > length_limit:
raise ValueError(f'输入的参数长度({length})超过限制{length_limit}')
f(info)
return wrapper
return check
@check_length(64)
def send_message(info):
...
Python内置装饰器
@property
当我们在类中定义私有变量时,比如_data
,我们不希望用户修改它(虽然事实上可以修改),但可以读取它,我们可以给他加一个get_data()
方法,也可以这样做:
class ABC:
def __init__(self):
self._data = 123
@property
def data(self):
return self._data
这样,用户就可以这样读取它:
abc = ABC()
abc.data
可以发现,abc.data
后面无需加括号,而且无法修改它,即无法abc.data = 0
。
@xxx.setter
如果我们还想让用户修改私有变量的值,但需要给输入的值做一定的限制,这时除了添加一个set_data()
方法,还可以:
class ABC:
def __init__(self):
self._data = 123
@data.setter
def data(self, value):
if data < 0:
data = 0
self._data = value
此时,我们就可以通过abc.data = 1
这样的方式来修改data的值了,而且,如果赋的值小于0,实际赋的值是0。
@classmethod
Python不支持构造函数重载,也就是说类里面只能有一个构造函数。我们虽然可以使用默认参数__init__(self, a, b='xxx', c='xxx')
来解决多个参数问题,但遇到不同类型参数就比较麻烦了。这时,@classmethod便派上了用场。
假设一个向量,既可以通过设置x和y的值来构造,也可以通过长度为2的列表来构造,我们就可以在类里面添加一个“工厂方法”:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
@classmethod
def gen(cls, lst: list):
if len(lst) != 2:
raise Exception('列表长度不是2!')
return cls(lst[0], lst[1])
这样,我们就能使用vec = Vector2D.gen([1, 2])
来创建实例了。
这里的cls
跟self
一样是约定俗成的,一般不要使用其他命名。
@staticmethod
顾名思义,这是静态方法:
class ABC:
@staticmethod
def func():
...
于是我们就可以通过类名直接访问它:ABC.func()
(也可以在实例中访问它)。
8.文件处理
基本的文件读写
Python将读写文件的过程进行了高度封装,于是你可以非常轻松地读取文件:
with open("file.txt") as file_obj:
contents = file_obj.read()
只需两行代码即可读取文件中的所有内容。
不过这样读取的内容会在最后面多一个换行符号,为了清除掉这个换行符号,你可以:
contents = contents.rstrip()
你也可以一行一行读取:
with open("file.txt") as file_obj:
for line in file_obj:
print(line.rstrip())
将文件内容的每一行读取为列表:
with open("file.txt") as file_obj:
lines = file_obj.readlines()
print(lines)
默认情况下,打开文件是以只读模式打开,无法写入。如果要写入,则需指定为写入模式:
with open("file_write.txt", "w") as file_obj:
file_obj.write("a")
file_obj.write(str(1))
关于模式的选择:
r
为只读模式w
为写入模式r+
既可读又可写a
追加模式
你还可以指定打开文件的编码格式:
# 以UTF-8编码打开文件
with open("file.txt", encoding="utf-8") as file_obj:
print(file_obj.read())
json
Python处理json文件非常的方便。
首先需要导入json模块:
import json
读取json文件:
with open("json.txt") as f:
info = json.load(f)
print(info)
一般json最外面都是用一个大括号括起来或一个方括号括起来的,比如大括号括起来:
{
"key": "value",
"list": [1, "a", {"key": "value"}],
"dict": {
"key": "value"
}
}
是不是很像Python中字典、列表等数据类型的嵌套呢?在这个例子中,得到的info就是一个字典。
读取info,只要像正常的字典那样去读取就行了:
with open("my_data") as f:
info = json.load(f)
value = info['key']
lst = info['list']
inner_dict = info['dict']
inner_value = inner_dict['key']
如果最外面是用方括号括起来,则info是一个列表,同样支持嵌套。
将数据保存为json也很简单,你只需把数据都保存到字典或列表中,然后再调用json.dump
即可:
info = {
"num": 1,
"list": [1, 2, 3],
"dic": {
"num": 2
}
}
with open("my_data.json", "w") as f:
json.dump(info, f)
with ... as 的用法
在上面文件操作的代码中,可以发现仅仅需要一个with open()
即可,无需像其他语言那样做各种异常处理和关闭文件流等操作。其实这些已经在with
对应的操作中帮你处理好了。
你也可以自己写一个with
的处理方法:
class myWith:
def __enter__(self):
print('enter')
return self
def __exit__(self, type, value, trace):
print('exit')
def func(self):
print('do something')
def test():
return myWith()
with test() as m:
m.func()
运行结果:
enter
do something
exit
可以看到,用with
拿到myWith后,会先运行__enter__()
,且__enter__()
的返回值会传给as
后的变量,即m
。在with范围内的代码,即m.func()
运行完毕后,调用__exit__()
方法。于是,我们可以在__exit__
中实现资源的关闭,比如文件流的关闭。
另外,可以看到__exit__()
方法还有很多参数,其实__exit__()
还可以对异常做处理:
class myWith:
def __enter__(self):
return self
def __exit__(self, type, value, trace):
print(type)
print(value)
print(trace)
def func(self):
return 1 / 0
def test():
return myWith()
with test() as m:
m.func()
可以看到,m.func()
必然会产生错误,这个时候也会运行__exit__()
方法,并拿到错误信息:
<class 'ZeroDivisionError'>
value division by zero
<traceback object at 0x280b7ee6930>
9.异常处理
捕获异常
try:
a = 1 / 0
except ZeroDivisionError:
print("除零错误")
也可以将捕获的异常实例赋给变量:
try:
a = a / 0
except ZeroDivisionError as err:
print(err)
运行结果:
division by zero
同时捕获多个异常:
except (ZeroDivisionError, TypeError):
pass
捕获多个异常并对不同异常作不同处理:
except ZeroDivisionError:
...
except TypeError:
...
如果异常有继承关系,不应该先捕获父级再捕获子级,否则子级将永远无法被捕获。
捕获任意异常:
try:
...
except:
...
不建议这样做,否则异常将会难以追踪,甚至会出出现Ctrl C
都无法结束程序的情况。
try-except-else
结构,可以在try没有捕获到异常时执行else中的语句:
try:
f = open('filename.txt')
except OSError:
print('cannot open file')
else:
print('open file successfully')
f.close()
finally
无论异常是否发生,都会执行:
def readAndClose(connection):
try:
return connection.read()
except:
print('出现异常')
finally:
connection.close()
不建议同时在finally中写return,否则会出现返回值被覆盖的情况:
def func():
try:
return 1
finally:
return 2
得到的返回值:2
你可以在这里查找内置异常。
抛出异常
raise NameError
也可给异常传递参数,比如给异常加上说明:
raise NameError('错误代码:0x0101,你可在help.xxx.com查找帮助。')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: 错误代码:0x0101,你可在help.xxx.com查找帮助。
传递多个参数,并在异常捕获中获得这些参数:
try:
raise NameError('abc', 'def')
except NameError as err:
print(err.args)
运行结果:
('abc', 'def)
如果你需要捕获异常后,继续将异常往外传递,只需在except中加上raise:
try:
raise NameError
except NameError as err:
print('异常产生!')
raise
运行结果:
异常产生!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
NameError
当你想要表达一个异常由另一个异常引起时,可以使用from:
try:
connect()
except ConnectionError as err:
raise RuntimeError('连接失败') from err
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in func
ConnectionErrorThe above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError: 连接失败
自定义一个自己的异常:
class MyError(Exception):
pass
raise MyError
10. 常用标准库和内置函数
Python包含了许多有用的内置方法和库,这里仅列出部分常用的,详细内容可看这里。
前面已经出现过的部分库和内置函数的使用,这里不再赘述。
字符串处理
字符串本身就有许多有用的方法,比如s.startswith()
、s.endswith()
用于判断字符串是否以某个字符串开头或结尾,也可以是元组:
url = 'http://blog.lyzen.cn'
prefix = ('http', 'https')
b = url.startswith(prefix)
通过s.find()
来获取某个字符串第一次出现的下标,s.rfind()
则是获取某个字符串最后一次出现的下标,如果找不到,则返回-1。
正则表达式,需要导入re
模块:
import re
if re.match(r'\d+.\d+', '3.14'):
print('matched')
你也可以把正则表达式预编译,以便多次使用:
re_exp = re.compile(r'\d+.\d+')
b1 = re_exp.match('3.14')
b2 = re_exp.match('2.71')
还可以用re.findall()
寻找字符串中符合要求的所有字符串:
>>> s = '3.14abc2.71'
>>> re.findall(r'\d+.\d+', s)
['3.14', '2.71']
对字符串进行替换,可以用.replace()
:
>>> ori = '123456789'
>>> ori.replace('123', 'abc')
abc456789
也可用re.sub()
进行正则表达式替换:
>>> ori = 'abc3.14def'
>>> re.sub(r'\d+.\d+', '???', ori)
abc???def
以上操作可以以忽略大小写的形式进行,需要使用re模块并加上re.IGNORECASE
标记:
>>> s = 'AbCdEf'
>>> re.sub('abc', '123', s, flags=re.IGNORECASE)
123dEf
将字符串转为全大写或大小写,使用s.upper()
或s.lower()
即可。
我们可以使用str.join()
来将一定的字符串序列以某个符号连接起来:
>>> parts = ['123', 'abc', '456']
>>> '-'.join(parts)
123-abc-456
对于字符串中变量的替换,可以这样做:
>>> ori = 'Hello {name}! Welcome to {where}.'
>>> ori.format(name='Peter', where='Guangzhou')
Hello Peter! Welcome to Guangzhou.
数字
由于浮点数的特性,进行浮点数计算时,会出现误差:
>>> 0.1 + 0.2
0.30000000000000004
如果对小数的精度有要求,则可以使用Decimal(当然,这是以损失性能为代价的):
>>> from decimal import Decimal
>>> a = Decimal('0.1')
>>> b = Decimal('0.2')
>>> a + b
0.3
>>> a + b == Decimal('0.3')
True
也可以控制小数点的精度:
>>> from decimal import Decimal, localcontext
>>> a = Decimal('22')
>>> b = Decimal('7')
>>> with localcontext() as c:
... c.prec = 50
... print(a / b)
...
3.1428571428571428571428571428571428571428571428571
当我们需要将一个类似于'123'
的字符串转化为整数时,只需:
>>> a = int('123')
>>> a + 1
124
有时候我们需要将数字储存在字节串中,比如一个IP、一个UUID等。
对于IPv4,一段IP实际上只需4个字节即可储存,比如'192.128.1.102'
可用(1)0xC0800166
或(2)b'\xC0\x80\x01\x66'
表示,将(2)转化为(1):
>>> ip = b'\xC0\x80\x01\x66'
>>> hex(int.from_bytes(ip, 'big'))
'0xc0800166'
>>> hex(int.from_bytes(ip, 'little'))
'0x660180c0'
将(1)转化为(2):
>>> a = 0xC0800166
>>> a.to_bytes(4, 'big') # 4指4字节
b'\xc0\x80\x01f'
分数计算:
>>> from fractions import Fraction
>>> a = Fraction(3, 4)
>>> a ** 2
Fraction(9, 16)
>>> float(a ** 2)
0.5625
随机数
生成一个0~10的随机整数(包括0和10):
import random
a = random.randint(0, 10)
生成一个0~1之间的浮点数:
import random
a = random.random()
生成5~10之间的均匀浮点数:
import random
a = random.uniform(5, 10)
生成均值为50,标准差为10的高斯分布的随机浮点数:
import random
a = random.gauss(5, 10)
设置随机数种子:
import random
random.seed(123)
生成一个随机数生成器并设置种子:
from random import Random
r = Random()
r.seed(123)
r.randint(1, 10)
r.random()
从列表中随机选一个元素:
import random
lst = [1, 2, 3, 4, 5]
a = random.choice(lst)
从列表中随机选多个元素:
>>> import random
>>> lst = [1, 2, 3, 4, 5]
>>> random.sample(lst)
[4, 3]
打乱列表中元素的顺序:
>>> import random
>>> lst = [1, 2, 3, 4, 5]
>>> random.shuffle(lst)
>>> lst
[3, 2, 5, 1, 4]
日期和时间
表示特定时间,并做运算:
>>> from datetime import datetime, timedelta
>>> print(datetime(2022, 8, 26))
2022-08-26 00:00:00
>>> print(datetime(2022, 8, 26, 1, 26, 59))
2022-08-26 01:26:59
>>> print(datetime.now())
2022-08-26 01:27:53.833984
>>> t = datetime(2022, 8, 26, 0, 0, 0)
>>> t += timedelta(days=1, hours=2, minutes=3)
2022-08-27 02:03:00
字符串与时间转换:
>>> from datetime import datetime
>>> text = '2022-08-26'
>>> t = datetime.strptime(text, '%Y-%m-%d')
>>> print(t)
2022-08-26 00:00:00
>>> from datetime import datetime
>>> t = datetime.now()
>>> text = datetime.strftime(t, '%a %b %d %Y %H:%M:%S')
>>> text
'Fri Aug 26 2022 01:32:17'
常用格式符:
%a 星期英文缩写,如星期五:Fri
%A 星期英文,如星期五:Friday
%b 月份英文缩写,如八月:Aug
%B 月份英文,如八月:August
%Y 年
%m 月份
%d 当前月的第几天
%j 当天是当年的第几天
%H 小时(24小时制)
%I 小时(12小时制)
%P 上午下午(AM or PM)
%M 分钟
%S 秒
%f 微秒,范围:[0, 999999]
等等
像时区、夏令时、闰秒等不在本文讨论范围内,感兴趣的可以上网搜索。
网络编程
对于简单的GET和POST请求,Python内置的urllib库就可以做到了:
from urllib import request, parse
url = 'http://api.test.com'
headers = {
'User-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36',
'cookie': 'cookie'
}
params = {
'name1': 'value1',
'name2': 'value2'
}
req = request.Request(url+'?' + parse.urlencode(params))
u = reqest.urlopen(req)
res = u.read()
但如果需要比较复杂的操作,则建议使用第三方库requests,这是一个非常常用的且好用的库。
- 1 2