当前位置: 首页 > news >正文

wordpress注册邮箱设置包头网站建设优化

wordpress注册邮箱设置,包头网站建设优化,微信小程序开发者工具怎么使用,快三网站开发题目001: 在Python中如何实现单例模式。 点评#xff1a;单例模式是指让一个类只能创建出唯一的实例#xff0c;这个题目在面试中出现的频率极高#xff0c;因为它考察的不仅仅是单例模式#xff0c;更是对Python语言到底掌握到何种程度#xff0c;建议大家用装饰器和元类…题目001: 在Python中如何实现单例模式。 点评单例模式是指让一个类只能创建出唯一的实例这个题目在面试中出现的频率极高因为它考察的不仅仅是单例模式更是对Python语言到底掌握到何种程度建议大家用装饰器和元类这两种方式来实现单例模式因为这两种方式的通用性最强而且也可以顺便展示自己对装饰器和元类中两个关键知识点的理解。 方法一使用装饰器实现单例模式。 from functools import wrapsdef singleton(cls):单例类装饰器instances {}wraps(cls)def wrapper(*args, **kwargs):if cls not in instances:instances[cls] cls(*args, **kwargs)return instances[cls]return wrappersingleton class President:pass 扩展装饰器是Python中非常有特色的语法用一个函数去装饰另一个函数或类为其添加额外的能力。通常通过装饰来实现的功能都属横切关注功能也就是跟正常的业务逻辑没有必然联系可以动态添加或移除的功能。装饰器可以为代码提供缓存、代理、上下文环境等服务它是对设计模式中代理模式的践行。在写装饰器的时候带装饰功能的函数上面代码中的wrapper函数通常都会用functools模块中的wraps再加以装饰这个装饰器最重要的作用是给被装饰的类或函数动态添加一个__wrapped__属性这个属性会将被装饰之前的类或函数保留下来这样在我们不需要装饰功能的时候可以通过它来取消装饰器例如可以使用President President.__wrapped__来取消对President类做的单例处理。需要提醒大家的是上面的单例并不是线程安全的如果要做到线程安全需要对创建对象的代码进行加锁的处理。在Python中可以使用threading模块的RLock对象来提供锁可以使用锁对象的acquire和release方法来实现加锁和解锁的操作。当然更为简便的做法是使用锁对象的with上下文语法来进行隐式的加锁和解锁操作。 方法二使用元类实现单例模式。 class SingletonMeta(type):自定义单例元类def __init__(cls, *args, **kwargs):cls.__instance Nonesuper().__init__(*args, **kwargs)def __call__(cls, *args, **kwargs):if cls.__instance is None:cls.__instance super().__call__(*args, **kwargs)return cls.__instanceclass President(metaclassSingletonMeta):pass 扩展Python是面向对象的编程语言在面向对象的世界中一切皆为对象。对象是通过类来创建的而类本身也是对象类这样的对象是通过元类来创建的。我们在定义类时如果没有给一个类指定父类那么默认的父类是object如果没有给一个类指定元类那么默认的元类是type。通过自定义的元类我们可以改变一个类默认的行为就如同上面的代码中我们通过元类的__call__魔术方法改变了President类的构造器那样。 补充关于单例模式在面试中还有可能被问到它的应用场景。通常一个对象的状态是被其他对象共享的就可以将其设计为单例例如项目中使用的数据库连接池对象和配置对象通常都是单例这样才能保证所有地方获取到的数据库连接和配置信息是完全一致的而且由于对象只有唯一的实例因此从根本上避免了重复创建对象造成的时间和空间上的开销也避免了对资源的多重占用。再举个例子项目中的日志操作通常也会使用单例模式这是因为共享的日志文件一直处于打开状态只能有一个实例去操作它否则在写入日志的时候会产生混乱。 题目002不使用中间变量交换两个变量a和b的值。 点评典型的送人头的题目通常交换两个变量需要借助一个中间变量如果不允许使用中间变量在其他编程语言中可以使用异或运算的方式来实现交换两个变量的值但是Python中有更为简单明了的做法。 方法一 a a ^ b b a ^ b a a ^ b 方法二 a, b b, a 扩展需要注意a, b b, a这种做法其实并不是元组解包虽然很多人都这样认为。Python字节码指令中有ROT_TWO指令来支持这个操作类似的还有ROT_THREE对于3个以上的元素如a, b, c, d b, c, d, a才会用到创建元组和元组解包。想知道你的代码对应的字节码指令可以使用Python标准库中dis模块的dis函数来反汇编你的Python代码。 题目003写一个删除列表中重复元素的函数要求去重后元素相对位置保持不变。 点评这个题目在初中级Python岗位面试的时候经常出现题目源于《Python Cookbook》这本书第一章的第10个问题有很多面试题其实都是这本书上的原题所以建议大家有时间好好研读一下这本书。 def dedup(items):no_dup_items []seen set()for item in items:if item not in seen:no_dup_items.append(item)seen.add(item)return no_dup_items 如果愿意也可以把上面的函数改造成一个生成器代码如下所示。 def dedup(items):seen set()for item in items:if item not in seen:yield itemseen.add(item) 扩展由于Python中的集合底层使用哈希存储所以集合的in和not in成员运算在性能上远远优于列表所以上面的代码我们使用了集合来保存已经出现过的元素。集合中的元素必须是hashable对象因此上面的代码在列表元素不是hashable对象时会失效要解决这个问题可以给函数增加一个参数该参数可以设计为返回哈希码或hashable对象的函数。 题目004假设你使用的是官方的CPython说出下面代码的运行结果。 点评下面的程序对实际开发并没有什么意义但却是CPython中的一个大坑这道题旨在考察面试者对官方的Python解释器到底了解到什么程度。 a, b, c, d 1, 1, 1000, 1000 print(a is b, c is d)def foo():e 1000f 1000print(e is f, e is d)g 1print(g is a)foo() 运行结果 True False True False True上面代码中a is b的结果是True但c is d的结果是False这一点的确让人费解。CPython解释器出于性能优化的考虑把频繁使用的整数对象用一个叫small_ints的对象池缓存起来造成的。small_ints缓存的整数值被设定为[-5, 256]这个区间也就是说在任何引用这些整数的地方都不需要重新创建int对象而是直接引用缓存池中的对象。如果整数不在该范围内那么即便两个整数的值相同它们也是不同的对象。 CPython底层为了进一步提升性能还做了另一个设定对于同一个代码块中值不在small_ints缓存范围内的整数如果同一个代码块中已经存在一个值与其相同的整数对象那么就直接引用该对象否则创建新的int对象。需要大家注意的是这条规则对数值型适用但对字符串则需要考虑字符串的长度这一点大家可以自行证明。 扩展如果你用PyPy另一种Python解释器实现支持JIT对CPython的缺点进行了改良在性能上优于CPython但对三方库的支持略差来运行上面的代码你会发现所有的输出都是True。 题目005Lambda函数是什么举例说明的它的应用场景。 点评这个题目主要想考察的是Lambda函数的应用场景潜台词是问你在项目中有没有使用过Lambda函数具体在什么场景下会用到Lambda函数借此来判断你写代码的能力。因为Lambda函数通常用在高阶函数中主要的作用是通过向函数传入函数或让函数返回函数最终实现代码的解耦合。 Lambda函数也叫匿名函数它是功能简单用一行代码就能实现的小型函数。Python中的Lambda函数只能写一个表达式这个表达式的执行结果就是函数的返回值不用写return关键字。Lambda函数因为没有名字所以也不会跟其他函数发生命名冲突的问题。 扩展面试的时候有可能还会考你用Lambda函数来实现一些功能也就是用一行代码来实现题目要求的功能例如用一行代码实现求阶乘的函数用一行代码实现求最大公约数的函数等。 fac lambda x: __import__(functools).reduce(int.__mul__, range(1, x 1), 1) gcd lambda x, y: y % x and gcd(y % x, x) or x Lambda函数其实最为主要的用途是把一个函数传入另一个高阶函数如Python内置的filter、map等中来为函数做解耦合增强函数的灵活性和通用性。下面的例子通过使用filter和map函数实现了从列表中筛选出奇数并求平方构成新列表的操作因为用到了高阶函数过滤和映射数据的规则都是函数的调用者通过另外一个函数传入的因此这filter和map函数没有跟特定的过滤和映射数据的规则耦合在一起。 items [12, 5, 7, 10, 8, 19] items list(map(lambda x: x ** 2, filter(lambda x: x % 2, items))) print(items) # [25, 49, 361] 扩展用列表的生成式来实现上面的代码会更加简单明了代码如下所示。 items [12, 5, 7, 10, 8, 19] items [x ** 2 for x in items if x % 2] print(items) # [25, 49, 361] 题目006说说Python中的浅拷贝和深拷贝。 点评这个题目本身出现的频率非常高但是就题论题而言没有什么技术含量。对于这种面试题在回答的时候一定要让你的答案能够超出面试官的预期这样才能获得更好的印象分。所以回答这个题目的要点不仅仅是能够说出浅拷贝和深拷贝的区别深拷贝的时候可能遇到的两大问题还要说出Python标准库对浅拷贝和深拷贝的支持然后可以说说列表、字典如何实现拷贝操作以及如何通过序列化和反序列的方式实现深拷贝最后还可以提到设计模式中的原型模式以及它在项目中的应用。 浅拷贝通常只复制对象本身而深拷贝不仅会复制对象还会递归的复制对象所关联的对象。深拷贝可能会遇到两个问题一是一个对象如果直接或间接的引用了自身会导致无休止的递归拷贝二是深拷贝可能对原本设计为多个对象共享的数据也进行拷贝。Python通过copy模块中的copy和deepcopy函数来实现浅拷贝和深拷贝操作其中deepcopy可以通过memo字典来保存已经拷贝过的对象从而避免刚才所说的自引用递归问题此外可以通过copyreg模块的pickle函数来定制指定类型对象的拷贝行为。 deepcopy函数的本质其实就是对象的一次序列化和一次返回序列化面试题中还考过用自定义函数实现对象的深拷贝操作显然我们可以使用pickle模块的dumps和loads来做到代码如下所示。 import picklemy_deep_copy lambda obj: pickle.loads(pickle.dumps(obj)) 列表的切片操作[:]相当于实现了列表对象的浅拷贝而字典的copy方法可以实现字典对象的浅拷贝。对象拷贝其实是更为快捷的创建对象的方式。在Python中通过构造器创建对象属于两阶段构造首先是分配内存空间然后是初始化。在创建对象时我们也可以基于“原型”对象来创建新对象通过对原型对象的拷贝复制内存就完成了对象的创建和初始化这种做法更加高效这也就是设计模式中的原型模式。在Python中我们可以通过元类的方式来实现原型模式代码如下所示。 import copyclass PrototypeMeta(type):实现原型模式的元类def __init__(cls, *args, **kwargs):super().__init__(*args, **kwargs)# 为对象绑定clone方法来实现对象拷贝cls.clone lambda self, is_deepTrue: \copy.deepcopy(self) if is_deep else copy.copy(self)class Person(metaclassPrototypeMeta):passp1 Person() p2 p1.clone() # 深拷贝 p3 p1.clone(is_deepFalse) # 浅拷贝 题目007Python是如何实现内存管理的 点评当面试官问到这个问题的时候一个展示自己的机会就摆在面前了。你要先反问面试官“你说的是官方的CPython解释器吗”。这个反问可以展示出你了解过Python解释器的不同的实现版本而且你也知道面试官想问的是CPython。当然很多面试官对不同的Python解释器底层实现到底有什么差别也没有概念。所以千万不要觉得面试官一定比你强怀揣着这份自信可以让你更好的完成面试。 Python提供了自动化的内存管理也就是说内存空间的分配与释放都是由Python解释器在运行时自动进行的自动管理内存功能极大的减轻程序员的工作负担也能够帮助程序员在一定程度上解决内存泄露的问题。以CPython解释器为例它的内存管理有三个关键点引用计数、标记清理、分代收集。 引用计数对于CPython解释器来说Python中的每一个对象其实就是PyObject结构体它的内部有一个名为ob_refcnt 的引用计数器成员变量。程序在运行的过程中ob_refcnt的值会被更新并藉此来反映引用有多少个变量引用到该对象。当对象的引用计数值为0时它的内存就会被释放掉。 typedef struct _object {_PyObject_HEAD_EXTRAPy_ssize_t ob_refcnt;struct _typeobject *ob_type; } PyObject; 以下情况会导致引用计数加1 对象被创建对象被引用对象作为参数传入到一个函数中对象作为元素存储到一个容器中 以下情况会导致引用计数减1 用del语句显示删除对象引用对象引用被重新赋值其他对象一个对象离开它所在的作用域持有该对象的容器自身被销毁持有该对象的容器删除该对象 可以通过sys模块的getrefcount函数来获得对象的引用计数。引用计数的内存管理方式在遇到循环引用的时候就会出现致命伤因此需要其他的垃圾回收算法对其进行补充。 标记清理CPython使用了“标记-清理”Mark and Sweep算法解决容器类型可能产生的循环引用问题。该算法在垃圾回收时分为两个阶段标记阶段遍历所有的对象如果对象是可达的被其他对象引用那么就标记该对象为可达清除阶段再次遍历对象如果发现某个对象没有标记为可达则就将其回收。CPython底层维护了两个双端链表一个链表存放着需要被扫描的容器对象姑且称之为链表A另一个链表存放着临时不可达对象姑且称之为链表B。为了实现“标记-清理”算法链表中的每个节点除了有记录当前引用计数的ref_count变量外还有一个gc_ref变量这个gc_ref是ref_count的一个副本所以初始值为ref_count的大小。执行垃圾回收时首先遍历链表A中的节点并且将当前对象所引用的所有对象的gc_ref减1这一步主要作用是解除循环引用对引用计数的影响。再次遍历链表A中的节点如果节点的gc_ref值为0那么这个对象就被标记为“暂时不可达”GC_TENTATIVELY_UNREACHABLE并被移动到链表B中如果节点的gc_ref不为0那么这个对象就会被标记为“可达“GC_REACHABLE对于”可达“对象还要递归的将该节点可以到达的节点标记为”可达“链表B中被标记为”可达“的节点要重新放回到链表A中。在两次遍历之后链表B中的节点就是需要释放内存的节点。 分代回收在循环引用对象的回收中整个应用程序会被暂停为了减少应用程序暂停的时间Python 通过分代回收空间换时间的方法提高垃圾回收效率。分代回收的基本思想是对象存在的时间越长是垃圾的可能性就越小应该尽量不对这样的对象进行垃圾回收。CPython将对象分为三种世代分别记为0、1、2每一个新生对象都在第0代中如果该对象在一轮垃圾回收扫描中存活下来那么它将被移到第1代中存在于第1代的对象将较少的被垃圾回收扫描到如果在对第1代进行垃圾回收扫描时这个对象又存活下来那么它将被移至第2代中在那里它被垃圾回收扫描的次数将会更少。分代回收扫描的门限值可以通过gc模块的get_threshold函数来获得该函数返回一个三元组分别表示多少次内存分配操作后会执行0代垃圾回收多少次0代垃圾回收后会执行1代垃圾回收多少次1代垃圾回收后会执行2代垃圾回收。需要说明的是如果执行一次2代垃圾回收那么比它年轻的代都要执行垃圾回收。如果想修改这几个门限值可以通过gc模块的set_threshold函数来做到。 题目008说一下你对Python中迭代器和生成器的理解。 点评很多人面试者都会写迭代器和生成器但是却无法准确的解释什么是迭代器和生成器。如果你也有同样的困惑可以参考下面的回答。 迭代器是实现了迭代器协议的对象。跟其他编程语言不通Python中没有用于定义协议或表示约定的关键字像interface、protocol这些单词并不在Python语言的关键字列表中。Python语言通过魔法方法来表示约定也就是我们所说的协议而__next__和__iter__这两个魔法方法就代表了迭代器协议。可以通过for-in循环从迭代器对象中取出值也可以使用next函数取出迭代器对象中的下一个值。生成器是迭代器的语法升级版本可以用更为简单的代码来实现一个迭代器。 扩展面试中经常让写生成斐波那契数列的迭代器大家可以参考下面的代码。 class Fib(object):def __init__(self, num):self.num numself.a, self.b 0, 1self.idx 0def __iter__(self):return selfdef __next__(self):if self.idx self.num:self.a, self.b self.b, self.a self.bself.idx 1return self.araise StopIteration() 如果用生成器的语法来改写上面的代码代码会简单优雅很多。 def fib(num):a, b 0, 1for _ in range(num):a, b b, a byield a 题目009正则表达式的match方法和search方法有什么区别 点评正则表达式是字符串处理的重要工具所以也是面试中经常考察的知识点。在Python中使用正则表达式有两种方式一种是直接调用re模块中的函数传入正则表达式和需要处理的字符串一种是先通过re模块的compile函数创建正则表达式对象然后再通过对象调用方法并传入需要处理的字符串。如果一个正则表达式被频繁的使用我们推荐用re.compile函数创建正则表达式对象这样会减少频繁编译同一个正则表达式所造成的开销。 match方法是从字符串的起始位置进行正则表达式匹配返回Match对象或None。search方法会扫描整个字符串来找寻匹配的模式同样也是返回Match对象或None。 题目010下面这段代码的执行结果是什么。 def multiply():return [lambda x: i * x for i in range(4)]print([m(100) for m in multiply()]) 运行结果 [300, 300, 300, 300]上面代码的运行结果很容易被误判为[0, 100, 200, 300]。首先需要注意的是multiply函数用生成式语法返回了一个列表列表中保存了4个Lambda函数这4个Lambda函数会返回传入的参数乘以i的结果。需要注意的是这里有闭包closure现象multiply函数中的局部变量i的生命周期被延展了由于i最终的值是3所以通过m(100)调列表中的Lambda函数时会返回300而且4个调用都是如此。 如果想得到[0, 100, 200, 300]这个结果可以按照下面几种方式来修改multiply函数。 方法一使用生成器让函数获得i的当前值。 def multiply():return (lambda x: i * x for i in range(4))print([m(100) for m in multiply()]) 或者 def multiply():for i in range(4):yield lambda x: x * iprint([m(100) for m in multiply()]) 方法二使用偏函数彻底避开闭包。 from functools import partial from operator import __mul__def multiply():return [partial(__mul__, i) for i in range(4)]print([m(100) for m in multiply()]) 题目011Python中为什么没有函数重载 点评C、Java、C#等诸多编程语言都支持函数重载所谓函数重载指的是在同一个作用域中有多个同名函数它们拥有不同的参数列表参数个数不同或参数类型不同或二者皆不同可以相互区分。重载也是一种多态性因为通常是在编译时通过参数的个数和类型来确定到底调用哪个重载函数所以也被称为编译时多态性或者叫前绑定。这个问题的潜台词其实是问面试者是否有其他编程语言的经验是否理解Python是动态类型语言是否知道Python中函数的可变参数、关键字参数这些概念。 首先Python是解释型语言函数重载现象通常出现在编译型语言中。其次Python是动态类型语言函数的参数没有类型约束也就无法根据参数类型来区分重载。再者Python中函数的参数可以有默认值可以使用可变参数和关键字参数因此即便没有函数重载也要可以让一个函数根据调用者传入的参数产生不同的行为。 题目012用Python代码实现Python内置函数max。 点评这个题目看似简单但实际上还是比较考察面试者的功底。因为Python内置的max函数既可以传入可迭代对象找出最大又可以传入两个或多个参数找出最大最为关键的是还可以通过命名关键字参数key来指定一个用于元素比较的函数还可以通过default命名关键字参数来指定当可迭代对象为空时返回的默认值。 下面的代码仅供参考 def my_max(*args, keyNone, defaultNone):获取可迭代对象中最大的元素或两个及以上实参中最大的元素:param args: 一个可迭代对象或多个元素:param key: 提取用于元素比较的特征值的函数默认为None:param default: 如果可迭代对象为空则返回该默认值如果没有给默认值则引发ValueError异常:return: 返回可迭代对象或多个元素中的最大元素if len(args) 1 and len(args[0]) 0:if default:return defaultelse:raise ValueError(max() arg is an empty sequence)items args[0] if len(args) 1 else argsmax_elem, max_value items[0], items[0]if key:max_value key(max_value)for item in items:value itemif key:value key(item)if value max_value:max_elem, max_value item, valuereturn max_elem 题目013写一个函数统计传入的列表中每个数字出现的次数并返回对应的字典。 点评送人头的题目不解释。 def count_letters(items):result {}for item in items:if isinstance(item, (int, float)):result[item] result.get(item, 0) 1return result 也可以直接使用Python标准库中collections模块的Counter类来解决这个问题Counter是dict的子类它会将传入的序列中的每个元素作为键元素出现的次数作为值来构造字典。 from collections import Counterdef count_letters(items):counter Counter(items)return {key: value for key, value in counter.items() \if isinstance(key, (int, float))} 题目014使用Python代码实现遍历一个文件夹的操作。 点评基本也是送人头的题目只要用过os模块就应该知道怎么做。 Python标准库os模块的walk函数提供了遍历一个文件夹的功能它返回一个生成器。 import osg os.walk(/Users/Hao/Downloads/) for path, dir_list, file_list in g:for dir_name in dir_list:print(os.path.join(path, dir_name))for file_name in file_list:print(os.path.join(path, file_name)) 说明os.path模块提供了很多进行路径操作的工具函数在项目开发中也是经常会用到的。如果题目明确要求不能使用os.walk函数那么可以使用os.listdir函数来获取指定目录下的文件和文件夹然后再通过循环遍历用os.isdir函数判断哪些是文件夹对于文件夹可以通过递归调用进行遍历这样也可以实现遍历一个文件夹的操作。 题目015现有2元、3元、5元共三种面额的货币如果需要找零99元一共有多少种找零的方式 点评还有一个非常类似的题目“一个小朋友走楼梯一次可以走1个台阶、2个台阶或3个台阶问走完10个台阶一共有多少种走法”这两个题目的思路是一样如果用递归函数来写的话非常简单。 from functools import lru_cachelru_cache() def change_money(total):if total 0:return 1if total 0:return 0return change_money(total - 2) change_money(total - 3) \change_money(total - 5) 说明在上面的代码中我们用lru_cache装饰器装饰了递归函数change_money如果不做这个优化上面代码的渐近时间复杂度将会是$O(3^N)$而如果参数total的值是99这个运算量是非常巨大的。lru_cache装饰器会缓存函数的执行结果这样就可以减少重复运算所造成的开销这是空间换时间的策略也是动态规划的编程思想。 题目016写一个函数给定矩阵的阶数n输出一个螺旋式数字矩阵。 例如n 2返回 1 2 4 3例如n 3返回 1 2 3 8 9 4 7 6 5这个题目本身并不复杂下面的代码仅供参考。 def show_spiral_matrix(n):matrix [[0] * n for _ in range(n)]row, col 0, 0num, direction 1, 0while num n ** 2:if matrix[row][col] 0:matrix[row][col] numnum 1if direction 0:if col n - 1 and matrix[row][col 1] 0:col 1else:direction 1elif direction 1:if row n - 1 and matrix[row 1][col] 0:row 1else:direction 1elif direction 2:if col 0 and matrix[row][col - 1] 0:col - 1else:direction 1else:if row 0 and matrix[row - 1][col] 0:row - 1else:direction 1direction % 4for x in matrix:for y in x:print(y, end\t)print() 题目017阅读下面的代码写出程序的运行结果。 items [1, 2, 3, 4] print([i for i in items if i 2]) print([i for i in items if i % 2]) print([(x, y) for x, y in zip(abcd, (1, 2, 3, 4, 5))]) print({x: fitem{x ** 2} for x in (2, 4, 6)}) print(len({x for x in hello world if x not in abcdefg})) 点评生成式推导式属于Python的特色语法之一几乎是面试必考内容。Python中通过生成式字面量语法可以创建出列表、集合、字典。 [3, 4] [1, 3] [(a, 1), (b, 2), (c, 3), (d, 4)] {2: item4, 4: item16, 6: item36} 6题目018说出下面代码的运行结果。 class Parent:x 1class Child1(Parent):passclass Child2(Parent):passprint(Parent.x, Child1.x, Child2.x) Child1.x 2 print(Parent.x, Child1.x, Child2.x) Parent.x 3 print(Parent.x, Child1.x, Child2.x) 点评运行上面的代码首先输出1 1 1这一点大家应该没有什么疑问。接下来通过Child1.x 2给类Child1重新绑定了属性x并赋值为2所以Child1.x会输出2而Parent和Child2并不受影响。执行Parent.x 3会重新给Parent类的x属性赋值为3由于Child2的x属性继承自Parent所以Child2.x的值也是3而之前我们为Child1重新绑定了x属性那么它的x属性值不会受到Parent.x 3的影响还是之前的值2。 1 1 1 1 2 1 3 2 3题目19说说你用过Python标准库中的哪些模块。 点评Python标准库中的模块非常多建议大家根据自己过往的项目经历来介绍你用过的标准库和三方库因为这些是你最为熟悉的经得起面试官深挖的。 模块名介绍sys跟Python解释器相关的变量和函数例如sys.version、sys.exit()os和操作系统相关的功能例如os.listdir()、os.remove()re和正则表达式相关的功能例如re.compile()、re.search()math和数学运算相关的功能例如math.pi、math.e、math.coslogging和日志系统相关的类和函数例如logging.Logger、logging.Handlerjson / pickle实现对象序列化和反序列的模块例如json.loads、json.dumpshashlib封装了多种哈希摘要算法的模块例如hashlib.md5、hashlib.sha1urllib包含了和URL相关的子模块例如urllib.request、urllib.parseitertools提供各种迭代器的模块例如itertools.cycle、itertools.productfunctools函数相关工具模块例如functools.partial、functools.lru_cachecollections / heapq封装了常用数据结构和算法的模块例如collections.dequethreading / multiprocessing多线程/多进程相关类和函数的模块例如threading.Threadconcurrent.futures / asyncio并发编程/异步编程相关的类和函数的模块例如ThreadPoolExecutorbase64提供BASE-64编码相关函数的模块例如bas64.encodecsv和读写CSV文件相关的模块例如csv.reader、csv.writerprofile / cProfile / pstats和代码性能剖析相关的模块例如cProfile.run、pstats.Statsunittest和单元测试相关的模块例如unittest.TestCase 题目20__init__和__new__方法有什么区别 Python中调用构造器创建对象属于两阶段构造过程首先执行__new__方法获得保存对象所需的内存空间再通过__init__执行对内存空间数据的填充对象属性的初始化。__new__方法的返回值是创建好的Python对象的引用而__init__方法的第一个参数就是这个对象的引用所以在__init__中可以完成对对象的初始化操作。__new__是类方法它的第一个参数是类__init__是对象方法它的第一个参数是对象。 题目21输入年月日判断这个日期是这一年的第几天。 方法一不使用标准库中的模块和函数。 def is_leap_year(year):判断指定的年份是不是闰年平年返回False闰年返回Truereturn year % 4 0 and year % 100 ! 0 or year % 400 0def which_day(year, month, date):计算传入的日期是这一年的第几天# 用嵌套的列表保存平年和闰年每个月的天数days_of_month [[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],[31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]]days days_of_month[is_leap_year(year)][:month - 1]return sum(days) date 方法二使用标准库中的datetime模块。 import datetimedef which_day(year, month, date):end datetime.date(year, month, date)start datetime.date(year, 1, 1)return (end - start).days 1 题目22平常工作中用什么工具进行静态代码分析。 点评静态代码分析工具可以从代码中提炼出各种静态属性这使得开发者可以对代码的复杂性、可维护性和可读性有更好的了解这里所说的静态属性包括 代码是否符合编码规范例如PEP-8。代码中潜在的问题包括语法错误、缩进问题、导入缺失、变量覆盖等。代码中的坏味道。代码的复杂度。代码的逻辑问题。 工作中静态代码分析主要用到的是Pylint和Flake8。Pylint可以检查出代码错误、坏味道、不规范的代码等问题较新的版本中还提供了代码复杂度统计数据可以生成检查报告。Flake8封装了Pyflakes检查代码逻辑错误、McCabe检查代码复杂性和Pycodestyle检查代码是否符合PEP-8规范工具它可以执行这三个工具提供的检查。 题目23说一下你知道的Python中的魔术方法。 点评魔术方法也称为魔法方法是Python中的特色语法也是面试中的高频问题。 魔术方法作用__new__、__init__、__del__创建和销毁对象相关__add__、__sub__、__mul__、__div__、__floordiv__、__mod__算术运算符相关__eq__、__ne__、__lt__、__gt__、__le__、__ge__关系运算符相关__pos__、__neg__、__invert__一元运算符相关__lshift__、__rshift__、__and__、__or__、__xor__位运算相关__enter__、__exit__上下文管理器协议__iter__、__next__、__reversed__迭代器协议__int__、__long__、__float__、__oct__、__hex__类型/进制转换相关__str__、__repr__、__hash__、__dir__对象表述相关__len__、__getitem__、__setitem__、__contains__、__missing__序列相关__copy__、__deepcopy__对象拷贝相关__call__、__setattr__、__getattr__、__delattr__其他魔术方法 题目24函数参数*arg和**kwargs分别代表什么 Python中函数的参数分为位置参数、可变参数、关键字参数、命名关键字参数。*args代表可变参数可以接收0个或任意多个参数当不确定调用者会传入多少个位置参数时就可以使用可变参数它会将传入的参数打包成一个元组。**kwargs代表关键字参数可以接收用参数名参数值的方式传入的参数传入的参数的会打包成一个字典。定义函数时如果同时使用*args和**kwargs那么函数可以接收任意参数。 题目25写一个记录函数执行时间的装饰器。 点评高频面试题也是最简单的装饰器面试者必须要掌握的内容。 方法一用函数实现装饰器。 from functools import wraps from time import timedef record_time(func):wraps(func)def wrapper(*args, **kwargs):start time()result func(*args, **kwargs)print(f{func.__name__}执行时间: {time() - start}秒)return resultreturn wrapper 方法二用类实现装饰器。类有__call__魔术方法该类对象就是可调用对象可以当做装饰器来使用。 from functools import wraps from time import timeclass Record:def __call__(self, func):wraps(func)def wrapper(*args, **kwargs):start time()result func(*args, **kwargs)print(f{func.__name__}执行时间: {time() - start}秒)return resultreturn wrapper 说明装饰器可以用来装饰类或函数为其提供额外的能力属于设计模式中的代理模式。 扩展装饰器本身也可以参数化例如上面的例子中如果不希望在终端中显示函数的执行时间而是希望由调用者来决定如何输出函数的执行时间可以通过参数化装饰器的方式来做到代码如下所示。 from functools import wraps from time import timedef record_time(output):可以参数化的装饰器def decorate(func):wraps(func)def wrapper(*args, **kwargs):start time()result func(*args, **kwargs)output(func.__name__, time() - start)return resultreturn wrapperreturn decorate 题目26什么是鸭子类型duck typing 鸭子类型是动态类型语言判断一个对象是不是某种类型时使用的方法也叫做鸭子判定法。简单的说鸭子类型是指判断一只鸟是不是鸭子我们只关心它游泳像不像鸭子、叫起来像不像鸭子、走路像不像鸭子就足够了。换言之如果对象的行为跟我们的预期是一致的能够接受某些消息我们就认定它是某种类型的对象。 在Python语言中有很多bytes-like对象如bytes、bytearray、array.array、memoryview、file-like对象如StringIO、BytesIO、GzipFile、socket、path-like对象如str、bytes其中file-like对象都能支持read和write操作可以像文件一样读写这就是所谓的对象有鸭子的行为就可以判定为鸭子的判定方法。再比如Python中列表的extend方法它需要的参数并不一定要是列表只要是可迭代对象就没有问题。 说明动态语言的鸭子类型使得设计模式的应用被大大简化。 题目27说一下Python中变量的作用域。 Python中有四种作用域分别是局部作用域Local、嵌套作用域Embedded、全局作用域Global、内置作用域Built-in搜索一个标识符时会按照LEGB的顺序进行搜索如果所有的作用域中都没有找到这个标识符就会引发NameError异常。 题目28说一下你对闭包的理解。 闭包是支持一等函数的编程语言Python、JavaScript等中实现词法绑定的一种技术。当捕捉闭包的时候它的自由变量在函数外部定义但在函数内部使用的变量会在捕捉时被确定这样即便脱离了捕捉时的上下文它也能照常运行。简单的说可以将闭包理解为能够读取其他函数内部变量的函数。正在情况下函数的局部变量在函数调用结束之后就结束了生命周期但是闭包使得局部变量的生命周期得到了延展。使用闭包的时候需要注意闭包会使得函数中创建的对象不会被垃圾回收可能会导致很大的内存开销所以闭包一定不能滥用。 题目29说一下Python中的多线程和多进程的应用场景和优缺点。 线程是操作系统分配CPU的基本单位进程是操作系统分配内存的基本单位。通常我们运行的程序会包含一个或多个进程而每个进程中又包含一个或多个线程。多线程的优点在于多个线程可以共享进程的内存空间所以线程间的通信非常容易实现但是如果使用官方的CPython解释器多线程受制于GIL全局解释器锁并不能利用CPU的多核特性这是一个很大的问题。使用多进程可以充分利用CPU的多核特性但是进程间通信相对比较麻烦需要使用IPC机制管道、套接字等。 多线程适合那些会花费大量时间在I/O操作上但没有太多并行计算需求且不需占用太多内存的I/O密集型应用。多进程适合执行计算密集型任务如视频编码解码、数据处理、科学计算等、可以分解为多个并行子任务并能合并子任务执行结果的任务以及在内存使用方面没有任何限制且不强依赖于I/O操作的任务。 扩展Python中实现并发编程通常有多线程、多进程和异步编程三种选择。异步编程实现了协作式并发通过多个相互协作的子程序的用户态切换实现对CPU的高效利用这种方式也是非常适合I/O密集型应用的。 题目30说一下Python 2和Python 3的区别。 点评这种问题千万不要背所谓的参考答案说一些自己最熟悉的就足够了。 Python 2中的print和exec都是关键字在Python 3中变成了函数。Python 3中没有long类型整数都是int类型。Python 2中的不等号在Python 3中被废弃统一使用!。Python 2中的xrange函数在Python 3中被range函数取代。Python 3对Python 2中不安全的input函数做出了改进废弃了raw_input函数。Python 2中的file函数被Python 3中的open函数取代。Python 2中的/运算对于int类型是整除在Python 3中要用//来做整除除法。Python 3中改进了Python 2捕获异常的代码很明显Python 3的写法更合理。Python 3生成式中循环变量的作用域得到了更好的控制不会影响到生成式之外的同名变量。Python 3中的round函数可以返回int或float类型Python 2中的round函数返回float类型。Python 3的str类型是Unicode字符串Python 2的str类型是字节串相当于Python 3中的bytes。Python 3中的比较运算符必须比较同类对象。Python 3中定义类的都是新式类Python 2中定义的类有新式类显式继承自object的类和旧式类经典类之分新式类和旧式类在MRO问题上有非常显著的区别新式类可以使用__class__属性获取自身类型新式类可以使用__slots__魔法。Python 3对代码缩进的要求更加严格如果混用空格和制表键会引发TabError。Python 3中字典的keys、values、items方法都不再返回list对象而是返回view object内置的map、filter等函数也不再返回list对象而是返回迭代器对象。Python 3标准库中某些模块的名字跟Python 2是有区别的而在三方库方面有些三方库只支持Python 2有些只能支持Python 3。 题目31谈谈你对“猴子补丁”monkey patching的理解。 “猴子补丁”是动态类型语言的一个特性代码运行时在不修改源代码的前提下改变代码中的方法、属性、函数等以达到热补丁hot patch的效果。很多系统的安全补丁也是通过猴子补丁的方式来实现的但实际开发中应该避免对猴子补丁的使用以免造成代码行为不一致的问题。 在使用gevent库的时候我们会在代码开头的地方执行gevent.monkey.patch_all()这行代码的作用是把标准库中的socket模块给替换掉这样我们在使用socket的时候不用修改任何代码就可以实现对代码的协程化达到提升性能的目的这就是对猴子补丁的应用。 另外如果希望用ujson三方库替换掉标准库中的json也可以使用猴子补丁的方式代码如下所示。 import json, ujsonjson.__name__ ujson json.dumps ujson.dumps json.loads ujson.loads 单元测试中的Mock技术也是对猴子补丁的应用Python中的unittest.mock模块就是解决单元测试中用Mock对象替代被测对象所依赖的对象的模块。 题目32阅读下面的代码说出运行结果。 class A:def who(self):print(A, end)class B(A):def who(self):super(B, self).who()print(B, end)class C(A):def who(self):super(C, self).who()print(C, end)class D(B, C):def who(self):super(D, self).who()print(D, end)item D() item.who() 点评这道题考查到了两个知识点 Python中的MRO方法解析顺序。在没有多重继承的情况下向对象发出一个消息如果对象没有对应的方法那么向上父类搜索的顺序是非常清晰的。如果向上追溯到object类所有类的父类都没有找到对应的方法那么将会引发AttributeError异常。但是有多重继承尤其是出现菱形继承钻石继承的时候向上追溯到底应该找到那个方法就得确定MRO。Python 3中的类以及Python 2中的新式类使用C3算法来确定MRO它是一种类似于广度优先搜索的方法Python 2中的旧式类经典类使用深度优先搜索来确定MRO。在搞不清楚MRO的情况下可以使用类的mro方法或__mro__属性来获得类的MRO列表。super()函数的使用。在使用super函数时可以通过super(类型, 对象)来指定对哪个对象以哪个类为起点向上搜索父类方法。所以上面B类代码中的super(B, self).who()表示以B类为起点向上搜索selfD类对象的who方法所以会找到C类中的who方法因为D类对象的MRO列表是D -- B -- C -- A -- object。 ACBD题目33编写一个函数实现对逆波兰表达式求值不能使用Python的内置函数。 点评逆波兰表达式也称为“后缀表达式”相较于平常我们使用的“中缀表达式”逆波兰表达式不需要括号来确定运算的优先级例如5 * (2 3)对应的逆波兰表达式是5 2 3 *。逆波兰表达式求值需要借助栈结构扫描表达式遇到运算数就入栈遇到运算符就出栈两个元素做运算将运算结果入栈。表达式扫描结束后栈中只有一个数这个数就是最终的运算结果直接出栈即可。 import operatorclass Stack:栈FILOdef __init__(self):self.elems []def push(self, elem):入栈self.elems.append(elem)def pop(self):出栈return self.elems.pop()propertydef is_empty(self):检查栈是否为空return len(self.elems) 0def eval_suffix(expr):逆波兰表达式求值operators {: operator.add,-: operator.sub,*: operator.mul,/: operator.truediv}stack Stack()for item in expr.split():if item.isdigit():stack.push(float(item))else: num2 stack.pop()num1 stack.pop()stack.push(operators[item](num1, num2))return stack.pop() 题目34Python中如何实现字符串替换操作 Python中实现字符串替换大致有两类方法字符串的replace方法和正则表达式的sub方法。 方法一使用字符串的replace方法。 message hello, world! print(message.replace(o, O).replace(l, L).replace(he, HE)) 方法二使用正则表达式的sub方法。 import remessage hello, world! pattern re.compile([aeiou]) print(pattern.sub(#, message)) 扩展还有一个相关的面试题对保存文件名的列表排序要求文件名按照字母表和数字大小进行排序例如对于列表filenames [a12.txt, a8.txt, b10.txt, b2.txt, b19.txt, a3.txt] 排序的结果是[a3.txt, a8.txt, a12.txt, b2.txt, b10.txt, b19.txt]。提示一下可以通过字符串替换的方式为文件名补位根据补位后的文件名用sorted函数来排序大家可以思考下这个问题如何解决。 题目35如何剖析Python代码的执行性能 剖析代码性能可以使用Python标准库中的cProfile和pstats模块cProfile的run函数可以执行代码并收集统计信息创建出Stats对象并打印简单的剖析报告。Stats是pstats模块中的类它是一个统计对象。当然也可以使用三方工具line_profiler和memory_profiler来剖析每一行代码耗费的时间和内存这两个三方工具都会用非常友好的方式输出剖析结构。如果使用PyCharm可以利用“Run”菜单的“Profile”菜单项对代码进行性能分析PyCharm中可以用表格或者调用图Call Graph的方式来显示性能剖析的结果。 下面是使用cProfile剖析代码性能的例子。 example.py import cProfiledef is_prime(num):for factor in range(2, int(num ** 0.5) 1):if num % factor 0:return Falsereturn Trueclass PrimeIter:def __init__(self, total):self.counter 0self.current 1self.total totaldef __iter__(self):return selfdef __next__(self):if self.counter self.total:self.current 1while not is_prime(self.current):self.current 1self.counter 1return self.currentraise StopIteration()cProfile.run(list(PrimeIter(10000))) 如果使用line_profiler三方工具可以直接剖析is_prime函数每行代码的性能需要给is_prime函数添加一个profiler装饰器代码如下所示。 profiler def is_prime(num):for factor in range(2, int(num ** 0.5) 1):if num % factor 0:return Falsereturn True 安装line_profiler。 pip install line_profiler 使用line_profiler。 kernprof -lv example.py 运行结果如下所示。 Line # Hits Time Per Hit % Time Line Contents 1 profile2 def is_prime(num):3 86624 48420.0 0.6 50.5 for factor in range(2, int(num ** 0.5) 1):4 85624 44000.0 0.5 45.9 if num % factor 0:5 6918 3080.0 0.4 3.2 return False6 1000 430.0 0.4 0.4 return True题目36如何使用random模块生成随机数、实现随机乱序和随机抽样 点评送人头的题目因为Python标准库中的常用模块应该是Python开发者都比较熟悉的内容这个问题回如果答不上来整个面试基本也就砸锅了。 random.random()函数可以生成[0.0, 1.0)之间的随机浮点数。random.uniform(a, b)函数可以生成[a, b]或[b, a]之间的随机浮点数。random.randint(a, b)函数可以生成[a, b]或[b, a]之间的随机整数。random.shuffle(x)函数可以实现对序列x的原地随机乱序。random.choice(seq)函数可以从非空序列中取出一个随机元素。random.choices(population, weightsNone, *, cum_weightsNone, k1)函数可以从总体中随机抽取有放回抽样出容量为k的样本并返回样本的列表可以通过参数指定个体的权重如果没有指定权重个体被选中的概率均等。random.sample(population, k)函数可以从总体中随机抽取无放回抽样出容量为k的样本并返回样本的列表。 扩展random模块提供的函数除了生成均匀分布的随机数外还可以生成其他分布的随机数例如random.gauss(mu, sigma)函数可以生成高斯分布正态分布的随机数random.paretovariate(alpha)函数会生成帕累托分布的随机数random.gammavariate(alpha, beta)函数会生成伽马分布的随机数。 题目37解释一下线程池的工作原理。 点评池化技术就是一种典型空间换时间的策略我们使用的数据库连接池、线程池等都是池化技术的应用Python标准库currrent.futures模块的ThreadPoolExecutor就是线程池的实现如果要弄清楚它的工作原理可以参考下面的内容。 线程池是一种用于减少线程本身创建和销毁造成的开销的技术属于典型的空间换时间操作。如果应用程序需要频繁的将任务派发到线程中执行线程池就是必选项因为创建和释放线程涉及到大量的系统底层操作开销较大如果能够在应用程序工作期间将创建和释放线程的操作变成预创建和借还操作将大大减少底层开销。线程池在应用程序启动后立即创建一定数量的线程放入空闲队列中。这些线程最开始都处于阻塞状态不会消耗CPU资源但会占用少量的内存空间。当任务到来后从队列中取出一个空闲线程把任务派发到这个线程中运行并将该线程标记为已占用。当线程池中所有的线程都被占用后可以选择自动创建一定数量的新线程用于处理更多的任务也可以选择让任务排队等待直到有空闲的线程可用。在任务执行完毕后线程并不退出结束而是继续保持在池中等待下一次的任务。当系统比较空闲时大部分线程长时间处于闲置状态时线程池可以自动销毁一部分线程回收系统资源。基于这种预创建技术线程池将线程创建和销毁本身所带来的开销分摊到了各个具体的任务上执行次数越多每个任务所分担到的线程本身开销则越小。 一般线程池都必须具备下面几个组成部分 线程池管理器用于创建并管理线程池。工作线程和线程队列线程池中实际执行的线程以及保存这些线程的容器。任务接口将线程执行的任务抽象出来形成任务接口确保线程池与具体的任务无关。任务队列线程池中保存等待被执行的任务的容器。 题目38举例说明什么情况下会出现KeyError、TypeError、ValueError。 举一个简单的例子变量a是一个字典执行int(a[x])这个操作就有可能引发上述三种类型的异常。如果字典中没有键x会引发KeyError如果键x对应的值不是str、float、int、bool以及bytes-like类型在调用int函数构造int类型的对象时会引发TypeError如果a[x]是一个字符串或者字节串而对应的内容又无法处理成int时将引发ValueError。 题目39说出下面代码的运行结果。 def extend_list(val, items[]):items.append(val)return itemslist1 extend_list(10) list2 extend_list(123, []) list3 extend_list(a) print(list1) print(list2) print(list3) 点评Python函数在定义的时候默认参数items的值就被计算出来了即[]。因为默认参数items引用了对象[]每次调用该函数如果对items引用的列表进行了操作下次调用时默认参数还是引用之前的那个列表而不是重新赋值为[]所以列表中会有之前添加的元素。如果通过传参的方式为items重新赋值那么items将引用到新的列表对象而不再引用默认的那个列表对象。这个题在面试中经常被问到通常不建议使用容器类型的默认参数像PyLint这样的代码检查工具也会对这种代码提出质疑和警告。 [10, a] [123] [10, a]题目40如何读取大文件例如内存只有4G如何读取一个大小为8G的文件 很显然4G内存要一次性的加载大小为8G的文件是不现实的遇到这种情况必须要考虑多次读取和分批次处理。在Python中读取文件可以先通过open函数获取文件对象在读取文件时可以通过read方法的size参数指定读取的大小也可以通过seek方法的offset参数指定读取的位置这样就可以控制单次读取数据的字节数和总字节数。除此之外可以使用内置函数iter将文件对象处理成迭代器对象每次只读取少量的数据进行处理代码大致写法如下所示。 with open(..., rb) as file:for data in iter(lambda: file.read(2097152), b):pass 在Linux系统上可以通过split命令将大文件切割为小片然后通过读取切割后的小文件对数据进行处理。例如下面的命令将名为filename的大文件切割为大小为512M的多个文件。 split -b 512m filename 如果愿意 也可以将名为filename的文件切割为10个文件命令如下所示。 split -n 10 filename 扩展外部排序跟上述的情况非常类似由于处理的数据不能一次装入内存只能放在读写较慢的外存储器通常是硬盘上。“排序-归并算法”就是一种常用的外部排序策略。在排序阶段先读入能放在内存中的数据量将其排序输出到一个临时文件依此进行将待排序数据组织为多个有序的临时文件然后在归并阶段将这些临时文件组合为一个大的有序文件这个大的有序文件就是排序的结果。 题目41说一下你对Python中模块和包的理解。 每个Python文件就是一个模块而保存这些文件的文件夹就是一个包但是这个作为Python包的文件夹必须要有一个名为__init__.py的文件否则无法导入这个包。通常一个文件夹下还可以有子文件夹这也就意味着一个包下还可以有子包子包中的__init__.py并不是必须的。模块和包解决了Python中命名冲突的问题不同的包下可以有同名的模块不同的模块下可以有同名的变量、函数或类。在Python中可以使用import或from ... import ...来导入包和模块在导入的时候还可以使用as关键字对包、模块、类、函数、变量等进行别名从而彻底解决编程中尤其是多人协作团队开发时的命名冲突问题。 题目42说一下你知道的Python编码规范。 点评企业的Python编码规范基本上是参照PEP-8或谷歌开源项目风格指南来制定的后者还提到了可以使用Lint工具来检查代码的规范程度面试的时候遇到这类问题可以先说下这两个参照标准然后挑重点说一下Python编码的注意事项。 空格的使用 使用空格来表示缩进而不要用制表符Tab。和语法相关的每一层缩进都用4个空格来表示。每行的字符数不要超过79个字符如果表达式因太长而占据了多行除了首行之外的其余各行都应该在正常的缩进宽度上再加上4个空格。函数和类的定义代码前后都要用两个空行进行分隔。在同一个类中各个方法之间应该用一个空行进行分隔。二元运算符的左右两侧应该保留一个空格而且只要一个空格就好。标识符命名 变量、函数和属性应该使用小写字母来拼写如果有多个单词就使用下划线进行连接。类中受保护的实例属性应该以一个下划线开头。类中私有的实例属性应该以两个下划线开头。类和异常的命名应该每个单词首字母大写。模块级别的常量应该采用全大写字母如果有多个单词就用下划线进行连接。类的实例方法应该把第一个参数命名为self以表示对象自身。类的类方法应该把第一个参数命名为cls以表示该类自身。表达式和语句 采用内联形式的否定词而不要把否定词放在整个表达式的前面。例如if a is not b就比if not a is b更容易让人理解。不要用检查长度的方式来判断字符串、列表等是否为None或者没有元素应该用if not x这样的写法来检查它。就算if分支、for循环、except异常捕获等中只有一行代码也不要将代码和if、for、except等写在一起分开写才会让代码更清晰。import语句总是放在文件开头的地方。引入模块的时候from math import sqrt比import math更好。如果有多个import语句应该将其分为三部分从上到下分别是Python标准模块、第三方模块和自定义模块每个部分内部应该按照模块名称的字母表顺序来排列。 题目43运行下面的代码是否会报错如果报错请说明哪里有什么样的错如果不报错请说出代码的执行结果。 class A: def __init__(self, value):self.__value valuepropertydef value(self):return self.__valueobj A(1) obj.__value 2 print(obj.value) print(obj.__value) 点评这道题有两个考察点一个考察点是对_和__开头的对象属性访问权限以及property装饰器的了解另外一个考察的点是对动态语言的理解不需要过多的解释。 1 2扩展如果不希望代码运行时动态的给对象添加新属性可以在定义类时使用__slots__魔法。例如我们可以在上面的A中添加一行__slots__ (__value, )再次运行上面的代码将会在原来的第10行处产生AttributeError错误。 题目44对下面给出的字典按值从大到小对键进行排序。 prices {AAPL: 191.88,GOOG: 1186.96,IBM: 149.24,ORCL: 48.44,ACN: 166.89,FB: 208.09,SYMC: 21.29 } 点评sorted函数的高阶用法在面试的时候经常出现key参数可以传入一个函数名或一个Lambda函数该函数的返回值代表了在排序时比较元素的依据。 sorted(prices, keylambda x: prices[x], reverseTrue) 题目45说一下namedtuple的用法和作用。 点评Python标准库的collections模块提供了很多有用的数据结构这些内容并不是每个开发者都清楚就比如题目问到的namedtuple在我参加过的面试中90%的面试者都不能准确的说出它的作用和应用场景。此外deque也是一个非常有用但又经常被忽视的类还有Counter、OrderedDict 、defaultdict 、UserDict等类大家清楚它们的用法吗 在使用面向对象编程语言的时候定义类是最常见的一件事情有的时候我们会用到只有属性没有方法的类这种类的对象通常只用于组织数据并不能接收消息所以我们把这种类称为数据类或者退化的类就像C语言中的结构体那样。我们并不建议使用这种退化的类在Python中可以用namedtuple命名元组来替代这种类。 from collections import namedtupleCard namedtuple(Card, (suite, face)) card1 Card(红桃, 13) card2 Card(草花, 5) print(f{card1.suite}{card1.face}) print(f{card2.suite}{card2.face}) 命名元组与普通元组一样是不可变容器一旦将数据存储在namedtuple的顶层属性中数据就不能再修改了也就意味着对象上的所有属性都遵循“一次写入多次读取”的原则。和普通元组不同的是命名元组中的数据有访问名称可以通过名称而不是索引来获取保存的数据不仅在操作上更加简单代码的可读性也会更好。 命名元组的本质就是一个类所以它还可以作为父类创建子类。除此之外命名元组内置了一系列的方法例如可以通过_asdict方法将命名元组处理成字典也可以通过_replace方法创建命名元组对象的浅拷贝。 class MyCard(Card):def show(self):faces [, A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K]return f{self.suite}{faces[self.face]}print(Card) # class __main__.Card card3 MyCard(方块, 12) print(card3.show()) # 方块Q print(dict(card1._asdict())) # {suite: 红桃, face: 13} print(card2._replace(suite方块)) # Card(suite方块, face5) 总而言之命名元组能更好的组织数据结构让代码更加清晰和可读在很多场景下是元组、字典和数据类的替代品。在需要创建占用空间更少的不可变类时命名元组就是很好的选择。 题目46按照题目要求写出对应的函数。 要求写一个函数传入一个有若干个整数的列表该列表中某个元素出现的次数超过了50%返回这个元素。 def more_than_half(items):temp, times None, 0for item in items:if times 0:temp itemtimes 1else:if item temp:times 1else:times - 1return temp 点评LeetCode上的题目在Python面试中出现过利用元素出现次数超过了50%这一特征出现和temp相同的元素就将计数值加1出现和temp不同的元素就将计数值减1。如果计数值为0说明之前出现的元素已经对最终的结果没有影响用temp记下当前元素并将计数值置为1。最终出现次数超过了50%的这个元素一定会被赋值给变量temp。 题目47按照题目要求写出对应的函数。 要求写一个函数传入的参数是一个列表列表中的元素可能也是一个列表返回该列表最大的嵌套深度。例如列表[1, 2, 3]的嵌套深度为1列表[[1], [2, [3]]]的嵌套深度为3。 def list_depth(items):if isinstance(items, list):max_depth 1for item in items:max_depth max(list_depth(item) 1, max_depth)return max_depthreturn 0 点评看到题目应该能够比较自然的想到使用递归的方式检查列表中的每个元素。 题目48按照题目要求写出对应的装饰器。 要求有一个通过网络获取数据的函数可能会因为网络原因出现异常写一个装饰器让这个函数在出现指定异常时可以重试指定的次数并在每次重试之前随机延迟一段时间最长延迟时间可以通过参数进行控制。 方法一 from functools import wraps from random import random from time import sleepdef retry(*, retry_times3, max_wait_secs5, errors(Exception, )):def decorate(func):wraps(func)def wrapper(*args, **kwargs):for _ in range(retry_times):try:return func(*args, **kwargs)except errors:sleep(random() * max_wait_secs)return Nonereturn wrapperreturn decorate 方法二 from functools import wraps from random import random from time import sleepclass Retry(object):def __init__(self, *, retry_times3, max_wait_secs5, errors(Exception, )):self.retry_times retry_timesself.max_wait_secs max_wait_secsself.errors errorsdef __call__(self, func):wraps(func)def wrapper(*args, **kwargs):for _ in range(self.retry_times):try:return func(*args, **kwargs)except self.errors:sleep(random() * self.max_wait_secs)return Nonereturn wrapper 点评我们不止一次强调过装饰器几乎是Python面试必问内容这个题目比之前的题目稍微复杂一些它需要的是一个参数化的装饰器。 题目49写一个函数实现字符串反转尽可能写出你知道的所有方法。 点评烂大街的题目基本上算是送人头的题目。 方法一反向切片 def reverse_string(content):return content[::-1] 方法二反转拼接 def reverse_string(content):return .join(reversed(content)) 方法三递归调用 def reverse_string(content):if len(content) 1:return contentreturn reverse_string(content[1:]) content[0] 方法四双端队列 from collections import dequedef reverse_string(content):q deque()q.extendleft(content)return .join(q) 方法五反向组装 from io import StringIOdef reverse_string(content):buffer StringIO()for i in range(len(content) - 1, -1, -1):buffer.write(content[i])return buffer.getvalue() 方法六反转拼接 def reverse_string(content):return .join([content[i] for i in range(len(content) - 1, -1, -1)]) 方法七半截交换 def reverse_string(content):length, content len(content), list(content)for i in range(length // 2):content[i], content[length - 1 - i] content[length - 1 - i], content[i]return .join(content) 方法八对位交换 def reverse_string(content):length, content len(content), list(content)for i, j in zip(range(length // 2), range(length - 1, length // 2 - 1, -1)):content[i], content[j] content[j], content[i]return .join(content) 扩展这些方法其实都是大同小异的面试的时候能够给出几种有代表性的就足够了。给大家留一个思考题上面这些方法哪些做法的性能较好呢我们之前提到过剖析代码性能的方法大家可以用这些方法来检验下你给出的答案是否正确。 题目50按照题目要求写出对应的函数。 要求列表中有1000000个元素取值范围是[1000, 10000)设计一个函数找出列表中的重复元素。 def find_dup(items: list):dups [0] * 9000for item in items:dups[item - 1000] 1for idx, val in enumerate(dups):if val 1:yield idx 1000 点评这道题的解法和计数排序的原理一致虽然元素的数量非常多但是取值范围[1000, 10000)并不是很大只有9000个可能的取值所以可以用一个能够保存9000个元素的dups列表来记录每个元素出现的次数dups列表所有元素的初始值都是0通过对items列表中元素的遍历当出现某个元素时将dups列表对应位置的值加1最后dups列表中值大于1的元素对应的就是items列表中重复出现过的元素。
http://www.pierceye.com/news/43179/

相关文章:

  • 门户网站英文网站规划设计方案
  • 只做男生穿搭的网站大型门户网站制作流程
  • 网站做友链的好处wdcp 网站迁移
  • go搭建网站苏州市建设局网站集群统一登录
  • 企业网站建设报价方案模板视频网站怎样做
  • 大型网站开发什么书籍好电脑上不了建设厅网站
  • 国外网站赏析汉口网站建设 优帮云
  • 石家庄小程序开发公司宁波seo服务推广软件
  • php网站建设流程flash 网站带后台
  • 在线咨询网站开发价格搭建一个网址
  • 做创意ppt网站有哪些西安百度竞价代运营
  • 一些做淘宝优惠券的网站郑州seo哪家专业
  • 专业网站制作公司教程连连跨境电商网站开发
  • 北京网站建设著名公司给网站做游戏视频怎么赚钱
  • 怎样给企业做网站四川省住房和城乡建设厅证书
  • 网站建设制劳务派遣
  • 东莞做网站排名优化推广在线域名解析ip地址查询
  • 网站建设 中企动力成都网站 技术支持
  • 南京seo整站优化技术海洋公司做网站
  • 做购物车网站多少钱权威解读当前经济热点问题
  • 网站建设论文开题报告北京seo推广
  • 手机数码网站建站 seo课程
  • wordpress制作培训网站太原网站开发定制
  • 网站建设带主机外贸软件建设
  • 昆明网站建设推荐谁好呢外包做的网站
  • 为学校网站建设ip域名查询网站入口
  • 河南省建设协会网站做网站制作赚钱吗
  • 网站怎么查询注册商大庆建网站
  • 设计需要看的网站有哪些哪里有免费做网站
  • joomla 网站 html 空wordpress 汉语字体