高级特性
70. 函数装饰器有什么作用?请列举说明?
答: 装饰器就是一个函数,它可以在不需要做任何代码变动的前提下给一个函数增加额外功能,启动装饰的效果。 它经常用于有切面需求的场景,
比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。 下面是一个日志功能的装饰器
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 _wrap
return decorate
@log("info")
def foo(a, b, c):
print(a + b + c)
print("in foo")
decorate=decorate(foo)
if __name__ == '__main__':
foo(1, 2, 3)
decorate()
71. Python垃圾回收机制?
答: Python不像C++,Java等语言一样,他们可以不用事先声明变量类型而直接对变量进行赋值。对Python语言来讲,对象的类型和内存都是在运行时确定的。
这也是为什么我们称Python语言为动态类型的原因。主要体现在下面三个方法:
1. 引用计数机制
2. 标记 - 清除
3. 分代回收
72.魔法函数call怎么使用?
答: call可以把类实例当做函数调用。 使用示例如下
class Bar:
def __call__(self, *args, **kwargs):
print('in call')
if __name__ == '__main__':
b = Bar()
b()
73. 如何判断一个对象是函数还是方法?
答: 看代码已经结果就懂了
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))
if __name__ == '__main__':
run()
'''
foo是函数,False
foo是方法,True
foo2是函数,True
foo2是方法,False
'''
74. @ 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()
75. Python中的接口如何实现?
答: 接口提取了一群类共同的函数,可以把接口当做一个函数的集合,然后让子类去实现接口中的函数。但是在Python中根本就没有一个叫做interface的关键字,如果非要去模仿接口的概念,可以使用抽象类来实现。抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化。使用abc模块来实现抽象类。
76. Python中的反射了解么?
答: Python的反射机制设定较为简单,一共有四个关键函数分别是getattr、hasattr、setattr、delattr。
77. metaclass作用?以及应用场景?
答: metaclass即元类,metaclass是类似创建类的模板,所有的类都是通过他来create的(调用new),这使得你可以自由的控制创建类的那个过程,实现你所需要的功能。
我们可以使用元类创建单例模式和实现ORM模式。
78. hasattr()、getattr()、setattr()的用法
答: 这三个方法属于Python的反射机制里面的
- 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()
79. 请列举你知道的Python的魔法方法及用途。
答:
-
-
__init__
:类的初始化方法。它获取任何传给构造器的参数(比如我们调用x = SomeClass(10, ‘foo’)
,__init__
就会接到参数10和 ‘foo’ 。__init__
在Python的类定义中用的最多。
-
-
-
__new__
:__new__
是对象实例化时第一个调用的方法,它只取下cls参数,并把其他参数传给__init__
。
__new__
很少使用,但是也有它适合的场景,尤其是当类继承自一个像元组或者字符串这样不经常改变的类型的时候.
-
-
-
__del__
:__new__
和__init__
是对象的构造器,__del__
是对象的销毁器。它并非实现了语句del x(因此该语句不等同于x.__del__())
。
而是定义了当对象被垃圾回收时的行为。当对象需要在销毁时做一些处理的时候这个方法很有用,比如socket对象、文件对象。
但是需要注意的是,当Python解释器退出但对象仍然存活的时候,__del__
并不会执行。 所以养成一个手工清理的好习惯是很重要的,比如及时关闭连接。
-
80. 如何知道一个Python对象的类型?
答: 可以通过type方法
81. Python的传参是传值还是传址?
答: Python中的传参即不是传值也不是传地址,传的是对象的引用。
82. 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)
83. 简述any()和all()方法
答:
- any(x): 判断x对象是否为空对象,如果都为空、0、false,则返回false,如果不都为空、0、false,则返回true。
- all(x): 如果all(x)参数x对象的所有元素不为0、''、False或者x为空对象,则返回True,否则返回False。
84. filter方法求出列表所有奇数并构造新列表,a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
答:
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(list(filter(lambda x: x % 2 == 1, a)))
其实现在不推荐使用filter, map等方法了,一般列表生成式就可以搞定了。
85. 什么是猴子补丁?
答: 猴子补丁(monkeypatching): 在运行时动态修改模块、类或函数,通常是添加功能或修正缺陷。猴子补丁在代码运行时内存中)发挥作用,不会修改源码,因此只对当前运行的程序实例有效。
因为猴子补丁破坏了封装,而且容易导致程序与补丁代码的实现细节紧密耦合,所以被视为临时的变通方案,不是集成代码的推荐方式。大概是下面这样的一个效果
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()
86. 在Python中是如何管理内存的?
答:
- 垃圾回收: Python不像C + +,Java等语言一样,他们可以不用事先声明变量类型而直接对变量进行赋值。对Python语言来讲,对象的类型和内存都是在运行时确定的。
- 这也是为什么我们称Python语言为动态类型的原因(这里我们把动态类型可以简单的归结为对变量内存地址的分配是在运行时自动判断变量类型并对变量进行赋值。
- 引用计数: Python采用了类似Windows内核对象一样的方式来对内存进行管理。每一个对象,都维护这一个对指向该对对象的引用的计数。
当变量被绑定在一个对象上的时候,该变量的引用计数就是1,(还有另外一些情况也会导致变量引用计数的增加),系统会自动维护这些标签,并定时扫描,当某标签的引用计数变为0的时候,该对就会被回收。 - 内存池机制: Python的内存机制以金字塔行,1、2层主要有操作系统进行操作;第0层是C中的malloc,free等内存分配和释放函数进行操作;
第1层和第2层是内存池,有Python的接口函数PyMem_Malloc函数实现,当对象小于256K时有该层直接分配内存;第3层是最上层,也就是我们对Python对象的直接操作在C中如果频繁的调用malloc与free时,
是会产生性能问题的.再加上频繁的分配与释放小块的内存会产生内存碎片。Python在这里主要干的工作有:如果请求分配的内存在1~256字节之间就使用自己的内存管理系统, 否则直接使用malloc。
这里还是会调用malloc分配内存,但每次会分配一块大小为256k的大块内存。经由内存池登记的内存到最后还是会回收到内存池,并不会调用C的free释放掉以便下次使用。
对于简单的Python对象,例如数值、字符串,元组(tuple不允许被更改)采用的是复制的方式(深拷贝?),也就是说当将另一个变量B赋值给变量A时,虽然A和B的内存空间仍然相同,但当A的值发生变化时,会
重新给A分配空间,A和B的地址变得不再相同。
87. 当退出Python时是否释放所有内存分配?
答: 不是的,循环引用其他对象或引用自全局命名空间的对象的模块,在Python退出时并非完全释放。另外,也不会释放c库保留的内存部分