Python光速入门教程(适合已经会其他编程语言的人)

1,677次阅读
没有评论

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继承与类Animaldog = 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的运算符也能重载,不过不是所有运算符都能重载,比如isorandnot。(请注意区分逻辑运算orand和逻辑位运算|&)

比如编写一个向量类,并重写加号(+)和等号(==):

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])来创建实例了。

这里的clsself一样是约定俗成的,一般不要使用其他命名。

@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 
ConnectionError 

The 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
Lyzen
版权声明:本站原创文章,由 Lyzen 2022-07-06发表,共计26261字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)
验证码