l = [1, 2, 'hello', 'world'] # 列表中同时含有int和string类型的元素
l
[1, 2, 'hello', 'world']
tup = ('jason', 22) # 元组中同时含有int和string类型的元素
tup
('jason', 22)
思考题
1. 想创建一个空的列表,我们可以用下面的 A、B 两种方式,请问它们在效率上有什么区别吗?我们应该优先考虑使用哪种呢?可以说说你的理由。
# 创建空列表
# option A:empty_list = list()
# option B:empty_list = []
第二种效率更高,通过[]关键字可以直接调用底层C代码创建一个列表,而第一种方式还需要初始化一个list实例才能创建一个列表
2. 你在平时的学习工作中,是在什么场景下使用列表或者元组呢?欢迎留言和我分享。
列表:当需要根据请求参数返回查询对象的集合时,返回的对象数量是可变的,此时需要使用的是list
元组:当定义关系型数据库对象model时,需要在Meta类中定义返回数据对象的字段名集合,一般是不会改变的,所以使用元组
基本用法
def my_decorator(func):
def wrapper():
print('wrapper of decorator')
func()
return wrapper
@my_decorator # 等同于 greet = my_decorator(greet)
def greet():
print('hello world')
greet()
有参数需要传递给装饰器怎么办?一个简单的办法,是可以在对应的装饰器函数 wrapper() 上,加上相应的参数,比如:
def my_decorator(func):
def wrapper(*args, **kwargs):
print('wrapper of decorator')
func(*args, **kwargs)
return wrapper
带有自定义参数的装饰器
def repeat(num):
def my_decorator(func):
def wrapper(*args, **kwargs):
for i in range(num):
print('wrapper of decorator')
func(*args, **kwargs)
return wrapper
return my_decorator
@repeat(4)
def greet(message):
print(message)
greet('hello world')
# 输出:
wrapper of decorator
hello world
wrapper of decorator
hello world
wrapper of decorator
hello world
wrapper of decorator
hello world
类装饰器
类装饰器主要依赖于函数__call__(),每当你调用一个类的示例时,函数__call__()就会被执行一次。
class Count:
def __init__(self, func): #执行Count(*args,**kwargs)时调用
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):#执行Count(*args,**kwargs)()时调用
self.num_calls += 1
print('num of calls is: {}'.format(self.num_calls))
return self.func(*args, **kwargs)
@Count
def example():
print("hello world")
example()
# 输出
num of calls is: 1
hello world
example()
# 输出
num of calls is: 2
hello world
...
事实上,meta-class 的 meta 这个词根,起源于希腊语词汇 meta,包含下面两种意思:
“Beyond”,例如技术词汇 metadata,意思是描述数据的超越数据;
“Change”,例如技术词汇 metamorphosis,意思是改变的形态。
metaclass,一如其名,实际上同时包含了“超越类”和“变形类”的含义,完全不是“基本类”的意思。所以,要深入理解 metaclass,我们就要围绕它的超越变形特性。
metaclass 的超越变形特性有什么用?
YAML是一个家喻户晓的 Python 工具,可以方便地序列化 / 逆序列化结构数据。
YAMLObject 的一个超越变形能力,就是它的任意子类支持序列化和反序列化(serialization & deserialization)。比如说下面这段代码:
class Monster(yaml.YAMLObject):
yaml_tag = u'!Monster'
def __init__(self, name, hp, ac, attacks):
self.name = name
self.hp = hp
self.ac = ac
self.attacks = attacks
def __repr__(self):
return "%s(name=%r, hp=%r, ac=%r, attacks=%r)" % (
self.__class__.__name__, self.name, self.hp, self.ac,
self.attacks)
yaml.load_all("""
--- !Monster
name: Cave spider
hp: [2,6] # 2d6
ac: 16
attacks: [BITE, HURT]
""")
Monster(name='Cave spider', hp=[2, 6], ac=16, attacks=['BITE', 'HURT'])
print yaml.dump(Monster(
name='Cave lizard', hp=[3,6], ac=16, attacks=['BITE','HURT']))
# 输出
!Monster
ac: 16
attacks: [BITE, HURT]
hp: [3, 6]
name: Cave lizard
调用统一的 yaml.load(),就能把任意一个 yaml 序列载入成一个 Python Object;
而调用统一的 yaml.dump(),就能把一个 YAMLObject 子类序列化。
对于 load() 和 dump() 的使用者来说,他们完全不需要提前知道任何类型信息,这让超动态配置编程成了可能。在我的实战经验中,许多大型项目都需要应用这种超动态配置的理念。
对于 YAML 的使用者,这一点也很方便,你只要简单地继承 yaml.YAMLObject,就能让你的 Python Object 具有序列化和逆序列化能力。是不是相比普通 Python 类,有一点“变态”,有一点“超越”?
Python 底层语言设计层面是如何实现 metaclass 的?刚才我们提到,metaclass 能够拦截 Python 类的定义。它是怎么做到的?要理解 metaclass 的底层原理,你需要深入理解 Python 类型模型。下面,我将分三点来说明。
第一,所有的 Python 的用户定义类,都是 type 这个类的实例。可能会让你惊讶,事实上,类本身不过是一个名为 type 类的实例。在 Python 的类型世界里,type 这个类就是造物的上帝。这可以在代码中验证:# Python 3和Python 2类似class MyClass: passinstance = MyClass()type(instance)# 输出
第二,用户自定义类,只不过是 type 类的__call__运算符重载。当我们定义一个类的语句结束时,真正发生的情况,是 Python 调用 type 的__call__运算符。简单来说,当你定义一个类时,写成下面这样时:
第三,metaclass 是 type 的子类,通过替换 type 的__call__运算符重载机制,“超越变形”正常的类。其实,理解了以上几点,我们就会明白,正是 Python 的类创建机制,给了 metaclass 大展身手的机会。一旦你把一个类型 MyClass 的 metaclass 设置成 MyMeta,MyClass 就不再由原生的 type 创建,而是会调用 MyMeta 的__call__运算符重载。