0%

python面试题

基本问题

  1. 列出5个常用Python标准库

    Python标准库非常庞大,包含了很多内置模块(C编写),用以实现系统级功能,例如I/O,还有大量以Python编写的模块,提供了日常编程中许多问题的标准解决方案。其中有些模块经过专门设计,通过将特定平台功能抽象化为平台中立的API来增加Python程序的可移植性。Windows版本的Python安装程序包通常包含整个标准库,还有许多额外的组件。但是类Unix系统下,Python会分成一系列的软件包,因此可能需要使用操作系统提供的包管理工具来获取部分或者全部可选组件。
    如果不确定接口里函数有哪些或者怎么用,可以通过引入模块后,
    dir(os)和help(os)查看有哪些函数和用法
    操作系统接口:
    1.os:提供了许多与操作系统交互的函数
        import os
        os.getcwd()
        os.chdir('home/bin')
        os.system('mkdir today')  # 执行mkdir命令
        注意:一定要使用import os 而不是from os import *,避免内建的
            open()函数被os.open()隐式替换掉。
        对于平时文件和目录管理任务,shutil模块提供了更易于使用的更高级别的接口。
        import shutil
        shutil.copyfile('data.db','archive.db')
        shutil.move('buil/executables','installdir')
    2.文件通配符glob模块,提供了在目录中使用通配符搜索创建文件列表的函数
        import glob
        glob.glob('*.py')  # 搜索出所有py文件
    3.命令行参数sys,通用实用程序脚本通常需要处理命令行参数。这些参数作为列表存储在sys模块的argv属性中。命令行运行python demo.py one two时
        import sys
        print(sys.argv)
        输出 demo.py one two
    argparse模块提供了一种处理命令行参数的机制。它应该总是优先于直接手工处理的sys.argv.
        import argparse
        from getpass import getuser
        parser = argparse.ArgumentParser(description='An example')
        parser.add_argument('name',nargs='?',default=getuser(),help='The name of someone to greet')
        parser.add_argument('--verbose','-v',action='count')
        args = parser.parser_args()
        greeting = ['Hi','Hello','Greetings! its wonderful'][args.verbos % 3]
        print(f'{greeting},{args.name}')
        if not args.verbose:
            print('Try running this again with multiple "-v" flags!')
        sys模块还有stdin,stdout,stderr的属性,后者对于发出警告和错误消息非常有用,即使在stdout被重定向后也可以看到
        sys.stderr.write('Warning,log file not found starting\n')
    4.字符串模式匹配re,正则表达式
        import re
        re.findall(r'\bf[a-z]*','which foot or hand fell fastest')
        re.sub(r'(\b[a-z]+) \1',r'\1','cat in the hat')
        当只需要简单的功能时,首选字符串方法,因为容易调试和阅读
        'tea for too'.replace('too','two')
    5.数学
    math模块提供了对浮点数数学的底层C库函数的访问:
        import math
        math.cos(math.pi / 4)
        math.log(1024,2)
    random模块提供了随机选择的工具
        import random
        random.choice(['apple','pear','banana'])
        random.sample(range(100),10)  # 随机选10个数当样本
        random.random()  # 0到1随机一个float数
        random.randrange(6)
    statistics模块,计算数值数据的基本统计属性(均值,中位数,方差)
    import statistics
    data = [2.75,1.75,1.25,0.25,.0.5,1.25]
    statistics.mean(data)  # 平均值
    statistics.median(data)  # 中位数
    statistics.variance(data)  # 方差
    SciPy项目<https://scipy.org>有许多其他模块用于数值计算
    6.互联网访问
    urllib.request用于从URL检索数据,smtplib用于发送邮件
    from urllib.request import urlopen
    with urlopen('http://tycho.usno.navy.mil') as response:
        for line in response:
            line = line.decode('utf-8')
            if 'EST' in line or 'EDT' in line:
                print(line)  # 获取西方时间
    import smtplib
    server = smtplib.SMTP('localhost')  # 邮件服务器运行地
    server.sendmail('bayhax@163.com','whlbayahx@163.com',
    """To:whlbayahx@163.com
    From:bayhax@163.com
    fd
    """)
    server.quit()
    7.日期和时间
    datetime 提供了简单和复杂的方式操作日期和时间的类。虽然支持时间和日期算法,但实现的重点是有效的成员提取进行输出格式化操作。
        from datetime import date
        now = date.today()
        now.strftime("%m-%d-%y. %d %b %Y is a %A on the %d day of %B")
        birthday = date(1949,10,1)
        age = now - birthday
        age.days
    8.数据压缩 zlib gzip bz2 lzma zipfile tarfile
        import zlib
        s = b'witch which has which witchs wrist watch'
        len(s)
        t = zlib.compress(s)
        len(t)
        zlib.decompress(t)
        zlib.crc32(s)
    9.性能测试timeit
        from timeit import Timer
        Timer('t=a;a=b;b=t','a=1;b=2').timeit()
        Timer('a,b=b,a','a=1,b=2').timeit()
        与timeit的精细粒度级别相反,profile和pstats模块提供了用于在较大代码块中识别时间关键部分的工具。
    10.质量控制
    开发高质量软件的一种方法是在开发过程中为每个函数编写测试,并在开发过程中经常运行这些测试。
    doctest模块提供了一个工具,用于扫描模块并验证程序文档字符串中嵌入的测试。测试构造就像将典型调用及其结果剪切并粘贴到文档字符串一样简单。这通过向用户提供示例来改进文档,并且允许doctest模块确保代码保持对文档的真实。
        def average(values):
            """Computes the arithmetic mean of a list of numbers.
            \>>> print(average([10,30,70]))
            40.0
            """
            return sum(values) / len(values)
        import doctest
        doctest.testmod()
    unittest模块不像doctest模块那样易于使用,但是它允许在已个单独的文件中维护更全面的测试集
        import unittest
        class TestStatisticalFunctions(unittest.TestCase):
            def test_average(self):
                self.assertEqual(average([20,30,70]),40.0)
                self.assertEqual(round(average([1,5,7]),1),4.3)
                with self.assertRaise(ZeroDivisionError):
                    average([])
                with self.assertRaise(TypeError):
                    average(20,30,70)
        unittest.main()
    11.自带电池
        Python有“自带电池”的理念。通过其包的复杂和强大功能可以最好地看到这一点。例如:
        xmlrpc.client 和 xmlrpc.server 模块使得实现远程过程调用变得小菜一碟。尽管存在于模块名称中,但不需要直接了解或处理XML。
        email 包是一个用于管理电子邮件的库,包括MIME和其他符合 RFC 2822 规范的邮件文档。与 smtplib 和 poplib 不同(它们实际上做的是发送和接收消息),电子邮件包提供完整的工具集,用于构建或解码复杂的消息结构(包括附件)以及实现互联网编码和标头协议。
        json 包为解析这种流行的数据交换格式提供了强大的支持。 csv 模块支持以逗号分隔值格式直接读取和写入文件,这种格式通常为数据库和电子表格所支持。 XML 处理由 xml.etree.ElementTree , xml.dom 和 xml.sax 包支持。这些模块和软件包共同大大简化了 Python 应用程序和其他工具之间的数据交换。
        sqlite3 模块是 SQLite 数据库库的包装器,提供了一个可以使用稍微非标准的 SQL 语法更新和访问的持久数据库。
        国际化由许多模块支持,包括 gettext , locale ,以及 codecs 包。
    专业编程需要的包,很少用在脚本中,上面多数用在脚本中。
    12.格式化输出
    reprlib提供了一个定制化版本的repr()函数,用于缩略显示大型或深层嵌套的容器对象。
        import reprlib
        reprlib.repr(set('supercalifragilisticexpialidocious'))
    pprint模块提供了更加复杂的打印控制,其输出的内置对象和用户自定义对象能够被解释器直接读取。当输出结果过长而需要折行时,’美化输出机制’会添加换行符和缩进,以更清楚展示数据结构。
        import pprint
        t = [[[['black','cyan'],'white',['green','red']],[['magenta','yellow'],'blue']]]
        pprint.pprint(t, width=30)
    textwrap模块能够格式化文本段落,以适应屏幕宽度
        import textwrap
        doc = """The wrap() method is just like fill() expect that         it returns a list of strings instead of one big          string with newlines to separate the wrapped lines."""
        print(textwrap.fill(doc, width=40))
    locale模块处理与特定地域文化相关的数据格式,locale模块的format函数包含一个grouping属性,可以直接将数字格式化为带有组分隔符的样式:
        import locale
        locale.setlocale(locale.LC_ALL,'English_United States.1252')
        conv = locale.localeconv()
        x = 1234567.8
        locale.format("%d", x, grouping=True)
        locale.format_string("%s%.*f",(conv['currency_symbo'],conv['frac_digits'],x),grouping=True)
    13.模板
    string模块包含一个通用的Template类,具有适用于最终用户的简化语法。允许用户在不更改应用逻辑的情况下定制自己的应用。
    上述格式化操作是通过占位符实现的,占位符由$加上合法的Python标识符构成。一旦使用花括号将占位符括起来,就可以在后面直接跟上更多的字母和数字而无需空格分割。$$将被转义成单个字符$
        from string import Template
        t = Template('${village}folkk send $$110 to $cause')
        t.substitute(village='Nottingham',cause='the dicth fund')
    如果在字典或关键字参数中未提供某个占位符的值,那么substitute()方法将抛出KeyError。对于邮件合并类型的应用,用户可提供的数据有可能是不完整的,此时使用safe_substitute()方法更加合适--如果数据丢失,它会直接将占位符原样保留
        t = Template('Return the $item to $owner.')
        d = dict(item='unladen swallow')
        t.substitute(d)
        t.safe_substitute(d)
    Template的子类可以自定义定界符。例如
        照片浏览器的批量重命名功能
        import time, os.path
        photofiles = ['img_1074.jpg','img_1076.jpg','img_1077.jpg']
        class BatchRename(Template):
            delimiter = '%'
        fmt = input('Enter rename style (%d-date %n-seqnum %f-format): ')
        t = BatchRename(fmt)
        date = time.strftime('%d%b%y')
        for i,filename in enumerate(phototfiles):
            base,ext = os.path.splitext"(filename)
            newname = t.substitute(d=date,n=i,f=ext)
            print('{0} --> {1}'.format(filename, newname))
    13.二进制数据记录格式
    struct模块提供了pack()和unpack()函数,用于处理不定长度的二进制记录格式。Pack代码'H','I'分别代表了两字节和四字节无符号证书。"<"代表他们是标准尺寸的小尾型字节序:
        import struct
        with open('myfile.zip','rb') as f:
            data = f.read()
        start = 0
        for i in range(3):
            start += 14
            fields = struct.unpack('<IIIHH',data[start:start+16])
            crc32, comp_size, uncomp_size, filenamesize, extra_size = fields
            start += 16
            filename = data[start:start+filenamesize]
            start += filenamesize
            extra = data[start:start+extra+size]
            print(filename, hex(crc32), comp_size, uncomp_size)
            start += extra_size + comp_size
    14.多线程
    线程是一种对于非顺序依赖的多个任务进行解耦的技术。多线程可以提高应用的响应效率,当接收用户输入的同事,保持其他任务在后台运行。例如将I/O和计算应用在两个并行的线程中。
        import threading, zipfile
        class AsyncZip(thread.Thread):
            def __init__(self, infile, outfile):
                threading.Thread.__init__(self)
                self.infile = infile
                self.outfile = outfile
            def run(self):
                f = zipfile.ZipFile(self.outfile,'w',zipfile.ZIP_DEFLATED)
                f.write(self.infile)
                f.close()
                print('Finished background zip of:',self.infile)
        background = AsyncZip('mydata.txt','myarchive.zip')
        background.start()
        print('The main program continues to run in foreground')
        background.join()
        print('Main program waited until background was done.')
    多线程应用面临的主要挑战是,相互协调的多个线程之间需要共享数据或其他资源。为此,threading 模块提供了多个同步操作原语,包括线程锁、事件、条件变量和信号量。
    尽管这些工具非常强大,但微小的设计错误却可以导致一些难以复现的问题。因此,实现多任务协作的首选方法是将对资源的所有请求集中到一个线程中,然后使用 queue 模块向该线程供应来自其他线程的请求。应用程序使用 Queue 对象进行线程间通信和协调,更易于设计,更易读,更可靠。
    15.日志记录
    logging模块提供功能齐全且灵活的日志记录系统。
    import logging
    logging.debug('Debugging information')
    logging.info('Informational message')
    logging.warning('Warning:config file %s not found')
    logging.error('Error occurred')
    logging.critical('Critical error -- shutting down')
    默认情况下,informational 和 debugging 消息被压制,输出会发送到标准错误流。其他输出选项包括将消息转发到电子邮件,数据报,套接字或 HTTP 服务器。新的过滤器可以根据消息优先级选择不同的路由方式:DEBUG,INFO,WARNING,ERROR,和 CRITICAL。
    日志系统可以直接从 Python 配置,也可以从用户配置文件加载,以便自定义日志记录而无需更改应用程序。
    16.弱引用
    Python 会自动进行内存管理(对大多数对象进行引用计数并使用 garbage collection 来清除循环引用)。 当某个对象的最后一个引用被移除后不久就会释放其所占用的内存。
    此方式对大多数应用来说都适用,但偶尔也必须在对象持续被其他对象所使用时跟踪它们。 不幸的是,跟踪它们将创建一个会令其永久化的引用。 weakref 模块提供的工具可以不必创建引用就能跟踪对象。 当对象不再需要时,它将自动从一个弱引用表中被移除,并为弱引用对象触发一个回调。 典型应用包括对创建开销较大的对象进行缓存:
        import weakref, gc
        class A:
            def __init__(self, value):
                self.value = value
            def __repr__(self):
                return str(self.value)
        a = A(10)
        d = weakref.WeakValueDictionary()
        d['primary'] = a
        d['primary']
        del a
        gc.collect()
        d['primary']
    17.用于操作列表的工具
    array模块提供了一种array()对象,类似于列表,但只能存储类型一致的数据且存储密集更高。
        from array import array
        a = array('H', [4000,10,700,20000])
        sum(a)
        a[1:3]
    collections模块提供了deque()对象,类似于列表,但从左端添加和弹出的速度较快,而在中间查找的速度较慢。适用于实现队列和广度优先树搜索。
        from collections import deque
        d = deque(['task1','task2','task3'])
        d.append('task4')
        print('Handling',d.popleft())
        unsearched = deque([starting_node])
        def breadth_first_search(unsearched):
            node = unsearched.popleft()
            for m in gen_moves(node):
                if is_goal(m):
                    return m
                unsearched.append(m)
    在替代的列表实现以外,标准库也提供了其他工具,例如bisect模块具有用于操作排序列表的函数:
        import bisect
        scores = [(100,'perl'),(200,'tcl'),(400,'lua'),(500,'python')]
        bisect.insort(scores, (300,'ruby'))
    heapq模块提供了基于常规列表来实现堆的函数。最小值的条目总是保持在位置零。这对于需要重复访问最小元素而不希望运行完整列表排序的应用来说非常有用。
    from heapq import heapify, heappop, heappush
    data = [1,3,5,6,7,9,2,4,6,8,0]
    heapify(data)
    heappush(data, -5)
    [heappop(data) for i in range(3)]
    18.十进制浮点运算
    decimal模块提供了Decimal数据类型用于十进制浮点运算。相比内置的float二进制浮点实现。该类特别适用于:
        财务应用和其他需要精确十进制表示的用途,
        控制精度
        控制四舍五入以满足法律或监管要求
        跟踪有效小数位
        用户期望结果与手工完成的计算相匹配的应用程序
        from decimal import *
        round(Decimal('0.70') * Decimal('1.05'), 2)
    Decimal表示的结果会保留尾部的零,并根据具有两个有效位的被乘数自动推出四个有效位。Decimal可以模拟手工运算来避免当二进制浮点数无法精确表示十进制数时会导致的问题。
    精确表示特性使得Decimal类能够执行对于二进制浮点数来说不适用的模运算和相等性检测
        Decimal('1.00') % Decimal('.10')  # Decimal('0.00')
        1.00 % 0.10   # 两个结果不一样  0.0999999999995
        sum([Decimal('0.1')]*10) == Decimal('1.0')  # True
        sum([0.1]*10) == 1.0  # False
    decimal模块提供了运算所需要的足够精度
    getcontext().prec = 36
    Decimal(1) / Decimal(7)
  2. Python内建数据类型有哪些?

    数字(number)---不可变
    字符串(str)---不可变
    元组(tuple)---不可变
    列表(list)---可变
    字典(dict)---可变
    集合(set)---可变
  3. 简述with方法打开处理文件帮我们做了什么?

    f = open("./test.txt", "wb")
    try:
        f.write("hello world")
    except:
        pass
    finally:
        f.close()
    打开文件在进行读写的时候可能会出现一些异常状况,如果按照常规的f.open写法,我们需要try-except-finally做异常判断,并且文件最终不管遇到什么情况,都执行finally:f.close(),而with方法就帮我们实现了finally中的f.close()
  4. 列出Python中可变数据类型和不可变数据类型

    可变: list dict set
    不可变: int str bool tuple
  5. Python获取当前日期?

    improt datetime
    today = datetime.date.today()
    print(today)
  6. 统计字符串每个单词出现的次数

    import io
    import re
    class Counter:
        def __init__(self, path):
            """
            :param path: 文件路径
            """
            self.mapping = dict()
            with io.open(path, encoding="utf-8") as f:
                data = f.read()
                words = [s.lower() for s in re.findall("\w+", data)]
                for word in words:
                    self.mapping[word] = self.mapping.get(word, 0) + 1
        def most_common(self, n):
            assert n > 0, "n should be large than 0"
            return sorted(self.mapping.items(), key=lambda item: item[1], reverse=True)[:n]
    if __name__ == '__main__':
        most_common_5 = Counter("三国演义.txt").most_common(5)
        for item in most_common_5:
            print(item)
  7. 用Python删除文件和用Linux命令删除文件方法

    python中:
        import os
        if os.path.exists("文件路径"):
            os.remove(文件)
        else:
            print("文件不存在")
    
        删除文件夹
        import os
        os.rmdir("文件夹路径"")
    linux中:
        rm 文件    
        rm -r  文件夹
  8. 写一段自定义异常代码

    # raise自定义异常
    def fn():
        try:
            for i in range(5):
                if i > 2:
                    raise Exception("数字大于2了")
        except Exception as ret:
            print(ret)
    fn()
  9. 举例说明异常模块中try except else finally 的相关意义

    try:
        语句块里要处理的动作
    except 错误类型 as e:
        出现该错误时要提示的错误信息或者进行处理的相关步骤
    except 错误类型 as e:
        同上
    else:
        没有错误时执行的语句,有错误时不执行
    finally:
        有没有错误最后都要执行的语句块
  10. 遇到bug如何处理

    可以进行try-except-finally查看错误信息,
    然后根据错误信息进行相应处理
    
    如果简单化直观处理没写try-except,print打印语句,
    快速定位错误所在地方

语言特性

  1. 谈谈对Python和其他语言的区别

    1.语言简洁,优雅,不用大括号,用缩进代表,强制体现语言书写的规范与优美,变量不用声明,直接使用。
    2.解释性语言,非编译型语言,一行一行的解释,执行,开发效率高
    3.开源,历史较悠久,通用性语言,爬虫,web开发,数据分析,人工智能都可以做,第三方库完备。
    4.但是执行效率相较于C来说,很慢。但是随着现在硬件的发展,在一些地方可以由硬件进行弥补。
    5.对初学者友好,没有任何基础的学起来容易上手。
  2. 简述解释型语言和编译型语言

    解释性语言:一行一行的解释,边解释边运行
    编译型语言:编译后再整体执行
  3. Python的解释器种类及相关特点

    CPython:从官网下载安装好的Python,解释器就是CPython,使用最广
    IPython:基于CPython之上的一个交互式解释器,只是在交互上功能较
        强,如有魔法方法%time等,在解释Python脚本时,和CPython是一样的
    PyPy:目标是执行速度,它采用JIT技术,对Python代码进行动态编译,         所以可以显著提高执行速度
    Jython:是运行在Java平台上的Python解释器,可以直接把Python代码J     ava字节码进行执行
    IronPython:和Jython类似,只不过IronPython是运行在微软的.Net平     台上的解释器,可以直接把Python代码编译成.Net字节码
  4. 说说你知道的Python3和Python2的区别

    print:python3中print为一个函数,必须用括号;Python2中print
        为一个class,不用必须括号
    input()解析用户的输入:Python3中input得到的类型为str,Python2
        得到的类型为int,Python2的raw_input得到的是str类型
    整除:Python3中/真除,%取余,//结果取整的除法
        Python2中/带上小数点是真除,%取余,//结果取整
    range:Python3中只有range(),返回的是迭代对象,不是迭代器
        Python2中有range返回列表 和  xrange返回一个序列,返回的都是迭代对象,不是迭代器
  5. Python3和Python2中int和long的区别

    python2中有long类型,Python3中没有long类型,只有int类型
  6. xrange和range的区别

    range返回的是一个列表
    xrange返回的是一个序列,是一个生成器,生成较大序列时,性能更好
    range(5)----[0,1,2,3,4]
    range(1,5)----[1,2,3,4]
    range(0,6,2)----[0,2,4]
    xrange(5)----xrange(5)
    生成序列时:
        a = range(0,100)
        print type(a)
        print a
        print a[0] a[1]
        结果:  <type 'list'>
            [0,1,2,3,4.......99]
            0 1
        a = xrange(0,100)
        print type(a)
        print a
        print a[0],a[1]
        结果:  <type 'xrange'>
        xrange(100)
        0 1

    编码规范

  7. 什么是PEP8

    1.缩进。4个空格的缩进,不能使用tab,更不能tab+空格
    2.每行最大长度79,换行可以使用反斜杠,最好使用圆括号。
    换行点要在操作符的后边敲回车
    3.类和top-level函数定义之间空两行,类中的方法定义之间
    空一行,函数内逻辑无关段落之间空一行,其他地发尽量不要空行
    4.模块导入顺序:标准库  第三方库 自己写的库,中间空一行
    5.不要在一句import中多个库,比如import os,sys不推荐
    6.避免不必要的空格
    7.注释必须有
    8.函数命名要遵循规范
    9.尽可能使用is is not去掉==,比如if x is not None 要优于if x
    10.使用基于类的异常,每个模块或包都有自己的异常类,此异常类继承自Exception
    11.异常中try的代码尽可能的少。
  8. 了解Python之禅吗

    python交互式界面import this出现python之禅
    Beautiful is better than ugly.
    优美胜于丑陋
    Explicit is better than implicit.
    明了胜于晦涩
    Simple is better than complex.
    简洁胜于复杂
    Complex is better than complicated.
    复杂胜于凌乱
    Flat is better than nested.
    扁平胜于嵌套
    Sqparse is better than dense.
    间隔胜于紧凑
    Readability counts.
    可读性很重要
    Special cases aren't special enough to break the rulse.
    Although pracitcality beats purity.
    即便假借特例的实用性之名,也不可违背这些规则
    Errors should never pass ailently.
    Unless explicityly silences.
    不要包容所有错误,除非你确定这样做
    In the face of ambiguity, refuse the temptation to guess.
    当存在多种可能,不要尝试去猜测
    There should be one--and preferably only one --obvious way to do it.
    而是尽量去找一种,最好是唯一一种明显的解决方案
    Although that way may not be obvious at first unless you're Dutch.
    虽然这并不容易,因为你不是Dutch(python之父)
    Now is better than never.
    Although never is often better than *right* now,
    做也许好过不做,但是不假思索就动手还不如不做
    If the implementation is hard to explain, it's a bad idea.
    If the implementataion is easy to explain, it may be a good idea.
    如果你无法向别人描述你的方案,那肯定不是一个好方案,反之亦然。
    Namespace are one honking great idea -- let's do more of those!
    命名空间是一种绝妙的理念,我们应该多加利用。 
  9. 了解docstring吗

    docstring文档字符串
    代码中的注释,编写完代码,文档也有了
    三个双引号引起来的部分
    出现在模块、函数、类、方法里第一个语句的,就是docstring。会自动变成属性__doc__。
  10. 了解类型注解吗

    因为python是动态语言,在使用变量前不需要声明变量,可以直接用
    所以有时候会错误的使用变量的类型。但是对实际运行不会有影响
    a: int = 2
    def add(a: int) -> int:  参数a为int类型,返回值int类型
  11. 列举你知道的Python对象的命名规范,例如方法或者类等

    1. 变量命名总结
        单下划线开头变量:protected
        双下划线开头:private
        双下划线开头,双下划线结尾:系统内置变量
    2. 函数命名总结
        私有方法:小写和一个前导下划线
        特殊方法(魔法方法):小写和两个前导下划线,两个后置下划线
        一般方法小写,函数参数:小写和下划线,缺省值等号两边无空格
    3. 类名称命名    
        类总是使用驼峰格式命名,所有单词首字母大写其余字母小写

  12. Python中的注释有哪几种

    1.单行注释   # 注释内容
    2.多行注释  """注释"""   '''注释'''
    3.编码注释  python2中最上一行 -- coding:UTF-8 --
        python3默认UTF-8编码,一般不用写
    4.平台注释  Linux下,#usr/bin/python告诉操作系统调用
        usr/bin/python来执行本文件。
  13. 如何优雅的给一个函数加注释

    使用docstring配合类型注解
    """
    函数的作用
    :param 变量名: 意义
    :param 变量名: 意义
    :return 返回值
    """
  14. 如何给变量加注释

    a: str = "注释"
    如果说明少  在变量后空两格,#,#后空一格  a = 10  # 注释
    如果说明多  在变量上方使用#进行注释
  15. Python代码缩进中是否支持Tab和空格混用

    不能混用,PEPE8建议使用4个空格,而非tab
    因为不同环境下tab键空格数不同,空格永远一样
  16. 是否可以在一句import中导入多个库

    可以,但是不推荐,这样可读性不好,尽量一行引入一个模块
    同时尽量少用from .. import ...  因为在判断某个函数或者
    属性的来源时有些困难,不方便调试,可读性降低
  17. 再给py文件命名的时候应该注意什么

    给文件命名的时候不要和标准库的一些模块重复,比如abc,否则引入
    标准库的时候,可能会出现错误。另外名字要有意义,见名知意,不建议
    以数字开头或者中文命名
  18. 列举几个规范Python代码风格的工具

    pylint  flake8

数据类型

字符串

  1. 列举Python中的基本数据类型

    number/string/tuple/list/dict/set
  2. 如何区别可变数据和不可变数据

    1) type() 函数查看类型
    2)id() 查看变量地址,如果两个变量储存同样的值,并且这两个变量的
    的地址一样,则是不可变数据,反之是可变数据。
  3. 将“hello world”转换为首字母大写”Hello World”

    "hello world".title()
    第一个单词的首字母大写   s.capitalize()
  4. 如何检测字符串中只含有数字

    s.isdigit()返回True则只包含数字,
    s.isalpha()返回True则只包含字母,
    s.isalnum()返回True则数字字母混合,
    s.islower()是否是小写,数字算小写,
    s.upper()是否全是大写,
    s.istitle()是否是标题,首字母大写
  5. 将字符串”ilovechina”进行反转

    reverse是对于列表而言,所以不行,
    切片,反向递进:b = s[::-1]
    reversed函数,c = ''.join(reversed(a))
  6. Python中的字符串格式化方式你知道哪些

    %s 
    .format()
  7. 有一个字符串开头和末尾都有空格,比如” adabdw “,要求写一个函数把这个
    字符串的前后空格都去掉

    def rm_space(str1):
        str2 = str1.replace(" ","")
        # split转换成列表,再join去掉所有空格
        # str2 = "".join(str1.split())
  8. 获取字符串”123456”最后两个字符

    a = str[len(str)-2:]
  9. 一个编码为GBK的字符串S,要将其转成UTF-8编码的字符串,应该如何操作

    S.decode('gbk').decode('utf-8')
  10. s = ‘info: xiaoZhang 33 shandong’,
    用正则切分字符串输出[‘info’,’xiaoZhang’,’33’,’shandong’]
    a = “你好 中国 “,去除多余空格,只留一个空格
    t = re.split(r”: | “, “ “.join(s.split()))
    print(“ “.join(s.split()))

  11. 怎样将字符串转换为小写
    单引号 双引号 三引号的区别

    s.lower()
    字符串如果是双引号引起来的,里面如果再想用引号就要用单引号。
    单引号也一样,单引号双引号没什么大区别。
    三引号引起来的字符串可以换行,可以当做docstring,在函数说明中

    列表

  12. 已知AList = [1,2,3,1,2],对AList列表元素去重,写出具体过程

    blist = list(set(AList))
  13. 如何实现’1,2,3’变成[‘1’,’2’,’3’]

    import re
    t = re.split(",","1,2,3")
  14. 给定两个list,A和B,找出相同元素和不同元素

    list(set(A)&set(B))  相同
    list(set(A)^set(B))  不相同
  15. [[1,2],[3,4],[5,6]]一行代码展开该列表,得出[1,2,3,4,5,6]

    列表生成式
    l = [[1,2],[3,4],[5,6]]
    x = [j for i in l for j in i] 相当于两层循环
  16. 合并列表[1,5,7,9]和[2,2,6,8]

    a.extend(b)
  17. 如何打乱一个列表的元素

    import random
    random.shuffle(a)

字典

  1. 字典操作中del和pop有什么区别

    加入有字典变量a,那么操作为del a[key]   a.pop(key)
    del没有返回值,pop有返回值,返回value值
  2. 按照字典的内的年龄排序
    d1 = [

    {'name':'alice','age':38},
    {'name':'bob','age':18},
    {'name':'Carl','age':28}

    ]
    d1.sort(key=lambda x: x[‘age’])

  3. 请合并下面的两个字典a = {‘A’:1,”B”:2},b = {‘C’:3,’D’:4}

    a.update(b)  没有返回值
    c = {**a,**b}  有返回值
  4. 如何使用生成式的方式生成一个字典,写一段功能代码

    {key:value for key,value in d.items()}
  5. 如何把元组(“a”,”b”)和元组(1,2)变为字典{“a”:1,”b”:2}

    dict(zip(t1,t2))

    综合

  6. Python常用的数据结构的类型及其特性

    下列对字典对象的键类型描述不正确的是D,因为字典的键值必须为可hash类型
    列表是可变数据类型,不可hash
    A: {1:0,2:0,3:0}
    B: {"a":0,"b":0,"c":0}
    C: {(1,2):0,(2,3):0}
    D: {[1,2]:0,[2,3]:0}
  7. 如何交换字典{“A”:1,”B”:2}的键和值

    s = {value:key for key,value in s.items()}
    s = dict(zip(s.values,s.keys))
  8. Python里面如何实现tuple和list的转换

    直接强制类型转换,转换成什么就用什么类型
  9. 我们知道对于列表可以使用切片操作进行部分元素的选择,那么如何
    对生成器类型的对象实现相同的功能呢?

    from itertools import islice
    gen = iter(range(10))
    for i in islice(gen,0,4):
        print(i)
  10. 请将[i for i in range(3)]改成生成器

    a = (i for i in range(3))
  11. a=’hello’和b=’你好’编码成bytes类型

    a = b'hello'
    b = bytes("你好",'utf-8')
    c = '你好'.encode('utf-8')
  12. 下面的代码输出结果是什么?

    a = (1,2,3,[4,5,6,7],8)
    a[2] = 2
    print(a)
    报错异常,元组的元素不可变
  13. 下面的代码输出结果是什么?

    a = (1,2,3,[4,5,6,7],8)
    a[3][0] = 2
    print(a)
    里面元素是可变的,所以为a = (1,2,3,[2,5,6,7],8)

    操作类题目

  14. Python中交换两个变量的值

    a, b = b,a
  15. 在读文件操作的时候会使用read、readline或者readlines,简述
    他们各自的作用

    read()每次读取整个文件,通常用于将一个文件读取到一个字符串变量中
    readline()一行一行读取,加载到内存中,对于大文件耗资源
    readlines()将文件的句柄生成一个生成器,读的时候每次生成,省资源,耗时
  16. json序列化时,可以处理的数据类型有哪些,如何定制支持datetime类型

    可以处理:str  int  list  tuple dict  bool  None
    因为datetime类不支持json序列化,所以应该进行拓展
    #自定义时间序列化
    import json
    from datetime import datetime, date
    # JSONEncoder不知道怎么把数据转换成json字符串的时候
    # 它就会调用default()函数,所以处理json不支持的数据类型的时候
    # 都是重写这个函数,default()函数默认是直接抛出异常
    class DateToJson(json.JSONEncoder):
        def default(self, obj):
            if isinstance(obj, datetime):
                return obj.strftime('%Y-%m-%d %H: %M : %S')
            elif isinstance(obj, date):
                return obj.strftime('%Y-%m-%d')
            else:
                return json.JSONEncoder.default(self, obj)
    d = {'name':'csd', 'data': datetime.now()}
    print(json.dumps(d, cls=DateToJson))
  17. json序列化时,默认遇到中文会转换成Unicode,如果想要保留中文怎么办

    通过json.dumps的ensure_ascii参数解决
    import json
    a = json.dumps({'name':'张三'},ensure_ascii=False)
    print(a)
  18. 有两个磁盘文件A和B,各存放一行字母,要求把这两个文件中的信息合并,
    (按字母顺序排列),输出到一个新文件C中。

    with open('A.txt') as f1:
        f1_txt = f1.readline()
    with open('B.txt') as f2:
        f2_txt = f2.readline()
    f3_txt = f1_txt + f2_txt
    f3_list = sorted(f3_txt)
    with open('C.txt','a+') as f:
        f.write("".join(f3_list))
  19. 如果当前日期为20190530,要求写一个函数输出N天后的日期,(比如N为2,
    则输出20190601)

    # datetime库中timedelta方法,参数默认为0,可选
    # datetime.timedelta(days=0,secondes=0,microseconds=0,milliseconde=0,minutes=0,hours=0,weeks=0)
    import datetime
    def datetime_operate(n: int):
        now = datetime.datetime.now()
        _new_date = now + datetime.timedelta(days=n)
        new_date = _new_date.strftime("%Y%m%d")
        return new_date
    if __name__ == "__main__":
        print(datetime_operate(4))
  20. 写一个函数,接收整数参数n,返回一个函数,函数的功能是把函数的参数
    和n相乘并把结果返回

    # 闭包
    def mul_operate(num):
        def g(val):
            return num * val
        return g
    m = mul_operate(8)
    print(m(5))
  21. 下面的代码存在什么问题,如何改进

    def strappend(num):
        str='first'
        for i in range(num):
            str+=str(i)
        return str
    # 函数命名规范问题,str_append,变量名str不合理,不应该使用python
    # 内置的的名称,而且此处不仔细会产生歧义,是将数字强制转换为str类型,
    # 还是str变量的第i个元素而且str是不可变对象,所以每次都会创建新的存储
    # 空间num越大,存储空间越大,内存消耗严重,改为yeild生成器.
    def str_append(num):
        s = 'first'
        for i in range(num):
            s += str(i)
            yield s
    if __name__ == "__main__":
        for i in str_append(3):
            print(i)
  22. 一行代码输出1-100之间的所有偶数

    列表生成式
    a = [i for i in range(101) if i % 2 == 0]
  23. with语句的作用,写一段代码

    # with确保对资源的使用过程不论是否发生异常都会执行必要的清理操作
    # 比如文件的关闭,线程中锁的自动获取和释放等等
    # 一般访问文件资源时,容易犯两个错误,
    # 一忘了关闭文件释放资源,二忘了没有异常处理
    f = open("test.txt",'r')  # 打开
    data = f.read()  # 读取
    f.close  # 关闭
    # 改进写法,虽然避免了异常发生时没有关闭文件,但代码不简洁
    f = open('test.txt','r')
    try:
        data = f.read()
    except e:
        print(e)
    finally:
        f.close()
    # 使用with方法,简洁方便
    with open('test.txt','r') as f:
        f.read()
    # with的实现
    class Test:
        def __enter__(self):
            print('__enter__() is call!')
            return self
        def dosomething(self):
            print('dosomething!')
        def __exit__(self, exc_type, exc_value, traceback):
            print("__exit__() is call!")
            print(f'type:{exc_type}')
            print(f'value:{exc_value}')
            print(f'trace:{traceback}')
            print("__exit()__ is call!")
    with Test() as sample:
        pass
    # 当对象被实例化时,就会主动调用__enter__()方法,任务执行完成后调用
    # __exit__()方法,另外,__exit__()方法是带有三个参数的,如果上下文运
    # 行时没有发生异常,三个参数为None。
  24. Python字典和json字符串相互转换方法

    Python中使用dumps方法将dict对象转换为json对象,使用loads方法将json对象转换为dict对象
    dic = {'a':123, 'b':"456", 'c':"liming"}
    json_str = json.dumps(dic)
    dic2 = json.loads(json_str)
    print(dic2)
    打印出与dic一样的字典对象
    import json
    dic = {'a':123,'b':"456", 'c':"liming"}
    dic_str = json.loads(str(dic).replace("'","\""))
    print(dic_str)
    # 注意dic字典中key值是单引号,当用loads方法时,里面的参数必须是jsons
    # tr类型,而json的标准格式不支持单引号字符串,所以使用replace方法将
    # 单引号替换为双引号,避免出现错误。json.decoder.JSONDecodeError: 
    # Expecting property name enclosed in double quotes
  25. 请写一个Python逻辑,计算一个文件中的大写字母的数量

    with open('test.txt') as f:
        count = 0
        for i in f.read():
            if i.isupper():
                count += 1
    print(count)
  26. 请写一段Python连接Mongo数据库,然后查询的代码

    import pymongo
    db_configs = {
        'type':'mongo',
        'host':'192.168.1.1',
        'port':'2300',
        'user':'bayhax',
        'passwd':'whl19960411,,',
        'db_name':'bayhax'
    }
    class Mongo():
        def __init__(self, db=db_configs['db_name'], username=db_configs['user'], password=db_configs['passwd']):
        self.client = pymongo.MongoClient(f'mongodb://{db_configs["host"]}:db_configs["port"])
        self.username = username
        self.password = password
        if self.username and self.password"
            self.db1 = self.client[db].authenticate(self.username, self.password)
        self.db1 = self.client[db]
        def find_data(self):
            # 获取状态为0的数据
            data = self.db1.test.find({"status":0})
            gen = (item for item in data)
            return gen
        if __name__ == "__main__":
            m = Mongo()
            print(m.find_data())
  27. 说一说Redis的基本类型

    string
    hash
    list
    set
    zset(sorted set 有序集合)
  28. 请写一段Python连接Redis/MySQL数据库的代码

    from redis import StrictRedis, ConnectionPool
    redis_url = "redis://://xxxx@192.168.1.1:6379/15"
    pool = ConnectionPool.from_url(redis_url, decode_response=True)
    r = StrictRedis(connection_pool=poll)
    
    conn = pymysql.connect(host='localhost',port=3306,user='root',
        passwd = '123', db='user', charset = 'utf8mb4')
        cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
        cursor.execute(sql语句)
        conn.close()
  29. 了解Redis的事务吗

    redis事务是一些redis命令的集合,有两个特点
    1.事务是一个单独的隔离操作,事务中的所有命令都会序列化,按顺序的执行。
        事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
    2.事务是一个事务的命令是一个原子操作,事务中的命令要么全部被执行,要么    全部都不执行,一般来说,事务有四个他行ACID,分别为原子性,一致性,     隔离性,持久性。以事务从开始到执行会经历三个阶段:
        开始事务   命令入队   执行事务
    import redis
    import sys
    def run():
        try:
            conn = redis.StrictRedis("192.168.1.1")
            # python中redis事务通过pipeline的封装实现
            pipe = conn.pipeline()
            pipe.sadd("s001",'a')
            sys.exit()
            # 在事务还没有提交前退出,所以事务不会被执行
            pipe.sadd('s001','b')
            pipe.execute()
            pass
        except Exception as err:
            print(err)
            pass
    if __name__ == "__main__":
        run()
  30. 了解数据库的三范式吗

    1NF:强调的是列的原子性,即列不能够再分成其他列
    2NF:在1NF的基础上,必须有主键,包含在主键中的列必须完全依赖于
        主键,不能只依赖于主键的一部分
    3NF:在2NF的基础上,非主键的列必须直接依赖于主键,不能存在传递依赖
        即不能存在:非主键列 A 依赖于非主键列 B,非主键列 B 依赖于主键 
  31. 了解分布式锁吗

    分布式锁是控制分布式系统之间的同步访问共享资源的一种方式。对于分布式锁
    有三点:
    1.任何一个时间点必须只能够有一个客户端拥有锁。
    2.不能够有死锁,也就是最终客户端都能够获得锁,尽管可能会经历失败
    3.错误容忍性要好,只要有大部分的Redis实例存货,客户端就应该能够获得锁
    分布式锁的条件:
    互斥性:分布式锁需要保证在不同节点的不同线程的互斥
    可重入性:同一个节点上的同一个线程如果获取了锁之后,能够再次获取这个锁
    锁超时:支持超时释放锁,防止死锁
    高效,高可用:枷锁和解锁需要高效,同时也需要保证高可用防止分布式锁失效
                可以增加降级
    支持阻塞和非阻塞:可以实现超时获取失败,tryLock(long timeOut)     支持公平锁和非公平锁
    分布式锁的实现方案:
    1.数据库实现(乐观锁)
    2.基于zookeeper的实现
    3.基于Redis的实现(推荐)
  32. 用Python实现一个Redis的分布式锁的功能

    redis分布式锁的实现方式:SETNS + GETSET,NX是Not exists的缩写,
    SETNS命令就应该理解为:SET IF Not eXists.多个进程执行Redis命令
    SETNX lock.foo <current Unix time + lock timeout + 1>
    如果SETNX返回1,说明该进程获得锁,SETNX将键lock.foo的值设置为锁的超时时间(当前时间+锁的有效时间)。如果SETNX返回0,说明其他进程已经获得了锁,进程不能进入临界区。进程可以在一个循环中不断的尝试SETNX操作,以期获得锁
    import time
    import redis
    from conf.config import REDIS_HOST, REDIS_PORT, REDIS_PASSWORD
    class RedisLock:
        def __init__(self):
            self.conn = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, db=1)
            self._lock = 0
            self.lock_key = ''
        @staticmethod
        def my_float(timestamp):
            '''
            Args:
                timestamp:
            Returns:
                float或者0
                如果取出的是None,说明原本锁并没人用,getset已经写入,返回0,可以继续操作。
        '''
        if timestamp:
            return float(timestamp)
        else:
            # 防止取出的值为None,转换float报错
            return 0
    @staticmethod
    def get_lock(cls, key, timeout=10):
        cls.lock_key = f"{key}_dynamic_lock"
        while cls._lock != 1:
            timestamp = time.time() + timeout + 1
            cls._lock = cls.conn.setnx(cls.lock_key, timestamp)
            # if条件中,可能在运行到or之后被释放,也可能在and之后被释放
            # 将导致get到一个None,float失败
            if cls._lock == 1 or (
                            time.time() > cls.my_float(cls.conn.get(cls.lock_key)) and time.time() > cls.my_float(cls.conn.getset(cls.lock_key, timestamp))):
                break
            else:
                time.sleep(0.3)
        @staticmethod
        def release(cls):
            if cls.conn.get(cls.lock_key) and time.time() < cls.conn.get(cls.lock_key):
    def redis_lock_deco(cls):
        def _deco(fun):
            def __deco(*args, **kwargs):
                cls.get_lock(cls, args[1])
                try:
                    return func(*args, **kwargs)
                finally:
                    cls.release(cls)
            return __deco
    @redis_lock_deco(RedisLock())
    def my_func():
        print("myfunc() called.")
        time.sleep(20)
    if __name__ == "__main__":
        my_func()
  33. 写一段Python使用Mongo数据库创建索引的代码

    import pymongo
    db_configs = {
        'type': 'mongo',
        'host': '地址',
        'port': '端口',
        'user': 'spider_data',
        'passwd': '密码',
        'db_name': 'spider_data'
    }
    class Mongo():
        def __init__(self, db=db_configs["db_name"], username=db_configs["user"],
                    password=db_configs["passwd"]):
            self.client = pymongo.MongoClient(f'mongodb://{db_configs["host"]}:{db_configs["port"]}')
            self.username = username
            self.password = password
            if self.username and self.password:
                self.db1 = self.client[db].authenticate(self.username, self.password)
            self.db1 = self.client[db] 
        def add_index(self):
            """
                通过create_index添加索引
            """
            self.db1.test.create_index([('name', pymongo.ASCENDING)], unique=True)
        def get_index(self,):
            """
                查看索引列表
            """
            indexlist=self.db1.test.list_indexes()
            for index in indexlist:
                print(index)
    if __name__ == '__main__':
        m = Mongo()
        m.add_index()
        print(m.get_index())

高级特性

  1. 函数装饰器有什么作用,请列举说明

    装饰器就是一个函数,它可以在不需要做任何代码变动的前提下给一个函数增加额外功能,启动装饰的效果。经常用于有切面需求的场景,比如插入日志,性能测试,事务处理,缓存,全校校验等场景。
    from functools import wraps
    def log(label):
        def decorate(func):
            @wraps(func)
            def _wrap(*args, **kwargs):
                try:
                    func(*args, **kwargs)
                    print("name",func.__name__)
                except Exception as e:
                    print(e.args)
            return decorate
    @log("info")
    def foo(a, b, c):
        print(a+b+c)
        print("in foo")
    if __name__ == "__main__":
        foo(1,2,3)
        # decorate()  # decorate=decorate(foo)
  2. Python垃圾回收机制

    python不像C++,JAVA语言那样,他们不可以不用事先声明变量类型而直接对变量进行复制,对Python语言来讲,对象的类型和内存都是在运行时确定的。这也是Python称为动态语言类型的原因
    1.引用计数机制
    2.标记-清除
    3.分代回收
  3. 魔法函数call怎么使用

    _call_ 可以把实例当做函数调用。
    class Bar:
        def __call__(self, *args, **kwargs):
            print("in call")
    if __name__ == "__main__":
        b = Bar()
        b()
  4. 如何判断一个对象是函数还是方法

    from types import MethodType, FunctionType
    class Bar:
        def foo(self):
            pass
    def foo2():
        pass
    def run():
        print("foo是函数", isinstance(Bar().foo, FunctionType))
        print("foo是方法", isinstance(Bar().foo, MethodType))
        print("foo2是函数", isinstance(foo2, FunctionType))
        print("foo2是函数", isinstance(foo2, MethodType))
  5. @classmethod和@staticmethod用法和区别

    相同:@staticmethod和@classmethod 都可以直接类名.方法名()来调用,不
        用在实例化一个类。@classmethod我们要写一个只在类中运行而不在实例中运行的方法,如果想让方法不在实例中运行,可以这样
        def iget_no_of_instance(ins_obj):
            return ins_obj.__class__.no_inst
        class Kls(object):
            no_inst = 0
            def __init__(self):
                Kls.no_inst = Kls.no_inst + 1
        ik1 = Kls()
        ik2 = Kls()
        print(iget_no_of_instance(ik1))
        @staticmethod经常有一些跟类有关系的功能但在运行时又不需要实例和类
        参与的情况下需要用到静态方法
        IND = 'ON'
        class Kls(object):
            def __init__(self, data):
                self.data = data
            @staticmethod
            def check_ind():
                return (IND == 'ON')
            def do_reset(self):
                if self.check_ind():
                    print("Reset done for ", self.data)
            def set_db(self):
                if self.check_ind():
                    self.db = 'New db connection'
                print('DB connection made for: ', self.data)
    ik1 = Kls(12)
    ik1.do_reset()
    ik1.set_db()
  6. Python中的接口如何实现

    接口提取了一群共同的函数,可以把接口当做一个函数的集合,然后让子类去实现接口中的函数。但是在Python中没有叫做interface的关键字,如果非要去模仿接口的概念,可以使用抽象类来实现,抽象类是一个特殊的类,特殊类只能被继承,不能被实例化。使用abc模块实现抽象类
  7. Python中的反射了解吗

    Python中的反射机制设定较为简单,一共有四个关键函数
    getattr,hasattr,setattr,delattr
  8. metaclass作用,以及应用场景

    metaclass元类,metaclass是类似创建类的模板,所有的类都是通过元类create的(调用new),这使得我们可以自己自由的控制创建类的那个过程,实现所需要的功能。
    可以使用元类创建单例模式和实现ORM模式
  9. hasattr() getattr() setattr()的用法

    hasattr可以判断一个对象是否含有某个属性
    getattr可以充当get获取对象的属性
    setattr可以充当person.name='liming'的赋值操作
    class Person():
        def __init__(self):
            self.name = 'liming'
            self.age = 12
        def show(self):
            print(self.name)
            print(self.age)
        def set_name(self):
            setattr(Person, 'sex', '男')
        def get_name(self):
            print(getattr(self, 'name'))
            print(getattr(self, 'age'))
            print(getattr(self, 'sex'))
    def run():
        if hasattr(Person, 'show'):
            print("判断Person类是否含有show方法")
        Person().set_name()
        Person().get_name()
    if __name__ == "__main__":
        run()
  10. 请列举你知道的Python的魔法方法及用途

    1.__init__:
        类的初始化方法,获取任何传给构造器的参数,比如调用
        x=SomeClass(10,'foo'), __init__就会接收到10 和 'foo'。
        __init__在Python的类定义中用的最多
    2. __new__:
        __new__ 是对象实例化时第一个调用的方法,它只取cls参数,并把其他
        参数传给__init__。__new__很少使用,但当类继承自一个像元组或者字符串这样不经常改变类型的时候会使用
    3. __del__:
        __new__ 和 __init__是对象的构造器,__del__ 是对象的销毁器。
        它并非实现了语句 del x(因此该语句不等同于x.__del__()).而是定义
        了当对象被垃圾回收时的行为。当对象需要在销毁时做一些处理的时候用这个方法很有用,比如socket对象,文件对象。但是需要注意的是,当Python解释器退出但对象仍然存活的时候,__del__并不会执行。所以要养成一个手工清理的好习惯。比如及时关闭连接。
  11. 如何知道一个Python对象的类型

    type(对象名称)
  12. Python的传参是传值还是传址

    两者都不是,传的对象的引用
  13. Python中的元类(metaclass)使用举例

    实现单例模式
    class Singleton(type):
        def __init__(self, *args, **kwargs):
            print("in __init__")
            self.__instance = None
            super(Singleton, self).__init__(*args, **kwargs)
        def __call__(self, *args, **kwargs):
            print("in __call__")
            if self.__instance is None:
                self.__instance = super(Singleton, self).__call__(*args, **kwargs)
                return self.__instance
    class Foo(metaclass=Singleton):
        pass  
        # 代码执行到这里的时候,元类中的__new__方法和__init__方法 
        # 已经被执行了,而不是在FOO实例化的时候执行,而且只执行一次
    foo1 = Foo()
    foo2 = Foo()
    print(foo1 is foo2)
  14. 简述any()和all()方法

    any(x)判断x对象是否为空对象,如果都为空,0,false则返回false,
    如果不都为空,0,false返回true
    all(x)如果all(x)参数x对象的所有元素部位0 '' False或者x为空对象,
    则返回True,否则返回False
  15. filter方法求出列表所有奇数并构造新列表,a = [1,2,3,4,5,6,7,8,9]

    print(list(filter(lambda x : x % 2 == 1, a)))
    现在一般常用列表生成式,不用filter或者map方法
  16. 什么是猴子补丁

    monkey patching:在运行时动态修改模块、类或者函数。通常是添加功能或
        者修正缺陷。猴子补丁在代码运行时内存中)发挥作用,不会修改源码。
        因此只对当前运行的程序实例有效。因为猴子补丁破坏了封装,而且容易
        导致程序与补丁代码的实现细节紧密耦合,所以被视为临时的变通方案,
        不是集成代码的推荐方式
    def post():
        print("this is post")
        print("想不到吧")
    class Http():
        #classmethod
        def get(self):
            print("this is get")
    def main():
        Http.get = post  # 动态的修改了get原因的功能
    if __name__ == "__main__":
        main()
        Http.get()
  17. 在Python中是如何管理内存的

    垃圾回收:
    引用计数:Python采用了类似windows内核对象一样的方式来对内存进行管理,都维护一个对指向该对象的引用的计数。当变量被绑定在一个对象上的时候,该变量引用计数就是1,(还有其他一些情况也会导致变量引用计数的增加),系统会自动维护这些标签,并定时扫描,当某标签的引用计数变为0的时候,该对象就会被回收。
    内存池机制:Python的内存机制以金字塔行,1,2主要有操作系统进行操作
    第0层是C中的malloc,free等内存分配和释放函数进行操作
    第1层和第2层是内存池,由Python的接口函数PyMem_Malloc函数实现,当对象小于256K时由该层直接分配内存,
    第2层是最上层,也就是对Python对象的直接操作
    在C中如果频繁的调用malloc和free时,会产生性能问题,再加上频繁的分配与释放小块的内存会产生内存碎片,Python在这里主要的工作是
    如果请求分配的内存在1--256字节之间就是用自己的内存管理系统,否则直接使用malloc。还是会调用malloc分配内存,但每次回分配一块大小为256K的内存。经由内存池登记的内存到最后还是会回收到内存池,并不会调用C的free释放掉,这样是为了下次使用方便,对于简单的Python对象,例如数值,字符串,元组采用的是复制的方式(深拷贝),也就是说当将另一个变量B赋值给变量A时,虽然A和B的内存空间仍然相同,但是当A的值发生变化时,会重新给A分配空间,A和B的地址变得不再相同。
  18. 当退出Python时是否释放所有内存分配

    不是,循环引用其他对象或引用自全局命名空间的对象的模块,在Python退出时并非完全释放,另外,也不会释放C库保留的内存部分。

    正则表达式

  19. 使用正则表达式匹配出

    百度一下,你就知道

    中的地址
    import re
    source = '<html><h1><font size='3px'>www.baidu.com</font></h1><html>'
    pat = re.compile('<html><h1><font size='3px'>(.*?)</font></h1></html>')
    print(pat.findall(source)[0])

    a=”张明 98分”,用re.sub,将98替换为100

    import re
    a = '张明 98分'
    print(re.sub(r'\d+','100'),s)
  20. 正则表达式匹配中(.*)和(.*?)匹配区别

    (.*)为贪婪匹配模式极可能多的匹配内容
    (.*?)为非贪婪或者懒惰匹配模式,一般匹配到结果就好,匹配字符以少为主,
    import re
    s = '<html><div>文本1</div><div>文本2</div></html>'
    pat1 = re.compile(r'\<div>(.*?)\</div>')
    print(pat1.findall(s))
    pat2 = re.compile(r'\<div>(.*)\</div>')
    print(pat2.findall(s))
  21. 写一段匹配邮箱的正则表达式

    电子邮件地址有统一的标准格式:用户名@服务器域名。用户名表示邮件信箱、注册名或信件接收者的用户标识,@符号后是你使用的邮件服务器的域名。@可以读成“at”,也就是“在”的意思。整个电子邮件地址可理解为网络中某台服务器上的某个用户的地址。
    用户名,可以自己选择。由字母 a~z(不区分大小写)、数字 0~9、点、减号或下划线组成;只能以数字或字母开头和结尾。
    与你使用的网站有关,代表邮箱服务商。例如网易的有@163.com 新浪有@vip.sina.com 等。
    r"^[a-zA-Z0-9]+[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
    下面解释上面的表达式
    首先强调一点关于\w 的含义,\w 匹配英文字母和俄语字母或数字或下划线或汉字。
    注意^[]和[^]的区别,[]表示字符集合,^[]表示已[]内的任意字符集开始,[^]表示。
    ^[a-zA-Z0-9]+:这里注意^[]和[^]的,第一个^表示已什么开头,第二个[]的^表示不等于[]内。所以这段表示以英文字母和数字开头,后面紧跟的+,限定其个数>=1 个。
    [a-zA-Z0-9.+-]+:表示匹配英文字母和数字开头以及.+-, 的任意一个字符,并限定其个数>=1 个。为了考虑@前面可能出现.+-(但是不在开头出现)。
    @就是邮箱必备符号了
    @[a-zA-Z0-9-]+.:前面的不用说了,后面的.表示.转义了,也是必备符号。
    [ a-zA-Z0-9-.]+:$符表示以什么结束,这里表示以英文字和数字或 -. 1 个或多个结尾。
    来个例子验证一波:
    import re
    plt=re.compile(r"^[a-zA-Z0-9]+[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
    b=plt.findall('adas+fefe.we@qq.com.cn')
    print(b)
    网上找了个验证邮件地址的通用正则表达式(符合 RFC 5322 标准)     
    (?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])

    其他内容

  22. 解释一下Python中的pass语句的作用

    占位符,在写一个函数的时候,不确定里面些什么的时候,pass留位置,以后再写。
    def foo():
        pass
  23. 简述你对input()函数的理解

    python3中input函数接收用户输入的字符串,
    python2中有input 和 raw_input两种,input是接收的整型数据
    raw_input接收的是字符串类型
  24. Python中的is和==

    ==判断的是两个对象的值是否相等,is判断的更严格,他判断的是两个对象的地址和值是否完全相同
  25. Python中的作用域

    本地作用于(local)-->当前作用域被嵌入的本地作用域(Enclosing locals)
    \-->全局/模块作用域(global)--->内置作用域(Built-in)
  26. 三元运算写法和应用场景

    a = 2
    b = 5
    #普通写法
    if a > b:
        val = True
    else:
        val = Flase
    # 三元运算符
    val = a if a > b else b
    print(val)
  27. 了解enumerate吗

    enumerate可以在迭代一个对象的时候,同时获取当前对象的索引和值
    from string import ascii_lowercase
    s = ascii_lowercase
    for index, value in enumerate(s):
        print(index, value)
  28. 列举5个Python中的标准模块

    os:系统模块,python访问操作系统的模块
    sys:访问由解释器使用或维护的变量,与解释器进行交互的函数,运行时
    urllib:网络请求模块,包括对url的解析
    pathlib:路径操作模块,比os模块拼接方便
    asyncio:python的异步库,基于事件循环的协程模块
    re:正则表达式模块
    itertools:提供了操作生成器的一些模块
  29. 如何在函数中设置一个全局变量

    global设置
    n = 0
    def foo():
        global n
        n = 100
    foo()
    print(n)
  30. pathlib的用法举例

    pathlib可以对文件以及文件的其他属性进行操作。
  31. Python中的异常处理,写一个简单的应用场景

    除数为0的情况
    try:
        1 / 0
    except ZeroDivisionError as e:
        print(e.args)
  32. Python中递归的最大次数,那么如何突破呢

    默认递归最大层数为1000,可以更改
    import sys
    sys.setrecursionlimit(1500)  # 更改最大层数为1500
    这个只是修改的解释器在解释时允许的最大递归层数,此外,真正限制最大递归层数的是操作系统
  33. 什么是面向对象的mro

    多继承调用的时候应该有个先后顺序,避免重复调用。mro()方法可以获取继承关系,叫做方法解析顺序method resolution order
  34. isinstance作用及应用场景

    isinstance判断一个对象是否为另一个对象的子类。比如bool是int的子类
    print(isinstance(True,int))
  35. 什么是断言,应用场景

    assert,一般在表达式为True的情况下,程序才能通过
    assert()方法,断言成功,程序继续执行,断言失败,程序报错
    可以帮助别人或者自己以后看代码时理解代码
    找出程序逻辑不对的地方,断言会提醒你某个对象应该处于何种状态
    而且,断言为假,会抛出AssertionError异常,可能终止程序
    def foo(a):
        assert a == 2,Exception("不等于2")
        print("ok",a)
    if __name__ == "__main__":
        foo(1)
  36. lambda表达式格式及应用场景

    lambda表达式就是一个匿名函数,在函数编程中经常作为参数使用
    a = [('a',1),('b',2),('c',3),('d',4)]
    a_1 = list(map(lambda x:x[0],a))
  37. 新式类和旧式类的区别

    python2中默认经典类,只有显示继承了object才是新式类,
    python3中默认是新式类,经典类被移除,不必显示的继承object,新式类
    都从object继承,经典类不需要。新式类的MRO基类搜索顺序采用算法广度
    优先搜索,而就是累的MRO采用的是深度优先搜索。新式类相同父类执行一次
    构造函数,经典类重复执行多次
  38. dir()是干什么用的

    当使用某个对象不知道它具体的有哪些属性或者方法时可以使用dir()查看
  39. 一个包里有三个模块,demo1.py demo2.py demo3.py,但是
    使用from tools import * 导入时,如何保证只有demo1 demo3被导入了

    增加_init_.py文件,内容为
    __all__ = ['demo1','demo3']
  40. 列举5个Python中的异常类以及其含义

    AttributeError对象没有这个属性
    NotImplementedError尚未实现的方法
    StopIteration迭代器没有更多的值
    TypeError类型无效
    IndentationError缩进错误
  41. copy和deepcopy的区别是什么

    copy浅拷贝,只拷贝父对象,不拷贝对象内部的子对象
    deepcopy深拷贝,拷贝对象及其内部子对象
  42. 代码中经常遇到的*args, **kwargs含义及用法

    函数定义中*args和**kwargs传递可变参数。
    其中*args用来将参数打包成tuple给函数调用,
    而**kwargs打包关键字参数成dict给函数调用
  43. Python中会有函数或成员变量包含单下划线前缀和结尾
    和双下划线前缀结尾,区别是什么

    单下划线 开始的成员变量叫做保护变量,约定只有类对象和子类对象自己能访问这些变量;
    双下划线 开始的成员变量是私有成员,只有类对象自己能访问,连子类对象也不能访问到;
    以单下划线开头(_foo)表的是不能直接访问的类属性,需要通过提供的接口进行访问,不能用from xxx import *导入,以双下划线(__foo)代表类的私有成员
    以双下划线开头和结尾的代表是Python中他叔方法的标识
    class Person():
        '''docstring for ClassName'''
        def __init__(self):
            self.__age = 12
            self._sex = 'f'
        def _sex"(self):
            return '男'
        def set_age(self,age):
            self.__age = age
        def get_age(self):
            return self.__age
    if __name__ == "__main__":
        p = Person()
        print(p._sex)
        # print(p.__age) 私有成员,访问不成功
        print(p._Person__age)
  44. w a+ wb文件写入模式的区别

    w直接写入,如果文件存在则覆盖
    a+如果文件存在,则会追加,不覆盖
    wb以二进制字节类型写入文件
  45. 举例sort和sorted的区别

    相同点:
        sort 和sorted都可以对列表元素进行排序,
    不同点:
        sort()是在原位重新排列列表,而sorted是产生一个新列表。sort是
        应用在list上的方法,sorted可以对所有可以迭代的对象进行排序操作
        list的sort方法返回的是对已经存在的列表进行操作,而内建函数sorted
        方法返回的是一个新的list,而不是在原来的基础上进行的操作
  46. 什么是负索引

    表示从后面取元素,倒数第一个元素为-1
  47. pprint模块是干什么用的

    pprint是print函数的美化版,可以通过import pprint导入
    import pprint
    pprint.pprint("this is pprint")
    # 会将字符串引号引起来输出
  48. 解释一下Python中的赋值运算符 逻辑运算符

    赋值运算符
    a=7
    a+=1
    print(a)
    a-=1
    print(a)
    a*=2
    print(a)
    a/=2
    print(a)
    a**=2
    print(a)
    a//=3
    print(a)
    a%=4
    print(a)
    逻辑运算符
    print(False and True) #False
    print(7<7 or True) #True
    print(not 2==2) #False
  49. 讲讲python中的位运算符

    按二进制位进行操作
    a = 0011 1100
    b = 0000 1101
    a&b = 0000 1100
    a|b = 0011 1101
    a^b = 0011 0001
    ~a  = 1100 0011
  50. 在Python 中如何使用多进制数字

    二进制数字由 0 和 1 组成,我们使用 0b 或 0B 前缀表示二进制数
    print(int(0b1010))#10
    使用 bin()函数将一个数字转换为它的二进制形式
    print(bin(0xf))#0b1111
    八进制数由数字 0-7 组成,用前缀 0o 或 0O 表示 8 进制数
    print(oct(8))#0o10
    十六进数由数字 0-15 组成,用前缀 0x 或者 0X 表示 16 进制数
    print(hex(16))#0x10
    print(hex(15))#0xf
  51. 怎样声明多个变量并赋值

    a,b,c = 1,2,3

算法和数据结构

  1. 已知
    AList = [1,2,3]
    BSet = {1,2,3}
    从AList和BSet中查找4,最坏时间复杂度哪个大
    从AList和BSet中插入4,最坏时间复杂度哪个大

    查找对于列表和集合来说是一样的,都是O(n)
    而插入,list为O(n),集合是哈希表,所以是O(1)
  2. 用Python实现一个二分查找的函数

    def binary_search(arr, target):
        n = len(arr)
        left = 0
        right = n - 1
        while left <  right:
            mid = (left + right) // 2
            if arr[mid] < target:
                left = mid + 1
            elif arr[mid] > target:
                right = mid - 1
            else:
                print(f"index:{mid},value:{arr[mid]}")
                return True
        return False
    if __name__ == "__main__":
        l = [1,3,4,5,6,7,8]
        binary_search(1,8)
  3. Python单例模式的实现方法

    '''
    new函数实现简单的单例模式
    '''
    class Book:
        def __new__(cls, title):
            if not hasattr(cls, "_ins"):
                cls._ins = super().__new__(cls)
            print('in __new__')
            return cls._ins
        def __init__(self, title):
            print('in __init__')
            super().__init__()
            self.title = title
    if __name__ == "__main__":
        b = Book('The Spider Book')
        b2 = Book('The Flask Book')
        print(id(b))
        print(id(b2))
        print(b.title)
        print(b2.title)
  4. 使用Python实现一个斐波那契数列

    def fibonacci(n):
        a, b = 0, 1
        for i in range(n):
            a, b = b, a+b
            print(a)
    if __name__ == "__main__":
        fibonacci(10)
  5. 找出列表中重复数字

    class Solution:
        def duplicate(self, numbers):
            '''
            :param number:
            :return
            '''
            if numbers is None or len(numbers)<=1:
                return False
            use_set = set()
            duplication = []
            for index, value in enumerate(numbers):
                if value not in use_set:
                    use_set.add(value)
                else:    
                    duplication[index] = value
            return duplication
    if __name__ == "__main__":
        s = Soultion()
        d = s.duplication([1,2,-3,4,4,95,95,2,2,7,6,7])
        print(d)
  6. 找出列表中的单个数字

    def find_single(l : list):
        result = 0
        for v in l:
            result ^= v
        if result == 0:
            print("没有落单元素")
        else:
            print("落单元素", result)
    if __name__ == "__main__":
        l = [1,2,3,4,5,6,2,3,4,5,6]
        find_single(l)
  7. 写一个冒泡排序

    def bubble_sort(arr):
        n = len(arr) - 1
        for i in range(n):
            for j in range(n) - 1 - i:
                if arr[j] > arr[j + 1]:
                    arr[j], arr[j+1] = arr[j+1],arr[j]
        print(arr)
    l = [1,2,32,1,32,1,9,3,2,5]
    bubble_sort(l)
  8. 写一个快速排序

    def quick_sort(arr, first, last):
        if first >= last:
            return
        mid_value = arr[first]
        low = first
        high = last
        while low < high:
            while low < high and arr[high] >= mid_value:
                high -= 1
            arr[low] = arr[high]
            while low < high and arr[low] < mid_value:
                last += 1
            arr[high] = arr[low]
            arr[low] = mid_value
        quick_sort(arr, first, low - 1)
        quick_sort(arr, low + 1, last)
    l = [1,2,3,12,3,2,1,4,6,2,12,121,4]
    quick_sort(l,0,len(l)-1)
    print(l)
  9. 写一个拓扑排序

    """
    拓扑排序
    对拓扑排序。每一个有向无环图都至少存在一种拓扑排序。
    """
    import pysnooper
    from typing import Mapping 
    @pysnooper.snoop()
    def topological_sort(graph: Mapping):
        # in_degrees = {'a': 0, 'b': 0, 'c': 0, 'd': 0, 'e': 0, 'f': 0}
        in_degrees = dict((u, 0) for u in graph)
        for u in graph:
            for v in graph[u]:  # 根据键找出值也就是下级节点
                in_degrees[v] += 1  # 对获取到的下级节点的入度加 1
        # 循环结束之后的结果: {'a': 0, 'b': 1, 'c': 1, 'd': 2, 'e': 1, 'f': 4}
        Q = [u for u in graph if in_degrees[u] == 0]  # 入度为 0 的节点
        in_degrees_zero = []
        while Q:
            u = Q.pop()  # 默认从最后一个移除
            in_degrees_zero.append(u)  # 存储入度为 0 的节点
            for v in graph[u]:
                in_degrees[v] -= 1  # 删除入度为 0 的节点,以及移除其指向
                if in_degrees[v] == 0:
                    Q.append(v)
        return in_degrees_zero
    if __name__ == '__main__':
        # 用字典的键值表示图的节点之间的关系,键当前节点。值是后续节点。
        graph_dict = {
            'a': 'bf',  # 表示 a 指向 b 和 f
            'b': 'cdf',
            'c': 'd',
            'd': 'ef',
            'e': 'f',
            'f': ''
        }
        t = topological_sort(graph_dict)
        print(t)
  10. Python实现一个二进制计算

    def binary_add(a:str,b:str):
        return bin(int(a,2)+int(b,2))[2:]
    num1 = input("first number:")
    num2 = input("second number:")
    binary_add(num1,num2)
  11. 有一组”+”和”-“符号,要求将”+”排到左边,”-“排到右边
    写出具体的实现方法

    from collections import deque
    from timeit import Timer
    s = '+++--+++---'
    def func1():
        new_s = s.replace("+","0").replace("-","-1")
        result = "".join(sorted(new_s)).replace("0","+").replace("-1","-")
        return result
    # 方法二
    def func2():
        q = deque()
        left = q.appendleft
        right = q.append
        for i in s:
            if i == "+":
                left("+")
            elif i == "-":
                right("-")
def func3():
    data = list(s)
    start_index = 0
    end_index = 0
    count = len(s)
    while start_index + end_index < count:
        if data[start_index] == '-':
            data[start_index], data[count - end_index - 1] = data[count - end_index - 1], data[start_index]
            end_index += 1
        else:
            start_index += 1
    return "".join(data)
if __name__ == '__main__':
    timer1 = Timer("func1()", "from __main__ import func1")
    print("func1", timer1.timeit(1000000))
    timer2 = Timer("func2()", "from __main__ import func2")
    print("func2", timer2.timeit(1000000))
    timer3 = Timer("func3()", "from __main__ import func3")
    print("func3", timer3.timeit(1000000))
  1. 单链表反转

    class Node:
        def __init__(self, val=None):
            self.val = val
            self.next = None
    class SingleLinkList:
        def __init__(self, head=None):
            """链表的头部"""
            self._head = head
        def add(self, val: int):
            """
            给链表添加元素
            :param val: 传过来的数字
            :return:
            """
            # 创建一个节点
            node = Node(val)
            if self._head is None:
                self._head = node
            else:
                cur = self._head
                while cur.next is not None:
                    cur = cur.next  # 移动游标
                cur.next = node  # 如果 next 后面没了证明以及到最后一个节点了
        def traversal(self):
            if self._head is None:
                return
            else:
                cur = self._head
                while cur is not None:
                    print(cur.val)
                    cur = cur.next
        def size(self):
            """
            获取链表的大小
            :return:
            """
            count = 0
            if self._head is None:
                return count
            else:
                cur = self._head
                while cur is not None:
                    count += 1
                    cur = cur.next
                return count
        def reverse(self):
            """
            单链表反转
            思路:
            让 cur.next 先断开即指向 none,指向设定 pre 游标指向断开的元素,然后
            cur.next 指向断开的元素,再把开始 self._head 再最后一个元素的时候.
            :return:
            """
            if self._head is None or self.size() == 1:
                return
            else:
                pre = None
                cur = self._head
                while cur is not None:
                    post = cur.next
                    cur.next = pre
                    pre = cur
                    cur = post
                self._head = pre  # 逆向后的头节点     
    if __name__ == '__main__':
        single_link = SingleLinkList()
        single_link.add(3)
        single_link.add(5)
        single_link.add(6)
        single_link.add(7)
        single_link.add(8)
        print("对链表进行遍历")
        single_link.traversal()
        print(f"size:{single_link.size()}")
        print("对链表进行逆向操作之后")
        single_link.reverse()
        single_link.traversal()
  2. 交叉链表求交点

    # Definition for singly-linked list.
    class ListNode:
        def __init__(self, x):
            self.val = x
            self.next = None
    class Solution:
        def getIntersectionNode(self, headA, headB):
            """
            :tye head1, head1: ListNode
            :rtye: ListNode
            """
            if headA is not None and headB is not None:
                cur1, cur2 = headA, headB
                while cur1 != cur2:
                    cur1 = cur1.next if cur1 is not None else headA
                    cur2 = cur2.next if cur2 is not None else headB
                return cur1
    cur1、cur2,2 个指针的初始位置是链表 headA、headB 头结点,cur1、cur2 两个指针一直往后遍历。 直到 cur1 指针走到链表的末尾,然后 cur1 指向 headB; 直到 cur2 指针走到链表的末尾,然后 cur2 指向 headA; 然后再继续遍历; 每次 cur1、cur2 指向 None,则将 cur1、cur2 分别指向 headB、headA。 循环的次数越多,cur1、cur2 的距离越接近,直到 cur1 等于 cur2。则是两个链表的相交点。
  3. 用队列实现栈

    from queue import Queue
    #使用 2 个队列实现
    class MyStack:
        def __init__(self):
            """
            Initialize your data structure here.
            """
            # q1 作为进栈出栈,q2 作为中转站
            self.q1 = Queue()
            self.q2 = Queue()
        def push(self, x):
            """
            Push element x onto stack.
            :type x: int
            :rtype: void
            """
            self.q1.put(x)
        def pop(self):
            """
            Removes the element on top of the stack and returns that element.
            :rtype: int
            """
            while self.q1.qsize() > 1:
                self.q2.put(self.q1.get())  # 将 q1 中除尾元素外的所有元素转到 q2 中
            if self.q1.qsize() == 1:
                res = self.q1.get()  # 弹出 q1 的最后一个元素
                self.q1, self.q2 = self.q2, self.q1  # 交换 q1,q2
                return res
        def top(self):
            """
            Get the top element.
            :rtype: int
            """
            while self.q1.qsize() > 1:
                self.q2.put(self.q1.get())  # 将 q1 中除尾元素外的所有元素转到 q2 中
            if self.q1.qsize() == 1:
                res = self.q1.get()  # 弹出 q1 的最后一个元素
                self.q2.put(res)  # 与 pop 唯一不同的是需要将 q1 最后一个元素保存到 q2 中
                self.q1, self.q2 = self.q2, self.q1  # 交换 q1,q2
                return res
        def empty(self):
            """
            Returns whether the stack is empty.
            :rtype: bool
            """
            return not bool(self.q1.qsize() + self.q2.qsize())  # 为空返回 True,不为空返回 False
    #使用 1 个队列实现
    class MyStack2(object):
        def __init__(self):
            """
            Initialize your data structure here.
            """
            self.sq1 = Queue()
        def push(self, x):
            """
            Push element x onto stack.
            :type x: int
            :rtype: void
            """
            self.sq1.put(x)
        def pop(self):
            """
            Removes the element on top of the stack and returns that element.
            :rtype: int
            """
            count = self.sq1.qsize()
            if count == 0:
                return False
            while count > 1:
                x = self.sq1.get()
                self.sq1.put(x)
                count -= 1
            return self.sq1.get()
        def top(self):
            """
            Get the top element.
            :rtype: int
            """
            count = self.sq1.qsize()
            if count == 0:
                return False
            while count:
                x = self.sq1.get()
                self.sq1.put(x)
                count -= 1
            return x
        def empty(self):
            """
            Returns whether the stack is empty.
            :rtype: bool
            """
            return self.sq1.empty()
    if __name__ == '__main__':
        obj = MyStack2()
        obj.push(1)
        obj.push(3)
        obj.push(4)
        print(obj.pop())
        print(obj.pop())
        print(obj.pop())
        print(obj.empty())
  4. 找出数据流的中位数

    对于一个升序排序的数组,中位数为左半部分的最大值,右半部分的最小值,而左右两部分可以是无需的,只要保证左半部分的数均小于右半部分即可。因此,左右两半部分分别可用最大堆、最小堆实现。
    如果有奇数个数,则中位数放在左半部分;如果有偶数个数,则取左半部分的最大值、右边部分的最小值之平均值。
    分两种情况讨论: 当目前有偶数个数字时,数字先插入最小堆,然后选择最小堆的最小值插入最大堆(第一个数字插入左半部分的最小堆)。
    当目前有奇数个数字时,数字先插入最大堆,然后选择最大堆的最大值插入最小堆。 最大堆:根结点的键值是所有堆结点键值中最大者,且每个结点的值都比其孩子的值大。 最小堆:根结点的键值是所有堆结点键值中最小者,且每个结点的值都比其孩子的值小。
    # -*- coding:utf-8 -*-
    from heapq import *
    class Solution:
        def __init__(self):
            self.maxheap = []
            self.minheap = []
        def Insert(self, num):
            if (len(self.maxheap) + len(self.minheap)) & 0x1:  # 总数为奇数插入最大堆
                if len(self.minheap) > 0:
                    if num > self.minheap[0]:  # 大于最小堆里的元素
                        heappush(self.minheap, num)  # 新数据插入最小堆
                        heappush(self.maxheap, -self.minheap[0])  # 最小堆中的最小插入最大堆
                        heappop(self.minheap)
                    else:
                        heappush(self.maxheap, -num)
                else:
                    heappush(self.maxheap, -num)
            else:  # 总数为偶数 插入最小堆
                if len(self.maxheap) > 0:  # 小于最大堆里的元素
                    if num < -self.maxheap[0]:
                        heappush(self.maxheap, -num)  # 新数据插入最大堆
                        heappush(self.minheap, -self.maxheap[0])  # 最大堆中的最大元素插入最小堆
                        heappop(self.maxheap)
                    else:
                        heappush(self.minheap, num)
                else:
                    heappush(self.minheap, num)
        def GetMedian(self, n=None):
            if (len(self.maxheap) + len(self.minheap)) & 0x1:
                mid = self.minheap[0]
            else:
                mid = (self.minheap[0] - self.maxheap[0]) / 2.0
            return mid
    if __name__ == '__main__':
        s = Solution()
        s.Insert(1)
        s.Insert(2)
        s.Insert(3)
        s.Insert(4)
        print(s.GetMedian())
  5. 二叉搜索树中第K小的元素

    二叉搜索树(Binary Search Tree),又名二叉排序树(Binary Sort Tree)二叉搜索树是具有有以下性质的二叉树:
    若左子树不为空,则左子树上所有节点的值均小于或等于它的根节点的值。
    若右子树不为空,则右子树上所有节点的值均大于或等于它的根节点的值。
    左、右子树也分别为二叉搜索树。
    二叉搜索树按照中序遍历的顺序打印出来正好就是排序好的顺序。所以对其遍历一个节点就进行计数,计数达到 k 的时候就结束。
    class TreeNode:
        def __init__(self, x):
            self.val = x
            self.left = None
            self.right = None
    class Solution:
        count = 0
        nodeVal = 0
        def kthSmallest(self, root, k):
            """
            :type root: TreeNode
            :type k: int
            :rtype: int
            """
            self.dfs(root, k)
            return self.nodeVal
        def dfs(self, node, k):
            if node != None:
                self.dfs(node.left, k)
                self.count = self.count + 1
                if self.count == k:
                    self.nodeVal = node.val
                    # 将该节点的左右子树置为 None,来结束递归,减少时间复杂度
                    node.left = None
                    node.right = None
                self.dfs(node.right, k)

    爬虫相关

  6. 在requests模块中,requestes.content和requestes.text区别

    requests.content获取的是字节,requesets.text获取的文本内容
  7. 简要写一下lxml模块的使用方法框架

    from lxml import html
    source = '''
        <div class="nam"><span>中国</span></div>
        '''
    root = html.fromstring(source)
    _content = root.xpath("string(//div[@class='nam'])")
    if _content and isinstance(_content,list):
        content = _content[0]
    elif isinstance(_content,str):
        content = _content
    print(content)
  8. 说一说scrapy的工作流程

    spider 把百度需要下载的第一个 url:www.baidu.com 交给引擎。
    引擎把 url 交给调度器排序入队处理。
    调度器把处理好的 request 返回给引擎。
    通过引擎调动下载器,按照下载中间件的设置下载这个 request。
    下载器下载完毕结果返回给引擎(如果失败:不好意思,这个 request 下载失败,然后引擎告诉调度器,这个 request 下载失败了,你记录一下,我们待会儿再下载。)
    引擎调度 spider,把按照 Spider 中间件处理过了的请求,交给 spider 处理。
    spider 把处理好的 url 和 item 传给引擎。
    引擎根据不同的类型调度不同的模块,调度 Item Pipeline 处理 item。
    把 url 交给调度器。 然后从第 4 步开始循环,直到获取到你需要的信息,
    注意!只有当调度器中不存在任何 request 了,整个程序才会停止。
  9. scrapy的去重原理

    scrapy本身自带一个去重中间件,scrapy源码中可以找到一个dupefilters.py去重器。里面有个方法叫做request_seen,它在scheduler(发起请求的第一时间)的时候被调用。他代码里面调用了request_fingerprint方法(就是给request生成一个指纹)。给每一个传递过来的url生成一个固定长度的哈希值。但是这种量级千万到亿的级别内存是可以应付的。
  10. scrapy中间件有几种类,你用过哪些中间件

    scrapy的中间件理论上有三种(Schduler Middleware, Spider Middleware,Downloader Middleware)。在应用上一般有以下两种中间件:
    Spider Middleware:主要功能是在爬虫运行过程中进行一些处理。
    下载器中间件 Downloader Middleware:这个中间件可以实现修改User-Agent等headers信息,处理重定向,设置代理,失败重试,设置cookies等功能。
  11. 你写爬虫的时候都遇到了什么问题,反爬虫措施,你是怎么解决的

    Headers:从用户的headers进行反爬是最常见的发爬虫策略。Headers是一种区分浏览器和机器行为中最简单的方法,还有一些网站会对Referer(上级连接)进行检测(机器行为不太可能通过链接跳转实现)从而实现反爬虫。相应的解决措施:通过审查元素或者开发者工具获取相应的headers然后把相应的headers传输给Python的requests,绕过反爬虫措施。
    IP限制:一些网站会根据IP地址访问的频率次数进行反爬。如果单一IP地址访问频率过高,服务器会短时间内禁止这个IP访问。解决措施:构造自己的IP代理池,然后每次访问时随机选择代理(有些ip地址不稳定需要经常检查更新)
    UA限制:UA是用户访问网站时候的浏览器标识,其反爬机制与IP类似。
    解决措施:使用随机UA。
    验证码反爬虫或者模拟登录 验证码:办法古老但相当有效果。如果一个爬虫要解释一个验证码中的内容,这在原来通过图像识别可以实现,但是现在的验证码干扰线,噪点很多,很难识别。解决措施:验证码识别的基本方法:截图,二值化,中值滤波去燥,分割,紧缩重排(高矮同一),字库特征匹配识别。复杂的情况需求接入打码平台。
    Ajax动态加载,网页不希望被爬虫拿到的数据使用Ajax动态加载,这样就位爬虫造成了绝大的麻烦,如果一个爬虫不具备js引擎或者具备js引擎,但是没有处理js返回的方案,或者具备了js引擎但是没办法让站点显示启动脚本设置,ajax动态加载反制爬虫还是相当有效果的。
    Ajax动态加载的工作原理,从网页的URL加载网页的源代码后,会在浏览器里执行JavaScript程序,这些程序会加载出更多的内容,并把这些内容传输到网页中。这就是为什么有些网页直接爬取它的url时却没有数据的原因。
    处理方法:找对应的ajax接口,一般数据返回类型为json
    cookie限制  一次打开网页会生成一个随机的cookie,如果再次打开网页这个cookie不存在,那么久再次设置,第三次打开仍然不存在,就非常可能是爬虫
    解决措施:在headers挂上相应的cookie或者根据其他方法进行构造(例如从中选取几个字母进行构造)。如果过于复杂,可以考虑使用selenium模块(完全模拟浏览器行为)
  12. 为什么会用到代理

    如果使用同一个ip去不断的访问网站的话,会很容易封ip,严重的永久封禁,导致访问不了网站,不只是通过程序,通过浏览器也无法访问。
  13. 代理失效了怎么办

    一般通过大家代理池来实现代理切换等操作,来实现时使用新的代理ip,避免代理失效的问题。
  14. 列出你知道的header的内容以及信息

    User-Agent:它的内容包含发出请求的用户信息。
    Accept:指定客户端能够接受的内容类型
    Accept-Encoding:指定浏览器可以支持的web服务器返回内容压缩编码类型。
    Accept-Language:浏览器可接受的语言。
    Connection:表示是否需要永久链接。
    Content-Length:请求的内容长度。
    If-Modified-Since:如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码。
    Referer:先前网页的地址,当前请求网页紧随其后,即来路。
  15. 说一说打开浏览器访问 百度一下,你就知道 获取到结果的流程

    浏览器向DNS服务器发送baidu.com域名解析请求,DNS服务器返回解析后的ip给客户端浏览器,浏览器向该ip发送页面请求。DNS服务器接收到请求后,查询该页面,并将页面发送给客户端浏览器。客户端浏览器接收到页面后,解析页面中的引用,并再次向服务器发送引用资源请求。服务器收到资源请求后,查找并返回资源给客户端,客户端浏览器接收到资源后渲染,输出页面给客户。
  16. 爬取速度过快出现了验证码怎么处理

    一般在爬取过程中出现了验证码根据不同的情况,处理不一样。如果在一开始访问就有验证码,那么就像办法绕开验证码,比如通过wap端或者app去发现其他接口等,如果不行就得破解验证码了,复杂验证码就需要接入第三方打码平台了。如果开始的时候没有验证码,爬了一段时间出现验证码,可以考虑更新ip,可能是访问频率过高导致的。
  17. scrapy和scrapy-redis有什么区别,为什么选择redis数据库

    scrapy是一个python爬虫框架,爬取效率极高,具有高度定制性,但是不支持分布式。而scrapy-redis一套基于redis数据库,运行在scrapy框架之上的组件,可以让scrapy支持分布式策略,Slaver端共享Master端redis数据库里的item队列,请求队列和请求指纹集合。
    选择redis数据库是因为它支持主从同步,而且数据都是缓存在内存中,所以基于redis的分布式爬虫,对请求和数据的高频读取效率非常高。
  18. 分布式爬虫主要解决什么问题?

    主要为了给爬虫加速。解决了单个ip的限制,宽带的影响。以及CPU的使用情况和io等一系列操作。
  19. 写爬虫用多进程好还是多线程好,为什么

    多线程,因为爬虫是网络操作属于io密集型操作适合多线程或者协程。
  20. 解析网页的解析器使用最多的是哪几个

    lxml,pyquery
  21. 需要登录的网页,如何解决同时限制ip,cookie,session
    (其中有一些是动态生成的),在不使用爬虫爬取的情况下

    解决ip可以搭建代理ip地址池,adsl拨号使用等。
    不适用动态爬取的情况下可以使用反编译JS文件获取相应的文件,或者换用其他平台(比如手机端)看看是否可以获取相应的json文件,一般要学会习惯性的先找需要爬取网站的h5页面,看看有没有提供接口,进而简化操作。
  22. 验证码的解决(简单的:对图像做处理后可以得到,困难的:
    验证码是点击,拖动等动态进行的)

    图形验证码:干扰,杂色不是特别多的图片可以使用开源库Tesseract进行识别,太过复杂就是用第三方打码平台。
    点击或者拖动滑块验证码可以借助selenium,无图形界面浏览器(chromedirver或者phantomjs)和pillow包来模拟人的点击和滑动操作,pillow可以根据色差识别需要滑动的位置。
  23. 使用最多的数据库(mysql,mongodb,redis),对它们的理解

    MySQL数据库:开源免费的关系型数据库,实现创建数据库,数据表和表的字段,表与表之间可以进行关联(一对多,多对多),是持久化存储。
    MongoDB:非关系型数据库,数据库的三元素是数据库,集合,文档,可以进行持久化存储,也可以作为内存数据库,存储数据不需要事先设定格式,数据以键值对形式存储。
    redis:非关系型数据库,使用前可以不用设置格式,以键值对方式保存,文件格式相对自由,主要用与缓存数据库,也可以进行持久化存储。

    网络编程

  24. TCP和UDP的区别

    UDP是面向无连接的通讯协议,UDP数据包括目的端口号和源端口号
    优点:udp速度快,操作简单,要求系统资源较少,由于通讯不需要连接,可以
        实现广播发送。
    缺点:UDP传输数据前并不与对方建立连接,对接收到的数据也不发送确认信号    发送端不知道数据是否会正确接收,也不重复发送,不可靠
    TCP是面向连接的通讯协议,通过三次握手建立,通讯完成时四次挥手。
    优点:TCP在数据传递时,有确认,窗口,重传,阻塞等控制机制,能保证数据
        正确性,较为可靠
    缺点:TCP相对于UDP速度慢一点,要求系统资源较多。
  25. 简要介绍三次握手和四次挥手

    三次握手:
    第一次:主机A发送同步报文段(SYN)请求建立连接。
    第二次:主机B听到连接请求,就将该连接放入内核等待队列中,并向主机A发        送针对SYN的确认ACK,同时主机B也发送自己的请求建立连接SYN
    第三次:主机A针对主机B的SYN进行确认应答ACK
    四次挥手:
    第一次:当主机A发送数据完毕后,发送FIN结束报文段
    第二次:主机B收到FIN报文字段后,向主机A发送一个确认序号ACK(为了防止
            这段时间内,对方重传FIN报文段)
    第三次:主机B准备关闭连接,向主机A发送一个FIN结束报文段
    第四次:主机A收到FIN结束报文段,进入TIME_WAIT状态,并向主机B发送一个
            ACK表示连接彻底释放
    二三次挥手不能合在一起的原因是:虽然A不再发送数据了,但是还可以接受数据,这个时段不否定B可能会发送数据给A,所以要先发送确认ACK,再发送FIN,不然合在一块的话,可能B有数据发送不过去。还得重新再建立连接,浪费。
  26. 什么是粘包,socket中造成粘包的原因,哪些情况会出现粘包

    TCP是流式协议,只有字节流,流式没有边界的,根部就不存在粘包一说,一般粘包都是业务上没处理好造成的。但是在描述这个现象的时候,可能还得说粘包。TCP粘包通俗来讲,就是发送方发送的多个数据包,到接收方后粘在一起,导致数据包不能完整的体现发送的数据。导致粘包的原因,可能是发送方也可能是接收方的原因。
    发送方由于TCP需要尽可能的高效和可靠,所以TCP默认采用Nagle算法,以合并相连的小数据包,再一次性发送,以达到提升网络传输效率的目的。但是接收方并不知晓发送方合并的数据包,而且数据包的合并在TCP协议中是没有分界线的,所以这就会导致接收方不能还原本来的数据包。
    接收方TCP是基于“流”的。网络传数据的速度可能会快过接收方处理数据的速度,这时就会导致,接收方在读取缓冲区的时候,缓冲区存在多个数据包,在TCP协议中接收方是一次读取缓冲区中的所有内容,所以不能反映本来的数据信息
    解决方案:
    发送定长包,如果每个消息的大小都是一样的,那么在接收对等方只要累计接收数据,直到数据等于一个定长的数值就将它作为一个消息
    包尾加上\r\n标记,FTP协议正是这么做的,但问题在于如果数据正文中也含有\r\n,则会误判为消息的边界。
    包头加上包体长度,包头定长4字节,说明了包体的长度。接收对等方现接受包体长度,依据包体长度来接收包体。

    并发

  27. 举例说明conccurent.future的中线程池的用法

    from conccurent.future import ThreadPoolExecutor
    from requests
    URLS = ['http://www.163.com','https://www.baidu.com/','https://github.com']
    def load_url(url):
        req = requests.get(url, timeout=60)
        print(f'{url} page is {len(req.content)} bytes')
    with ThreadPoolExecute(max_workers=3) as pool:
        pool.map(load_url, URLS)
    print('主线程结束')
  28. 说一说多线程、多进程和协程的区别

    进程:进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,
        进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内空间,不同进程通过进程间通信来通信。由于进程比较重要,占据独立的内存,所以上下文进程间的切换开销(栈,寄存器,虚拟内存,文件句柄等)比较大,但相对比较稳定安全。
    线程:线程时进程的一个实体,是CPU调度和分派的基本单位,它比进程更小的
        能独立运行的基本单位,线程自己基本上不拥有系统资源,只拥有一点在运行中不可缺少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要是通过共享内存,上下文切换很快,资源开销小,但比进程不够稳定容易丢失数据。
    协程:协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥
        自己的寄存器上下文和栈。协程调度时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复之前先保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文切换非常快。
    区别:
        进程与线程比较:线程是指进程内的一个执行单元,也是进程内的可调度实体。线程与进程的区别:
        1.地址空间:线程时进程内的一个执行单元,进程内至少有一个线程,他们共享进程的地址空间,而进程有自己独立的地址空间。
        2.资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源你。
        3.线程时处理器调度的基本单位,但进程不是。
        4.二者均可并发执行。
        5.每个独立的线程有一个程序运行的入口,顺序执行序列和程序的出口。
        但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供过个线程执行控制
    协程与线程比较:
        1.一个线程可以多个协程,一个进程也可以单独拥有多个协程,这样python则能使用多核CPU。
        2.线程进程都是同步机制,而协程是异步
        3.协程能保留上一次调用时的状态,每次过程重入时,就相当于上一次调用的状态。
  29. 简述GIL

    GIL:全局解释器锁。每个线程在执行的过程中都需要先获取GIL,保证同一时刻只有一个线程可以执行代码。
    线程释放GIL锁的情况:在IO操作可能等可能会引起阻塞的systemcall之前,可以暂时释放GIL,但是执行完毕后,必须重新获取GIL,Python3中使用计时器(执行时间到达阈值后,当前线程释放GIL)或Python2中tickets计数达到100。Python使用多进程是可以利用多核CPU资源的。
    多线程爬取比单线程性能有提升,因为遇到IO阻塞会自动释放GIL锁。
  30. 进程之间如何通信

    可以通过队列的形式:
    from multiprocessing import Queue, Process
    import time, random
    # 要写入的数据
    list1 = ['java','python','javascript']
    def write(queue):
        '''
        向队列中添加数据
        :param queue:
        :return:
        '''
        for value in list1:
            print(f"正在想队列中添加数据--->{value}")
            # put_nowait 不会等待队列有空闲位置再放入数据,如果数据放入#不成功就直接崩溃,比如数据满了,put的话就会一直等待
            queue.put_nowait(value)
            time.sleep(random.random())
    def read(queue):
        while True:
            # 判断队列是否为空
            if not queue.empty():
                # get_nowait #队列为空,取值的时候不等待,但是取不到值那么直接崩溃了
                value = queue.get_nowait()
                print(f"从队列中渠道的数据为--->{value}")
                time.sleep(random.random())
            else:
                break
    if __name__ == "__main__":
        # 父进程创建出队列,通过参数的形式传递给子进程
        # queue = Queue(2)
        queue = Queue()
        # 创建两个进程,一个写数据 一个读数据
        write_data = Process(target=write, args=(queue,))
        read_data = Process(target=read,args=(queue,))
        # 启动进程,写入数据
        write_data.start()
        # 使用join等待写入数据结束
        write_data.join()
        # 启动进程,读取数据
        print("*"*20)
        read_data.start()
        # 使用join等待读取数据结束
        read_data.join()
        print("所有数据都写入并读取完成")
  31. IO多路复用的作用

    阻塞I/O只能阻塞一个I/O操作,而I/O复用模型能够阻塞多个I/O
    操作,所以才叫做多路复用。I/O多路复用适用于提升效率,
    单个进程可以同时监听多个网络连接IO,在IO密集型的系统中,相对于线程切换的开销问题,IO多路复用可以极大的提升系统效率。
  32. select、poll、epoll模型的区别

    select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制
    可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
    select模型:select目前几乎所有的平台上都支持,其良好的跨平台支持也是他的一个优点。select的一个缺点在于单个单个进程能够监视的文件描述符的数量存在最大限制,在Linux一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但也会造成效率的降低。
    poll模型:poll和select的实现非常类似,本质上的区别就是存档fd集合的数据结构不一样。select在一个进程内可以维持最多1024个连接,poll在此基础上做个加强,可以维持任意数量的连接。但是select和poll方式有一个很大的问题就是,我们不难看出select是通过轮训的方式查找是否可读或者可写,比如同时拥有100万个连接都没有断开,而只有一个客户端发送了数据,所以这里他还是需要循环这么多次,造成资源浪费。所以后来出现了epoll系统调用。
    epoll模型:epoll是select和poll的增强版,epoll同poll一样,文件描述符数量无限制。但是也并不是多有情况下,epoll都比select/poll好,比如在大多数客户端都很活跃的情况下,系统会把所有的回调函数都唤醒,所以会导致负载过高。既然要处理这么多的连接,那倒不如select遍历简单有效。
  33. 什么是并发和并行

    并行是指同一时刻同时做很多件事情,并发是同一时间间隔内做多件事情。
    并发与并行两个既相似又不同的概念:并发性:又称共行性,是指能处理多个同时性活动的能力;并行是指同时发生的两个并发事件,具有并发的含义,而并发则不一定并行,也亦是说并发事件之间不一定在同一时刻发生。并发的实质是一个物理CPU(也可以多个物理CPU)在若干道程序之间多路复用,并发性是对有限的物理资源强制行使多用户共享提高效率。
    并行性指两个或者两个以上事件或活动在同一时刻发生。在多到程序环境下,并行性使多个程序同一时刻可在不同CPU上同时执行。并行,是每个CPU运行一个程序。
  34. 一个线程1让线程2去调用一个函数怎么实现

    import threading
    def func1(t2):
        print('正在执行函数func1')
        t2.start()
    def func2():
        print("正在执行函数func2")
    if __name__ == "__main__":
        t2 = threading.Thread(target=func2)
        t1 = threading.Thread(target=func1,args=(t2,))
        t1.start()
  35. 解释什么是异步非阻塞

    异步,异步与同步相对,当一个异步过程调用发出后,调用者在没有得到结果之前,皆可以继续执行后续操作。当这个调用完成后,一般通过状态、通知和回调来通知调用者。对于异步调用,调用的返回并不受调用者控制。
    非阻塞,非阻塞是这样定义的,当线程遇到I/O操作时,不会以阻塞的方式等待
    I/O操作的完成或数据的返回,而只是将I/O请求发送给操作系统,继续执行
    下一条语句。当操作系统完成I/O操作时,以事件的形式通知执行I/O操作的
    线程,线程会在特定时候处理这个事件。简单理解就是程序不会卡住,可以继续执行。
  36. threading.local的作用

    threading.local()这个方法是用来保存一个全局变量,但是这个全局变量只有在当前线程才能访问,如果你在开发多线程应用的时候,需要每个线程保存一个单独的数据供当前线程操作,可以使用这个方法。
    import threading
    import time
    a = threading.local()  # 全局对象
    def worker():
        a.x = 0
        for i in range(200):
            time.sleep(0.01)
            a.x += 1
        print(threading.current_thread(),a.x)
    for i in range(20):
        threading.Thread(target=worker).start()

    Git面试题

  37. 说说你知道的Git命令

    git init:创建一个名为.git的子目录,这个子目录含有初始化的Git仓库中所有的必要文件,是Git仓库的骨干
    git clone url:将服务器代码下载到本地
    git pull:将服务器代码拉到本地进行同步,如果本地有修改会产生冲突
    git push:提交本地修改的代码到服务器
    git checkout -b branch:创建并切换分支
    git status:查看修改状态
    git add 文件名:提交到暂存区
    git commit -m “提交内容”:输入提交的注释内容
    git log:查看提交的日志情况
  38. git如何查看某次提交修改的内容

    首先可以git log显示历史的提交列表,之后用git show可以显示某次提交的
    修改内容,同样git show filename可以显示某次提交的某个内容的修改信息