面试题
1.通过装饰器装饰的函数如何不改变其自身相关信息
答:可以使用模块 functools
中的 wraps
装饰器来装饰自定义装饰器的内函数。(有点绕口,下面查看示例)正常情况下被装饰的函数,虽然可以使用原函数名进行调用,但实际上此时的函数名指向了装饰器的内函数,可以通过打印 __name__
以及 __doc__
属性来验证。使用 wraps
后,被装饰的函数相关信息就会全部保留。
from functools import wraps # <-- 导入模块 # 定义一个装饰器 def eg_decorator(func): @wraps(func) # <-- 使用装饰器 def wrapper(*args, **kwds): """内函数""" print('我是装饰器啊...') return func(*args, **kwds) return wrapper # 使用定义的装饰器装饰函数 @eg_decorator def noname(): """ 函数自身 :return: 无返回 """ print('我是示例函数噻...') # 调用函数 noname() # 打印函数的 __name__ 属性 print(noname.__name__) # 打印函数的文档字符串 print(noname.__doc__)
然后查看输出结果进行验证:
我是装饰器啊... 我是示例函数噻... noname 函数自身 :return: 无返回
2.手写一个类,可以连接 redis 并实现 get 和 set 方法
import redis class RedisTest(object): def __init__(self, ip: str, port: int): self.conn = redis.Redis(host=ip, port=port, decode_responses=False) def get(self, key: str) -> str: return self.conn.get(key) def set(self, key, value): self.conn.set(key, value) redis_obj = RedisTest('127.0.0.1', 6379) redis_obj.set('name', 'ethanyan') name = redis_obj.get('name') print(name) # b'ethanyan'
3.elasticsearch 二次查询
答:elasticsearch
中可以通过 terms lookup
(跨索引查询)实现二次查询。例如查询某个用户关注列表中所有用户发表的主帖,如果是拆开查询,需要两步:先查出关注列表,再查询发表的主帖。而使用 terms lookup
查询只需要一步即可。
POST my-index-post/_search { "query": { "bool": { "must": [ { "terms": { "user_item_id": { "index": "my-index-user", "type": "user", "id": "0f42d65be1f5287e1c9c26e3728814aa", "path": "friends" } } } ] } } }
此示例来源于 https://www.playpi.org/2019060601.html
。
分析一下上面的 DSL
语句,它先利用 id
查询用户索引my-index-user/user
,返回关注列表 friends
中的所有 item_id
;然后利用上一步骤中返回的 itemt_id
列表,去匹配主帖的 user_item_id
字段,从而查询所有的主帖。
4.语法糖 property
答:python
中提供了一个装饰器 property
,在使用对象的私有属性时,可以不再使用属性的函数的调用方式,而像普通的公有属性一样去使用属性。简单进行举例,假设一个类中有一些私有属性,我们可以通过 set/get
方法,专门来为这些私有属性提供访问接口以及设置属性时的校验操作,增加程序的健壮性和安全性,但这样操作后接口调用比较繁琐,使用装饰器 property
后,再去设置或访问私有属性时,如同正常公有属性一样实现无感操作。
如果需要设计一个「银行账户类」,只需提供姓名和余额的访问与设置操作即可。首先看一下传统的 set/get
方法:
class Account(object): def __init__(self, name, money): # 为了安全性考虑,不能向外部直接暴露私密信息 self.__name = name # 帐户人姓名 self.__balance = money # 帐户余额 def get_name(self): return self.__name def set_balance(self, money): if isinstance(money, int): if money >= 0: self.__balance = money else: raise ValueError('输入的金额不正确') else: raise ValueError('输入的金额不是数字') def get_balance(self): return self.__balance
如果需要设置账户的余额,就需要调用方法 set_balance
,而使用装饰器后,则大大进行了简化:
class Account(object): def __init__(self, name, money): self.__name = name # 帐户人姓名 self.__balance = money # 帐户余额 # property 只能对获取方法装饰,并且获取方法不需要再写 get @property def name(self): return self.__name # 如果 property 装饰的属性还有 set 方法,需要写到 get方法后定义 @property def balance(self): return self.__balance # 实现 set 方法, 格式: @xxx.setter ,xxx 要和property装饰的方法名一致 @balance.setter def balance(self, money): if isinstance(money, int): if money >= 0: self.__balance = money else: raise ValueError('输入的金额不正确') else: raise ValueError('输入的金额不是数字') # 可以像共有属性一样进行访问与设置 ac = Account('tom', 10) print(ac.name) print(ac.balance) ac.balance = 1000 print(ac.balance)
5.简单介绍一下 with 语句
答:with
语句是一种简化的语法,适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的「清理」操作,释放资源。比如在连接数据库并进行相关操作后可以自动关闭,无需手动调用。其原理为自动调用了上下文管理器中的关闭语句,内部主要有两个方法构成 __enter__
和 __exit__
。__enter__
方法会在执行 with
后面的语句时执行,一般用来处理操作前的内容。比如一些创建对象,初始化等。__exit__
方法会在 with
内的代码执行完毕后执行,一般用来处理一些善后收尾工作,比如文件的关闭,数据库的关闭等。