Python 和 JavaScript 在笔者看来是很相似的语言,本文归纳了 Python 的各种 tricks。
函数
匿名函数
函数的简化写法,配合 map、filter、reduce 等高阶函数实现函数式编程
Copy # def foo(parameters):
# return expression
foo = lambda parameters : expression
map - 映射
Copy numbers = [ 1 , 2 , 3 , 4 , 5 ]
list ( map ( lambda e : e ** 2 , numbers))
# [1, 4, 9, 16, 25]
filter - 过滤
Copy values = [ None , 0 , '' , True , 'alphardex' , 666 ]
list ( filter ( lambda e :e, values))
# [True, "alphardex", 666]
reduce - 归并
Copy from functools import reduce
numbers = [ 1 , 2 , 3 , 4 , 5 ]
reduce ( lambda acc , cur : acc + cur, numbers)
# 15
星号和双星号
数据容器的合并
Copy l1 = [ 'kaguya' , 'miyuki' ]
l2 = [ 'chika' , 'ishigami' ]
[ * l1 , * l2]
# ['kaguya', 'miyuki', 'chika', 'ishigami']
d1 = { 'name' : 'rimuru' }
d2 = { 'kind' : 'slime' }
{ ** d1 , ** d2 }
# {'name': 'rimuru', 'kind': 'slime'}
函数参数的打包与解包
Copy # 打包
def foo ( * args ):
print (args)
foo ( 1 , 2 )
# (1, 2)
def bar ( ** kwargs ):
print (kwargs)
bar (name = 'hayasaka' , job = 'maid' )
# {'name': 'hayasaka', 'job': 'maid'}
# 解包
t = ( 10 , 3 )
quotient , remainder = divmod ( * t)
quotient
# 商:3
remainder
# 余:1
数据容器
列表
迭代
Copy li = [ 'umaru' , 'ebina' , 'tachibana' ]
for e in li :
print (li)
# umaru ebina tachibana
同时迭代元素与其索引
用 enumerate 即可
Copy li = [ 'umaru' , 'ebina' , 'tachibana' ]
print ([ f ' { i + 1} . { elem } ' for i, elem in enumerate (li)])
# ['1. umaru', '2. ebina', '3. tachibana']
同时迭代 2 个以上的可迭代对象
用 zip 即可
Copy subjects = ( 'nino' , 'miku' , 'itsuki' )
predicates = ( 'saikou' , 'ore no yome' , 'is sky' )
print ([ f ' { s } { p } ' for s, p in zip (subjects, predicates)])
# ['nino saikou', 'miku ore no yome', 'itsuki is sky']
测试是否整体/部分满足条件
all 测试所有元素是否都满足于某条件,any 则是测试部分元素是否满足于某条件
Copy all (e < 20 for e in [ 1 , 2 , 3 , 4 , 5 ])
# True
any (e % 2 == 0 for e in [ 1 , 3 , 4 , 5 ])
# True
解包
最典型的例子就是 2 数交换
Copy a , b = b , a
# 等价于 a, b = (b, a)
用星号运算符解包可以获取剩余的元素
Copy first , * rest = [ 1 , 2 , 3 , 4 ]
first
# 1
rest
# [2, 3, 4]
用下划线可以忽略某个变量
Copy filename , _ = 'eroge.exe' . split ( '.' )
filename
# 'eroge'
字典
迭代
Copy d = { 'name' : 'sekiro' , 'hobby' : 'blacksmithing' , 'tendency' : 'death' }
[key for key in d . keys () ]
# ['name', 'hobby', 'tendency']
[value for value in d . values () ]
# ['sekiro', 'blacksmithing', 'death']
[ f ' { key } : { value } ' for key , value in d . items () ]
# ['name: sekiro', 'hobby: blacksmithing', 'tendency: death']
排序
Copy data = [ { 'rank' : 2 , 'author' : 'alphardex' }, { 'rank' : 1 , 'author' : 'alphardesu' } ]
data_by_rank_desc = sorted (data, key =lambda d : d[ 'rank' ], reverse = True )
# [{'rank': 2, 'author': 'alphardex'}, {'rank': 1, 'author': 'alphardesu'}]
缺失键处理
get 返回键值,如果键不在字典中,将会返回一个默认值
Copy d = { 'name' : 'okabe rintaro' , 'motto' : 'elpsycongroo' }
d . get ( 'job' , 'mad scientist' )
# mad scientist
setdefault 返回键值,如果键不在字典中,将会添加它并设置一个默认值
Copy d = { 'name' : 'okabe rintaro' , 'motto' : 'elpsycongroo' }
# if 'job' not in d:
# d['job'] = 'mad scientist'
d . setdefault ( 'job' , 'mad scientist' )
# mad scientist
d
# {'name': 'okabe rintaro', 'motto': 'elpsycongroo', 'job': 'mad scientist'}
语言专属特性
推导式
推导式是一种快速构建可迭代对象的方法,因此凡是可迭代的对象都支持推导式
列表推导式
获取 0-10 内的所有偶数
Copy even = [i for i in range ( 10 ) if not i % 2 ]
even
# [0, 2, 4, 6, 8]
字典推导式
将装满元组的列表转换为字典
Copy SEIREI = [(0, 'takamiya mio'), (1, 'tobiichi origami'), (2, 'honjou nia'), (3, 'tokisaki kurumi'), (4, 'yoshino'), (5, 'itsuka kotori'), (6, 'hoshimiya mukuro'), (7, 'natsumi'), (8, 'yamai'), (9, 'izayoi miku'), (10, 'yatogami tohka')]
seirei_code = { seirei : code for code , seirei in SEIREI }
seirei_code
# {'takamiya mio': 0, 'tobiichi origami': 1, 'honjou nia': 2, 'tokisaki kurumi': 3, 'yoshino': 4, 'itsuka kotori': 5, 'hoshimiya mukuro': 6, 'natsumi': 7, 'yamai': 8, 'izayoi miku': 9, 'yatogami tohka': 10}
{ code : seirei . upper () for seirei , code in seirei_code . items () if code > 6 }
# {7: 'NATSUMI', 8: 'YAMAI', 9: 'IZAYOI MIKU', 10: 'YATOGAMI TOUKA'}
集合推导式
求所有数字的平方并去除重复元素
Copy { x ** 2 for x in [ 1 , 2 , 2 , 3 , 3 ] }
# {1, 4, 9}
生成器表达式
求 0-10 内的所有偶数的和
Copy even_sum_under_10 = sum (i for i in range ( 11 ) if not i % 2 )
even_sum_under_10
# 30
装饰器
装饰器是一个可调用的对象,顾名思义它能够装饰在某个可调用的对象上,给它增加额外的功能
常用于缓存、权限校验、日志记录、性能测试、事务处理等场景
以下实现了一个简单的日志装饰器,能打印出函数的执行时间、函数名、函数参数和执行结果
Copy import time
from functools import wraps
def clock ( func ):
@wraps (func) # 防止被装饰函数的属性被wrapper覆盖
def wrapper ( * args , ** kwargs ):
t0 = time . perf_counter ()
result = func ( * args, ** kwargs) # 由于闭包,wrapper函数包含了自由变量func
elapsed = time . perf_counter () - t0
name = func . __name__
args = ', ' . join ( repr (arg) for arg in args)
kwargs = ', ' . join ( f ' { k } = { w } ' for k, w in sorted (kwargs. items ()))
all_args_str = ', ' . join (astr for astr in [args_str, kwargs_str] if astr)
print ( f '[ { elapsed :.8f } s] { name } ( { all_args_str } ) -> { result } ' )
return result
return wrapper # 返回内部函数,取代被装饰的函数
@clock
def factorial ( n : int ) -> int :
return 1 if n < 2 else n * factorial (n - 1 )
factorial ( 5 )
# [0.00000044s] factorial(1) -> 1
# [0.00011111s] factorial(2) -> 2
# [0.00022622s] factorial(3) -> 6
# [0.00030844s] factorial(4) -> 24
# [0.00042222s] factorial(5) -> 120
# 120
如果想让装饰器能接受参数,那就要再嵌套一层
Copy import time
from functools import wraps
DEFAULT_FMT = '[ {elapsed :.8f } s] {name} ( {all_args_str} ) -> {result} '
def clock ( fmt = DEFAULT_FMT):
def decorate ( func ):
@wraps (func)
def wrapper ( * args , ** kwargs ):
t0 = time . perf_counter ()
result = func ( * args, ** kwargs)
elapsed = time . perf_counter () - t0
name = func . __name__
args_str = ', ' . join ( repr (arg) for arg in args)
kwargs_str = ', ' . join ( f ' { k } = { w } ' for k, w in sorted (kwargs. items ()))
all_args_str = ', ' . join (astr for astr in [args_str, kwargs_str] if astr)
print (fmt. format ( ** locals ()))
return result
return wrapper
return decorate
@clock ()
def factorial_default_fmt ( n : int ) -> int :
return 1 if n < 2 else n * factorial_default_fmt (n - 1 )
@clock ( ' {name} : {elapsed} s' )
def factorial_customed_fmt ( n : int ) -> int :
return 1 if n < 2 else n * factorial_customed_fmt (n - 1 )
factorial_default_fmt ( 3 )
# [0.00000044s] factorial_default_fmt(1) -> 1
# [0.00009600s] factorial_default_fmt(2) -> 2
# [0.00018133s] factorial_default_fmt(3) -> 6
# 6
factorial_customed_fmt ( 3 )
# factorial_customed_fmt: 4.444450496521313e-07s
# factorial_customed_fmt: 9.733346314533264e-05s
# factorial_customed_fmt: 0.0001831113553407704s
# 6
在 django 中,可以通过装饰器对函数视图进行功能增强(比如@login_required 进行登录的权限校验,@cache_page 进行视图的缓存等)
上下文管理器
用于资源的获取与释放,以代替 try-except 语句
常用于文件 IO,锁的获取与释放,数据库的连接与断开等
Copy # try:
# f = open(input_path)
# data = f.read()
# finally:
# f.close()
with open (input_path) as f :
data = f . read ()
其实在 pathlib 里已经给我们封装好了文件 IO 方法
Copy # with open('file') as i:
# data = i.read()
from pathlib import Path
data = Path ( 'file' ). read_text ()
至于上下文管理器的实现,可以用@contextmanager
Copy from contextlib import contextmanager
@contextmanager
def open_write ( filename ):
try :
f = open (filename, 'w' )
yield f
finally :
f . close ()
with open_write ( 'onegai.txt' ) as f :
f . write ( 'Dagakotowaru!' )
多重继承
在 django 中经常要处理类的多重继承的问题,这时就要用到 super 函数
如果单单认为 super 仅仅是“调用父类的方法”,那就错了
在继承单个类的情况下,可以认为 super 是调用父类的方法(ES6 里面亦是如此)
但多重继承就不一样了,因为方法名可能会有冲突,所以 super 就不能单指父类了
在 Python 中,super 指的是 MRO 中的下一个类 ,用来解决多重继承时父类的查找问题
MRO 是啥?Method Resolution Order(方法解析顺序)
看完下面的例子,就会理解了
Copy class A :
def __init__ ( self ):
print ( 'A' )
class B ( A ):
def __init__ ( self ):
print ( 'enter B' )
super (). __init__ ()
print ( 'leave B' )
class C ( A ):
def __init__ ( self ):
print ( 'enter C' )
super (). __init__ ()
print ( 'leave C' )
class D ( B , C ):
pass
d = D ()
# enter B
# enter C
# A
# leave C
# leave B
print (d. __class__ . __mro__ )
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
首先,因为 D 继承了 B 类,所以调用 B 类的__init__,打印了enter B
打印enter B
后的 super 寻找 MRO 中的 B 的下一个类,也就是 C 类,并调用其__init__,打印enter C
打印enter C
后的 super 寻找 MRO 中的 C 的下一个类,也就是 A 类,并调用其__init__,打印A
打印A
后回到 C 的__init__,打印leave C
打印leave C
后回到 B 的__init__,打印leave B
特殊方法
在 django 中,定义 model 的时候,希望 admin 能显示 model 的某个字段而不是 XXX Object,那么就要定义好__str__
每当你使用一些内置函数时,都是在调用一些特殊方法,例如 len()调用了__len__(), str()调用__str__()等
以下实现一个 2d 数学向量类,里面有多个特殊方法
Copy from math import hypot
class Vector2d :
# 限制允许绑定的属性
__slots__ = ( '__x' , '__y' )
# 实例创建
def __init__ ( self , x , y ):
self . __x = float (x)
self . __y = float (y)
# 前双下划线是私有属性,property装饰是只读属性
@ property
def x ( self ):
return self . __x
@ property
def y ( self ):
return self . __y
# 可迭代对象
def __iter__ ( self ):
yield from (self . x , self . y)
# 字符串表示形式
def __repr__ ( self ) -> str :
return f ' {type (self). __name__} ( { self . x } , { self . y } )'
# 数值转换 - 绝对值
def __abs__ ( self ) -> float :
return hypot (self.x, self.y)
# 数值转换 - 布尔值
def __bool__ ( self ) -> bool :
return bool ( abs (self))
# 算术运算符 - 加
def __add__ ( self , other ):
x = self . x + other . x
y = self . y + other . y
return Vector2d (x, y)
# 算术运算符 - 乘
def __mul__ ( self , scalar : float ):
return Vector2d (self.x * scalar, self.y * scalar)
# 比较运算符 - 相等
def __eq__ ( self , other ):
return tuple (self) == tuple (other)
# 可散列
def __hash__ ( self ):
return hash (self.x) ^ hash (self.y)
v = Vector2d ( 3 , 4 )
# __slots__限制了允许绑定的属性,只能是x或y
v . z = 1
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# AttributeError: 'Vector2d' object has no attribute 'z'
# 由于x属性只读,因此无法再次赋值
v . x = 1
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# AttributeError: can't set attribute
# iter(v) => v.__iter__()
x , y = v
# x为3, y为4
# repr(v) => v.__repr__()
v
# Vector2d(3, 4)
# abs(v) => v.__abs__()
abs (v)
# 5.0
# bool(v) => v.__bool__()
bool (v)
# True
# v1 + v2 => v1.__add__(v2)
v1 = Vector2d ( 1 , 2 )
v2 = Vector2d ( 3 , 4 )
v1 + v2
# Vector2d(4, 6)
# v * 3 => v.__mul__(3)
v * 3
# Vector2d(9, 12)
# v1 == v2 => v1.__eq__(v2)
v1 = Vector2d ( 1 , 2 )
v2 = Vector2d ( 1 , 2 )
v1 == v2
# True
# hash(v) => v.__hash__()
hash (v)
# 7
v1 = Vector2d ( 1 , 2 )
v2 = Vector2d ( 3 , 4 )
set ([v1, v2])
# {Vector2d(1.0, 2.0), Vector2d(3.0, 4.0)}
如果把 Vector 改造为多维向量呢?关键就是要实现序列协议(__len__和__getitem__)
协议:本质上是鸭子类型 语言使用的非正式接口
不仅如此,还要实现多分量的获取以及散列化
Copy from array import array
import reprlib
import math
import numbers
import string
from functools import reduce
from operator import xor
from itertools import zip_longest
import numbers
from fractions import Fraction as F
class Vector :
typecode = 'd'
shortcut_names = 'xyzt'
def __init__ ( self , components ):
self . _components = array (self.typecode, components)
def __iter__ ( self ):
return iter (self._components)
def __repr__ ( self ):
components = reprlib . repr (self._components)
components = components [ components . find ( '[' ): - 1 ]
return f ' {type (self). __name__} ( { components } )'
def __str__ ( self ):
return str ( tuple (self))
def __eq__ ( self , other ):
return tuple (self) == tuple (other)
def __bool__ ( self ):
return bool ( abs (self))
# 序列协议 - 获取长度
def __len__ ( self ):
return len (self._components)
# 序列协议 - 索引取值
def __getitem__ ( self , index ):
cls = type (self) # Vector
if isinstance (index, slice ): # 索引是slice对象,则返回Vector实例
return cls (self._components[index])
elif isinstance (index, numbers.Integral): # 索引是整数类型,则返回_components中对应的数字
return self . _components [ index ]
else :
raise TypeError ( f ' { cls. __name__} indices must be integers.' )
# 属性访问,获取分量的值
def __getattr__ ( self , name ):
cls = type (self)
if len (name) == 1 :
pos = cls . shortcut_names . find (name)
if 0 <= pos < len (self._components):
return self . _components [ pos ]
raise AttributeError ( f ' { cls. __name__} has no attribute { name } ' )
# 属性设置,给分量设值时会抛出异常,使向量是不可变的
def __setattr__ ( self , name , value ):
cls = type (self)
if len (name) == 1 :
if name in string . ascii_lowercase :
raise AttributeError ( f "can't set attribute 'a' to 'z' in { cls. __name__} " )
super (). __setattr__ (name, value)
# 比较所有分量,都相等才算两向量相等
def __eq__ ( self , other ):
return len (self) == len (other) and all (a == b for a, b in zip (self, other))
# 散列化
def __hash__ ( self ):
hashes = map ( hash , self._components)
return reduce (xor, hashes, 0 )
# 绝对值
def __abs__ ( self ):
return math . sqrt ( sum (x ** 2 for x in self))
# 取正
def __pos__ ( self ):
return Vector (self)
# 取负
def __neg__ ( self ):
return Vector ( - x for x in self)
# 加 (减法__sub__的实现与之类似,略)
def __add__ ( self , other ):
try :
return Vector (a + b for a, b in zip_longest (self, other, fillvalue = 0.0 ))
except TypeError :
return NotImplemented
# 反向加(a+b中,如果a没有__add__或返回NotImplemented,则检查b是否有__radd__,有则调用之)
def __radd__ ( self , other ):
return self + other
# 乘 (除法__truediv__的实现与之类似,略)
def __mul__ ( self , scalar ):
return Vector (n * scalar for n in self) if isinstance (scalar, numbers.Real) else NotImplemented
# 反向乘
def __rmul__ ( self , scalar ):
return self * scalar
# 中缀运算符@ - 点积
def __matmul__ ( self , other ):
try :
return sum (a * b for a, b in zip (self, other))
except TypeError :
return NotImplemented
# 反向中缀运算符@
def __rmatmul__ ( self , other ):
return self @ other
v = Vector ( range ( 7 ))
v
# Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
v [ 1 : 3 ]
# Vector([1.0, 2.0])
v [ - 1 ]
# 6.0
v [ 1 , 3 ]
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# File "<stdin>", line 39, in __getitem__
# TypeError: Vector indices must be integers.
v . x , v . y , v . z
# (0.0, 1.0, 2.0)
v . x = 1
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# File "<stdin>", line 62, in __setattr__
# AttributeError: can't set attribute 'a' to 'z' in Vector
v1 = Vector (( 3 , 4 , 5 ))
v2 = Vector (( 6 , 7 ))
v1 == v2
# False
set ([v1, v2])
# {Vector([6.0, 7.0]), Vector([3.0, 4.0, 5.0])}
abs (v)
# 9.539392014169456
+ v
# Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
- v
# Vector([-0.0, -1.0, -2.0, -3.0, -4.0, ...])
v1 + v2
# Vector([9.0, 11.0, 5.0])
v * 3
# Vector([0.0, 3.0, 6.0, 9.0, 12.0, ...])
v * F ( 1 , 2 )
# Vector([0.0, 0.5, 1.0, 1.5, 2.0, ...])
v1 @ v2
# 46.0
想了解所有的特殊方法可查阅官方文档 ,以下列举些常用的:
Copy 字符串表示形式: __str__ , __repr__
数值转换: __abs__ , __bool__ , __int__ , __float__ , __hash__
集合模拟: __len__ , __getitem__ , __setitem__ , __delitem__ , __contains__
迭代枚举: __iter__ , __reversed__ , __next__
可调用模拟: __call__
实例创建与销毁: __init__ , __del__
属性访问: __getattr__ , __setattr__
运算符相关: __add__ , __radd__ , __mul__ , __rmul__ , __matmul__ , __rmatmul__ , ...
类方法和静态方法
@classmethod 是类方法,它定义操作类的方法,也就是说会将类绑定给方法,而不是实例
@staticmethod 是静态方法,啥都不绑定,一般用来给类绑定各种工具方法(不涉及对实例和类的操作)
在 django 中,我们经常要在视图函数中对模型类进行各种查询
然而,很多查询都是重复的代码,根据 DRY 原则,它们都是可以被封装的
那么,如果我们要给模型类封装一些查询操作,就要用到@classmethod
以下是 Post 类,里面定义了 latest_posts 方法用来获取最新的几个 Post
这样在视图函数中,就能直接调用该方法进行查询,节省了不少代码
Copy class Post ( models . Model ):
STATUS_NORMAL = 1
STATUS_DELETE = 0
STATUS_DRAFT = 2
STATUS_ITEMS = (
(STATUS_NORMAL , '正常' ) ,
(STATUS_DELETE , '删除' ) ,
(STATUS_DRAFT , '草稿' ) ,
)
...
status = models . PositiveIntegerField ( _ ( "状态" ), choices = STATUS_ITEMS, default = STATUS_NORMAL)
created_time = models . DateTimeField ( _ ( "创建时间" ), auto_now_add = True )
...
@ classmethod
def latest_posts ( cls , limit = None ):
queryset = cls . objects . filter (status = cls.STATUS_NORMAL). order_by ( '-created_time' )
if limit :
queryset = queryset [: limit ]
return queryset
描述符
实现了__set__或__get__协议的类就是描述符
set 和 get 代表存和取,因此描述符是一种对多个类属性运用相同存取逻辑的一种方式
例如 django 的 ORM 中的字段类型是描述符,用来把数据库记录中的字段数据与 Python 对象的属性对应起来
以下实现一个简单的描述符类,用来在读写属性时验证属性的正确性
Copy class Validator :
def __init__ ( self , storage_name ):
self . storage_name = storage_name
def __set__ ( self , instance , value ):
if not isinstance (value, int ):
raise ValueError ( 'Value must be an integer' )
if value > 200 :
raise ValueError ( 'Value must be under 200' )
class Person :
age = Validator ( 'age' )
def __init__ ( self , age ):
self . age = age
person = Person (age = 100 )
person . age = 'young'
# Traceback (most recent call last):
# ValueError: Value must be an integer
person . age = 201
# Traceback (most recent call last):
# ValueError: Value must be under 200
元类
进入元类这个概念之前,我们先回顾一下 type()这个函数,不,其实它是个类
通过 type(),我们可以获取一个对象所属的类,但通过 help 函数,发现 type()居然也可以用来创建类!
Copy type (name, bases, dict ) -> a new type
name 是新类的名称,bases 是继承的子类,dict 则是新类的属性名与其对应值的字典
Copy class A :
a = 1
def foo ( self ):
return self . a * 2
# 以上类的创建等价于
A = type ( 'A' , ( object , ), { 'a' : 1 , 'foo' : lambda self : self.a * 2 })
那么什么是元类呢?
平时我们用类来创建对象,但一切类都继承了对象,说白了类也是对象,而元类就是用来创建类对象的类
说白了,元类就是制造类的工厂
Copy 'alphardex' . __class__
# <class 'str'>
'alphardex' . __class__ . __class__
# <class 'type'>
通过以上的例子我们知道 type 就是用来创造一切类的元类,它是 Python 内置的元类
既然有内置的元类,也意味着你也可以自定义元类
以下实现一个元类,用来把类的所有非私有属性自动转换为大写(不已_开头的属性都是非私有的)
思路很简单:把属性和对应的值字典(attrs)里的非私有属性键改为大写(upper)就行了
Copy class UpperAttrMeta ( type ):
def __new__ ( cls , name , bases , attrs ):
"""
__init__方法用来初始化对象并传入参数
而__new__方法专门用来创建对象(显然这里我们要创建一个类对象并定制它)
"""
upper_attrs = { k . upper () if not k . startswith ( '_' ) else k : v for k , v in attrs . items ()}
return super (). __new__ (cls, name, bases, upper_attrs)
class Foo ( metaclass = UpperAttrMeta ):
name = 'alphardex'
__love = 'unknown'
f = Foo ()
f . NAME
# 'alphardex'
f . _Foo__love
# 'unknown'
元类的最经典的用途就是 ORM 的实现,以 django 的 ORM 为例
Copy class Person ( models . Model ):
name = models . CharField (max_length = 30 )
age = models . IntegerField ()
p = Person (name = 'alphardex' , age = '24' )
p . age
# 24
如果你访问一个模型实例的属性(例如这里的 age),你并不会得到什么 IntegerField(),而是得到了 24 这个数字,这就是元类的作用
元类平时很少用到,如果要动态修改类的属性,可以用猴子补丁(直接修改类方法)或者类装饰器
当然,这并不代表元类没什么用,想用到它的时候自然会用到的