【测开面经】Python篇

Python的优点

  • 简单易学
    阅读一个良好的Python程序就像读英语段落一样,Python最大的优点之一是具有伪代码的本质,它使我们在开发 Python程序时,专注的是解决问题,而不是搞明白语言本身。
  • 面向对象
    Python既支持面向过程编程,也支持面向对象编程。在“面向过程”的语言中,程序是由过程或仅仅是可重用代码的函数构建起来的。在“面向对象”的语言中,程序是由数据和功能组合而成的对象构建起来的。
  • 可移植性
    由于 Python的开源本质,它已经被移植在许多平台上。
  • 解释性
    在计算机内部,Python解释器把源代码转换成称为字节码的中间形式,然后再把它翻译成计算机使用的机器语言并运行。
  • 开源
  • 高级语言
    Python是高级语言。当使用 Python语言编写程序时,无需再考虑诸如如何管理程序使用的内存一类的底层细节。Python的垃圾回收机制
  • 可扩展性
    如果需要一段关键代码运行的更快或者希望某些算法不公开,就可以把部分程序用C或C语言编写,然后在 Python程序中使用它们。
  • 丰富的库
    Python标准库确实很庞大,它可以帮助你处理各种工作
  • 代码规范
    Python采用强制缩进的方式使得代码具有极佳的可读性。

Python基本数据类型

Python 3中有六个标准的数据类型:Numbers(数字,包括int、float、bool、complex等)、String(字符串)、List(列表)、Tuple(元组)、Sets(集合)、Dictionaries(字典)

数组和列表的区别

数组array定义为存储在连续内存位置的元素的集合,这些元素应为同一类型;
而list中的元素可以是任意类型,其他的操作类似

列表、元组、集合与字典的区别

列表、元组、集合与字典的区别

可变类型与不可变类型以及is与==的比较

可变类型与不可变类型以及is与==的比较

参数

  • 默认参数
    当进行函数重写时,如func(x)和func(x,y),此时调用旧函数时会发生报错,原因是新函数增加了一个参数,导致旧的代码因为缺少一个参数而无法正常调用。这个时候,默认参数就排上用场了,可以把第二个参数y设置一个默认值,设置默认参数时,必选参数在前,默认参数在后,并且默认参数必须指向不变对象;
  • 可变参数
    定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*号,调用该函数时,可以传入任意个参数,包括0个参数;
  • 关键字参数
    可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
  • 命名关键字参数
    命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数
def person(name, age, *, city, job):
    print(name, age, city, job)

如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了

def person(name, age, *args, city, job):
    print(name, age, args, city, job)

命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错,如果命名关键字参数具有默认值,调用时,可不传入参数。

对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的

格式化字符串

  • %
占位符替换内容
%d整数
%f浮点数
%s字符串
%x十六进制整数
print('%2d-%02d' % (3, 1))
print('%.2f' % 3.1415926)
print('Age: %s. Gender: %s' % (25, True))
print('growth rate: %d %%' % 7)
结果:
3-01
3.14 
'Age: 25. Gender: True'
'growth rate: 7 %'
  • format
    传入的参数依次替换字符串内的占位符{0}、{1}……
>>>'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.1%'
  • f-string
    字符串如果包含{xxx},就会以对应的变量替换
>>> r = 2.5
>>> s = 3.14 * r ** 2
>>> print(f'The area of a circle with radius {r} is {s:.2f}')
The area of a circle with radius 2.5 is 19.62

为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性.__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的,除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__。

生成器

说在前面:
列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。如[x * x for x in range(1, 11) if x % 2 == 0],在一个列表生成式中,for前面的if … else是表达式,而for后面的if是过滤条件,不能带else。

在Python中,使用了yield的函数被称为生成器(genetator)。和普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单理解生成器就是一个迭代器。在调用生成器运行的过程中,每次遇到yield时函数会暂停并保存当前所有的运行信息,返回yield的值,并在下次执行next方法时从当前位置继续进行。调用一个生成器函数,返回的是一个迭代器对象。

import sys
 
def fibonacci(n): # 生成器函数 - 斐波那契
    a, b, counter = 0, 1, 0
    while True:
        if (counter > n): 
            return
        yield a
        a, b = b, a + b
        counter += 1
f = fibonacci(10) # f 是一个迭代器,由生成器返回生成
# 让这个函数 fab 每次只返回一个迭代器——一个计算结果,而不是一个完整的 list,来节省内存
while True:
    try:
        print (next(f), end=" ")
    except StopIteration:
        sys.exit()

生成器的应用场景

生成器在没有牺牲很多的速度情况下,释放了内存,再一定的业务场景下,支持大数据的操作。
应用举例

生成器与迭代器的区别

生成器是生成元素的,它记住了上次返回时在函数体中的位置;而迭代器时访问集合元素的一种方式,是一种支持next操作的对象,输出生成器的内容。

凡是可作用于for循环的对象都是Iterable类型;
凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

装饰器

装饰器(Decorators)简单来说就是修改其他函数功能的函数

贴士
1)把一对小括号放在函数后面,这个函数就会执行;然而如果你不放括号在它后面,那它可以被到处传递,并且可以赋值给别的变量而不去执行它。
2)Python装饰器(decorator)在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变),为了不影响,Python的functools包中提供了一个叫wraps的decorator来消除这样的副作用。写一个decorator的时候,最好在实现之前加上functools的wrap,它能保留原有函数的名称和函数属性
3)Python内置的@property装饰器就是负责把一个方法变成属性调用的。@property把一个getter方法变成属性,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作

  • 代码示例

from functools import wraps
def decorator_name(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        if not can_run:
            return "Function will not run"
        return f(*args, **kwargs)
    return decorated
 
@decorator_name
def func():
    return("Function is running")
 
can_run = True
print(func())
# Output: Function is running
 
can_run = False
print(func())
# Output: Function will not run
  • 使用场景
    授权

from functools import wraps
 
def requires_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if not auth or not check_auth(auth.username, auth.password):
            authenticate()
        return f(*args, **kwargs)
    return decorated

日志


from functools import wraps
 
def logit(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging
 
@logit
def addition_func(x):
   """Do some math."""
   return x + x
 
 
result = addition_func(4)
# Output: addition_func was called
  • 带参数的装饰器

from functools import wraps
 
def logit(logfile='out.log'):
    def logging_decorator(func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called"
            print(log_string)
            # 打开logfile,并写入内容
            with open(logfile, 'a') as opened_file:
                # 现在将日志打到指定的logfile
                opened_file.write(log_string + '\n')
            return func(*args, **kwargs)
        return wrapped_function
    return logging_decorator
 
@logit()
def myfunc1():
    pass
myfunc1()
# Output: myfunc1 was called
# 现在一个叫做 out.log 的文件出现了,里面的内容就是上面的字符串
@logit(logfile='func2.log')
def myfunc2():
    pass
myfunc2()
# Output: myfunc2 was called
# 现在一个叫做 func2.log 的文件出现了,里面的内容就是上面的字符串
  • 装饰类
      装饰类的使用方法和装饰函数的用法一样。

from functools import wraps
 
class logit(object):
    def __init__(self, logfile='out.log'):
        self.logfile = logfile
 
    def __call__(self, func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called"
            print(log_string)
            # 打开logfile并写入
            with open(self.logfile, 'a') as opened_file:
                # 现在将日志打到指定的文件
                opened_file.write(log_string + '\n')
            # 现在,发送一个通知
            self.notify()
            return func(*args, **kwargs)
        return wrapped_function
 
    def notify(self):
        # logit只打日志,不做别的
        pass

闭包

在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。
  一个简单示例:

#闭包函数,其中 exponent 称为自由变量
def nth_power(exponent):
    def exponent_of(base):
        return base ** exponent
    return exponent_of # 返回值是 exponent_of 函数
square = nth_power(2) # 计算一个数的平方
cube = nth_power(3) # 计算一个数的立方
print(square(2))  # 计算 2 的平方
print(cube(2)) # 计算 2 的立方
  • 闭包中内函数修改外函数局部变量
    在基本的python语法当中,一个函数可以随意读取全局数据,但是要修改全局数据的时候有两种方法:1 global 声明全局变量 2 全局变量是可变类型数据的时候可以修改
    在闭包内函数也是类似的情况。在内函数中想修改闭包变量(外函数绑定给内函数的局部变量)的时候:
      1) 在python3中,可以用nonlocal 关键字声明 一个变量, 表示这个变量不是局部变量空间的变量,需要向上一层变量空间找这个变量。
      2) 在python2中,没有nonlocal这个关键字,我们可以把闭包变量改成可变类型数据进行修改,比如列表。

匿名函数

lambda的主体是一个表达式,而不是一个代码块,其拥有自己的命名空间,不能访问自有参数列表之外或全局命名空间里的参数。虽然lambda函数看起来只有一行,却不等同于C或C++的内联函数,后者的目的是调用函数时不占用栈内存从而增加运行效率。匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数。

>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]

深拷贝和浅拷贝

  • 直接赋值
    其实是对象的引用(别名)
  • 浅拷贝(copy)
    拷贝父对象,不会拷贝对象内部的子对象
    b = a.copy(),a和b是独立的对象,但他们的子对象还是指向同一对象(是引用)
>>>a = {1: [1,2,3]}
>>> b = a.copy()
>>> a, b
({1: [1, 2, 3]}, {1: [1, 2, 3]})
>>> a[1].append(4)
>>> a, b
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})

在这里插入图片描述

  • 深拷贝(deepcopy)
    copy模块的deepcopy方法,完全拷贝了父对象及其子对象
>>>import copy
>>> c = copy.deepcopy(a)
>>> a, c
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})
>>> a[1].append(5)
>>> a, c
({1: [1, 2, 3, 4, 5]}, {1: [1, 2, 3, 4]})

在这里插入图片描述

Python框架

  • 使用框架的好处
    大大提升开发效率
    让应用开发更加规范,拓展性更强
    让程序员把更多的精力放在业务逻辑的实现上,而不是重复、复杂的基础环境上(Web服务器、底层实现等)

Django与Flask对比

  • 基本介绍
    Flask是一个由Python写成的轻量级Web框架,显著特点是它是一个“微”框架,轻便灵活,但同时易于扩展。默认情况下,Flask只相当于一个内核,不包含数据库抽象层(ORM)、用户认证、表单验证、发送邮件等其它Web框架经常包含的功能。Flask依赖于各种灵活的扩展(比如邮件Flask-Mail,用户认证Flask-Login,数据库Flask-SQLAlcehmy)来给Web应用添加额外功能。Flask的这种按需扩展的灵活性是很多程序员喜欢它的地方,Flask没有指定的数据库,可以用MySQL,也可以用NoSQL。
    Django是一个开源的Python Web应用框架,采用了MVT的框架模式,即模型M,视图V和模板T。Django被认为是“大而全”的重量级Web框架,其自带大量的常用工具和组件(比如数据库ORM组件、用户认证、权限管理、分页、缓存),甚至自带了管理后台Admin,适合快速开发功能完善的企业级网站。Django自带免费的数据SQLite,同时支持MySQL和PostgreSQL等多种数据库。
  • 选择
    Flask自由灵活,可扩展性强,入门简单,非常适用于小型网站和开发Web服务的API;
    Django自由度不高,能开发小应用,但总会有“杀鸡焉用宰牛刀”的感觉,非常适用于开发企业级网站,自带的模板引擎简单好用,开发文档详细。

GIL

GIL(Global Interpreter Lock)不是Python独有的特性,它只是在实现CPython时,引入的一个概念。GIL是一个互斥锁,它阻止了多个线程同时执行Python字节码,降低了执行效率,但提高了线程的安全性。

  • GIL机制
    在这里插入图片描述
    由于GIL的机制,单核CPU在同一时刻只有一个线程在运行。当线程遇到I/O操作或Time Tick(线程最长执行时间)到期,释放GIL锁,其他的两个线程去竞争这把锁,得到锁之后,才开始执行。虽然都是释放GIL锁,但这两种情况不一样。比如,Thread1会遇到IO操作释放GIL,由Thread2和Thread3来竞争这个GIL锁,Thread不再参与这次竞争。如果Thread1因为Time Tick到期释放GIL,那么三个线程可以同时竞争这把GIL锁,可能出现Thread1在竞争中胜出,再次执行的情况。在单核CPU中,这种情况不算特别糟糕,CPU的利用率是很高的。在多核CPU下,由于GIL锁的全局特性,无法发挥多核的特性,GIL锁会使得多线程任务的效率大大降低。
    在这里插入图片描述
    Thread1在CPU1上运行,Thread2在CPU2上运行。GIL是全局的,CPU2上的Thread2需要等待CPU1上的Thread1让出GIL锁,才有可能执行。如果在多次竞争中,Thread1都胜出,Thread2没有得到GIL锁,意味着CPU2一直闲置,无法发挥多核的优势。为了避免同一线程霸占CPU,在Python3中,线程会自动调整自己的优先级,使得多线程任务执行效率更高。

多线程与多进程

同步与异步

同步是阻塞模式,
异步是非阻塞模式。
同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返 回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;
异步是指进程不需要一直等下去, 而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效 率。

线程生命周期

在这里插入图片描述

多线程

  • 创建多线程的方式
  1. 使用threading模块的Thread类的构造器创建线程
    __init__(self,group=None,target=None,name=None,args=(),kwargs=None,*,daemon=None)
    构造器涉及如下几个参数:
      group:指定该线程所属的线程组;
      target:指定该线程要调度的目标方法;
      args:指定一个元组,以位置参数的形式为target指定的函数传入参数;
      kwargs:指定一个字典;
      daemon:指定所构建的线程是否为后代线程。
    threading.Thread()一般接收2个参数:
      target=线程函数名
      args=线程函数参数
  2. 继承 threading 模块的 Thread 类创建线程类。首先,自定义一个类,对这个自定义的类有两个要求:必须继承threading.Thread这个父类;必须重写run()方法。这个run()方法相当于第一种方法中的线程函数,可以写自己需要的业务逻辑代码,在start()后将会调用。

当一个进程启动之后,会默认产生一个主线程,因为线程是程序执行流的最小单元,当设置多线程时,主线程会创建多个子线程,在python中,默认情况下(其实就是setDaemon(False)),主线程执行完自己的任务以后,就退出了,此时子线程会继续执行自己的任务,直到自己的任务结束。join所完成的工作就是线程同步,即主线程任务结束之后,进入阻塞状态,一直等待其他的子线程执行结束之后,主线程再终止。join有一个timeout参数:当设置守护线程(setDaemon(True))时,含义是主线程对于子线程等待timeout的时间将会杀死该子线程,最后退出程序。所以说,如果有10个子线程,全部的等待时间就是每个timeout的累加和。简单的来说,就是给每个子线程一个timeout的时间,让他去执行,时间一到,不管任务有没有完成,直接杀死。没有设置守护线程(setDaemon(False))时,主线程将会等待timeout的累加和这样的一段时间,时间一到,主线程结束,但是并没有杀死子线程,子线程依然可以继续执行,直到子线程全部结束,程序退出。

  1. 多线程
  2. ThreadLocal解决参数在一个线程中各个函数之间互相传递的问题

线程池

线程池的基类是concurrent。futures模块中的Executor,Executor提供了两个子类,即ThreadPoolExecutor和ProcessPoolExecutor,分别用于创建线程池和进程池。如果使用线程池/进程池来管理并发编程,那么只要相应的task函数提交给线程池/进程池,剩下的事情就由线程池/进程池来搞定。
Exectuor 提供了如下常用方法:
  submit(fn, *args, **kwargs):将 fn 函数提交给线程池。*args 代表传给 fn 函数的参数,*kwargs 代表以关键字参数的形式为 fn 函数传入参数。
  map(func, *iterables, timeout=None, chunksize=1):该函数类似于全局函数 map(func, *iterables),只是该函数将会启动多个线程,以异步方式立即对 iterables 执行 map 处理。
  shutdown(wait=True):关闭线程池。
程序将 task 函数提交(submit)给线程池后,submit 方法会返回一个 Future 对象,Future 类主要用于获取线程任务函数的返回值。由于线程任务会在新线程中以异步方式执行,因此,线程执行的函数相当于一个“将来完成”的任务,所以 Python 使用 Future 来代表。
Future 提供了如下方法:
  cancel():取消该 Future 代表的线程任务。如果该任务正在执行,不可取消,则该方法返回 False;否则,程序会取消该任务,并返回 True。
  cancelled():返回 Future 代表的线程任务是否被成功取消。
  running():如果该 Future 代表的线程任务正在执行、不可被取消,该方法返回 True。
  done():如果该 Funture 代表的线程任务被成功取消或执行完成,则该方法返回 True。
  result(timeout=None):获取该 Future 代表的线程任务最后返回的结果。如果 Future 代表的线程任务还未完成,该方法将会阻塞当前线程,其中 timeout 参数指定最多阻塞多少秒。
  exception(timeout=None):获取该 Future 代表的线程任务所引发的异常。如果该任务成功完成,没有异常,则该方法返回 None。
  add_done_callback(fn):为该 Future 代表的线程任务注册一个“回调函数”,当该任务成功完成时,程序会自动触发该 fn 函数。
在用完一个线程池后,应该调用该线程池的 shutdown() 方法,该方法将启动线程池的关闭序列。调用 shutdown() 方法后的线程池不再接收新任务,但会将以前所有的已提交任务执行完成。当线程池中的所有任务都执行完成后,该线程池中的所有线程都会死亡。

使用线程池来执行线程任务的步骤如下:
  1. 调用 ThreadPoolExecutor 类的构造器创建一个线程池。
  2. 定义一个普通函数作为线程任务。
  3. 调用 ThreadPoolExecutor 对象的 submit() 方法来提交线程任务。
  4. 当不想提交任何任务时,调用 ThreadPoolExecutor 对象的 shutdown() 方法来关闭线程池。

多进程

  • 创建多进程
  1. 导入进程包
    import multiprocessing
  2. 通过进程类创建进程对象
    进程对象=multiprocessing.Process(target=函数名)
函数名说明
target执行的函数或者方法
name进程名,一般不设置
group进程组,目前只能使用None
  1. 启动进程执行任务
    进程对象.start()
  2. 多进程
  3. 分布式进程

协程

通常在Python中我们进行并发编程一般都是使用多线程或者多进程来实现的,对于计算型任务由于GIL的存在我们通常使用多进程来实现,而对于IO型任务我们可以通过线程调度来让线程在执行IO任务时让出GIL,从而实现表面上的并发。其实对于IO型任务我们还有一种选择就是协程,协程是运行在单线程当中的"并发",协程相比多线程一大优势就是省去了多线程之间的切换开销,获得了更大的运行效率。
协程,又称微线程,协程的作用是在执行函数A时可以随时中断去执行函数B,然后中断函数B继续执行A(可以自由切换)。但这一过程并不是函数调用,这一整个过程看似多线程,然而协程只有一个线程执行。

  • 协程的优势
    执行效率高;因为子程序切换不是线程切换,有程序自身控制,没有切换线程的开销。所以与多线程相比,线程的数量越多,协程性能的优势越明显;
    不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在控制共享资源时也不需要加锁,因此执行效率很高。

协程实现

  • yield+send
    生产者消费者模型
def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
        	print('其实send就是替换整个yield表达式')
            return
        print('[CONSUMER]Consuming %s...' % n)
        r = '200 OK'

def producer(c):
    # 启动生成器
    c.send(None)
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER]Producing %s...' % n)
        r = c.send(n)
        print('[PRODUCER]Consumer return: %s' % r)
    c.close()

if __name__ == '__main__':
    c = consumer()
    producer(c)

# 结果
[PRODUCER]Producing 1...
[CONSUMER]Consuming 1...
[PRODUCER]Consumer return: 200 OK
[PRODUCER]Producing 2...
[CONSUMER]Consuming 2...
[PRODUCER]Consumer return: 200 OK
[PRODUCER]Producing 3...
[CONSUMER]Consuming 3...
[PRODUCER]Consumer return: 200 OK
[PRODUCER]Producing 4...
[CONSUMER]Consuming 4...
[PRODUCER]Consumer return: 200 OK
[PRODUCER]Producing 5...
[CONSUMER]Consuming 5...
[PRODUCER]Consumer return: 200 OK

send(msg)与next()的区别在于send可以传递参数给yield表达式,这时传递的参数会作为yield表达式的值,而yield的参数是返回给调用者的值。换句话说,就是send可以强行修改上一个yield表达式的值。比如函数中有一个yield赋值a = yield 5,第一次迭代到这里会返回5,a还没有赋值。第二次迭代时,使用send(10),那么就是强行修改yield 5表达式的值为10,本来是5的,结果a = 10。send(msg)与next()都有返回值,它们的返回值是当前迭代遇到yield时,yield后面表达式的值,其实就是当前迭代中yield后面的参数。第一次调用send时必须是send(None),否则会报错(TypeError: can't send non-None value to a just-started generator),之所以为None是因为这时候还没有一个yield表达式可以用来赋值。

  • asyncio+yield from
    为了简化并更好地标识异步IO,从Python3.5开始引入了新的语法async和await,可以让coroutine的代码更简洁易读。请注意,async和await是coroutine的新语法,使用新语法只需要做两步简单的替换:把@asyncio.coroutine替换为async;把yield from替换为await。
import asyncio
async def test(i):
    print('test_1', i)
    await asyncio.sleep(1)
    print('test_2', i)
if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    tasks = [test(i) for i in range(3)]
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()
# 结果
test_1 2
test_1 0
test_1 1
test_2 2
test_2 0
test_2 1
  • Gevent
    Gevent是一个基于Greenlet实现的网络库,通过greenlet实现协程。基本思想是一个greenlet就认为是一个协程,当一个greenlet遇到IO操作的时候,比如访问网络,就会自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO操作。
from gevent import monkey; monkey.patch_all()
from urllib import request
import gevent

def test(url):
    print('Get: %s' % url)
    response = request.urlopen(url)
    content = response.read().decode('utf8')
    print('%d bytes received from %s.' % (len(content), url))

if __name__ == '__main__':
    gevent.joinall([
        gevent.spawn(test, 'http://httpbin.org/ip'),
        gevent.spawn(test, 'http://httpbin.org/uuid'),
        gevent.spawn(test, 'http://httpbin.org/user-agent')
    ])
# 结果
Get: http://httpbin.org/ip
Get: http://httpbin.org/uuid
Get: http://httpbin.org/user-agent
33 bytes received from http://httpbin.org/ip.
53 bytes received from http://httpbin.org/uuid.
40 bytes received from http://httpbin.org/user-agent.

__init__、__new__、__call__

__init__

首先需要理解的是,两个下划线开头的函数是声明该属性为私有,不能在类的外部被使用或访问。而__init__函数(方法)支持带参数类的初始化,也可为声明该类的属性(类中的变量)。__init__函数(方法)的第一个参数必须为self,后续参数为自己定义。
init()方法来初始化对象。当一个对象被创建,Python首先创建一个空对象,然后为那个新对象调用init()方法。
注意点
init并不相当于C中构造函数,init作用是初始化已实例化后的对象;

a=A('hello')
可以理解为
a=object.__new__(A) # 每一个Python类都隐含继承了一个超类:object。所有类的超类object,有一个默认包含pass的init()实现,我们不需要去实现init()。如果不实现它,则在对象创建后就不会创建实例变量。在某些情况下,这种默认行为是可以接受的。
A.__init__(a,'hello')

子类可以不重写init,实例化子类时,会自动调用超类中已定义的init;但是如果重写init方法,为了能使用或扩展类中的行为,最好显式的调用超类的init方法

class C(A):
    def __init__(self,name):
        super(C,self).__init__(name)

__new__

new和init相配合才是python中真正的类构造器。new方法是传入类(cls),而init方法传入类的实例化对象(self),而有意思的是,new方法返回的值就是一个实例化对象(ps:如果new方法返回None,则init方法不会被执行,见下方代码1,并且返回值只能调用具有血缘关系中的new方法,而不能调用毫无关系的类的new方法),即如果新式类中重写了new()方法,那么你可以自由选择任意一个的其他的新式类(必定要是新式类,只有新式类必定都有new(),因为所有新式类都是object的后代,而经典类则没有new()方法)的new()方法来制造实例,包括这个新式类的所有前代类和后代类,只要它们不会造成递归死循环,见下方代码2。new方法主要是当你继承一些不可变的class时(比如int, str, tuple), 提供给你一个自定义这些类的实例化过程的途径。
代码1

class nonZero(int):
    def __new__(cls,value):
        return super().__new__(cls,value) if value != 0 else None
    def __init__(self,skipped_value):
        #此例中会跳过此方法
        print("__init__()")
        super().__init__()
print(type(nonZero(-12)))
print(type(nonZero(0)))
运行结果:
__init__()
<class '__main__.nonZero'>
<class 'NoneType'>

代码2

class Foo(object):
    def __init__(self, *args, **kwargs):
        ...
    def __new__(cls, *args, **kwargs):
        return object.__new__(cls, *args, **kwargs)    

# 以上return等同于 
# return object.__new__(Foo, *args, **kwargs)
# return Stranger.__new__(cls, *args, **kwargs)
# return Child.__new__(cls, *args, **kwargs)

class Child(Foo):
    def __new__(cls, *args, **kwargs):
        return object.__new__(cls, *args, **kwargs)

__call__

call方法的功能类似于在类中重载 () 运算符,使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用。Python 中,凡是可以将 () 直接应用到自身并执行,都称为可调用对象。可调用对象包括自定义的函数、Python 内置函数以及本节所讲的类实例对象。对于可调用对象,实际上“名称()”可以理解为是“名称.__call__()”的简写。

class CLanguage:
    # 定义__call__方法
    def __call__(self,name,add):
        print("调用__call__()方法",name,add)
clangs = CLanguage()
clangs("C语言中文网","http://c.biancheng.net")
# 等价于 clangs.__call__("C语言中文网","http://c.biancheng.net")

用 __call__() 弥补 hasattr() 函数的短板,该函数的功能是查找类的实例对象中是否包含指定名称的属性或者方法,但该函数有一个缺陷,即它无法判断该指定的名称,到底是类属性还是类方法。借助可调用对象的概念,类实例对象包含的方法,其实也属于可调用对象,但类属性却不是。

if hasattr(clangs,"name"):
   print(hasattr(clangs.name,"__call__"))
if hasattr(clangs,"say"):
   print(hasattr(clangs.say,"__call__"))

异常分类与处理

常见异常

类名描述
BaseException所有异常的基类
Exception常规异常的基类
AttributeError对象不存在
IndexError序列中无此序列
IOError输入/输出操作失败
KeyboardInterrupt用户中断执行
KeyError映射中不存在此键
NameError找不到变量、名字
SyntaxErrorPython语法错误
TypeError对类型无效的参数
ValueError传入无效的参数
ZeroDivisionError除或取模参数为0

异常处理

捕获异常可以使用try/except语句。try/except语句用来检测try语句块中的错误,从而让except语句捕获异常信息并处理。

  • 工作原理
    1.如果当try后的语句执行时发生异常,python就跳回到try并执行第一个匹配该异常的except子句,异常处理完毕,控制流就通过整个try语句(除非在处理异常时又引发新的异常)。
    2.如果在try后的语句里发生了异常,却没有匹配的except子句,异常将被递交到上层的try,或者到程序的最上层(这样将结束程序,并打印默认的出错信息)。
    3.如果在try子句执行时没有发生异常,python将执行else语句后的语句(如果有else的话),然后控制流通过整个try语句。

处理过程

  • 使用except带多个异常类型
try:
    正常的操作
   ......................
except(Exception1[, Exception2[,...ExceptionN]]):
   发生以上多个异常中的一个,执行这块代码
   ......................
else:
    如果没有异常执行这块代码

也可以不带任何异常类型而只使用except,但这不是一个很好的方式,不能通过该程序识别出具体的异常信息,因为它捕获了所有异常。

  • try-finally
try:
<语句>
finally:
<语句>    #退出try时总会执行
raise
  • 异常的参数
try:
    正常的操作
   ......................
except ExceptionType, Argument:
    你可以在这输出 Argument 的值...
  • 触发异常
raise [Exception [, args [, traceback]]]

实例

def functionName( level ):
    if level < 1:
        raise Exception("Invalid level!", level)
        # 触发异常后,后面的代码就不会再执行

self

结合上面的init与new函数其实就可以很好的理解self,self就是指调用时的类的实例。

包(package)

包就是文件夹,但该文件夹下必须存在 __init__.py 文件, 该文件的内容可以为空。__init__.py 用于标识当前文件夹是一个包。
实例

目录结构
test.py
package_runoob
|-- __init__.py
|-- runoob1.py
|-- runoob2.py
package_runoob 同级目录下的 test.py 来调用 package_runoob 包
from package_runoob.runoob1 import runoob1
from package_runoob.runoob2 import runoob2
runoob1()
runoob2()

有两个目录:dir A/C.py,dir B/D.py。问D.py如何引用C.py?
①首先在dir A文件夹中创建__init__.py文件,里面什么都不需要编辑。(如何将一个普通的文件夹变成一个python包,包的标识__init__)。②其次将dir A的路径加入到python解释器可以搜索到的路径列表。import sys sys.path.append()。

浮点数误差

print(0.1+0.2 == 0.3)
# 返回Fasle
# 修改
from decimal import Decimal
print(float(Decimal(str(0.1))+Decimal(str(0.2)))==0.3)
# 返回True

文件操作

读取键盘输入

input([prompt]) 函数和 raw_input([prompt]) 函数基本类似,但是 input 可以接收一个Python表达式作为输入,并将运算结果返回。

str = raw_input("请输入:")
print("你输入的内容是: ", str)
# 请输入:Hello Python!
# 你输入的内容是:  Hello Python!
str = input("请输入:")
print("你输入的内容是: ", str)
# 请输入:[x*5 for x in range(2,10,2)]
# 你输入的内容是:  [10, 20, 30, 40]

打开文件和关闭文件

文件模式参数图

在这里插入图片描述

操作函数

  • open
file object = open(file_name [, access_mode][, buffering])
# 参数分别是文件名称;打开文件的模式;是否寄存以及寄存大小
  • close
    File 对象的 close()方法刷新缓冲区里任何还没写入的信息,并关闭该文件,这之后便不能再进行写入。当一个文件对象的引用被重新指定给另一个文件时,Python 会关闭之前的文件。用 close()方法关闭文件是一个很好的习惯。
  • write
    write()方法可将任何字符串写入一个打开的文件。需要重点注意的是,Python字符串可以是二进制数据,而不是仅仅是文字。write()方法不会在字符串的结尾添加换行符(’\n’)
  • read
    read()方法从一个打开的文件中读取一个字符串。需要重点注意的是,Python字符串可以是二进制数据,而不是仅仅是文字。
  • 文件定位
    tell() 方法告诉你文件内的当前位置, 换句话说,下一次的读写会发生在文件开头这么多字节之后。
    seek(offset [,from]) 方法改变当前文件的位置。Offset变量表示要移动的字节数。From变量指定开始移动字节的参考位置。
    如果from被设为0,这意味着将文件的开头作为移动字节的参考位置。如果设为1,则使用当前的位置作为参考位置。如果它被设为2,那么该文件的末尾将作为参考位置。
  • rename
    rename() 方法需要两个参数,当前的文件名和新文件名。
  • remove
  • 用remove()方法删除文件,需要提供要删除的文件名作为参数。
  • 目录操作
  1. mkdir()
    使用os模块的mkdir()方法在当前目录下创建新的目录们。你需要提供一个包含了要创建的目录名称的参数。
os.mkdir("newdir")
  1. chdir()
    可以用chdir()方法来改变当前的目录。
os.chdir("newdir")
  1. getcwd
    getcwd()方法显示当前的工作目录。
os.getcwd()
  1. rmdir()
    rmdir()方法删除目录,目录名称以参数传递。在删除这个目录之前,它的所有内容应该先被清除。
os.rmdir('dirname')

正则表达式

正则表达式

  • 4
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZoomToday

给作者倒一杯卡布奇诺

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值