repo:
如果说优雅也有缺点的话,那就是你需要艰巨的工作才能得到它,需要良好的教育才能欣赏它。 —— Edsger Wybe Dijkstra
笔者精心整理了许多实用的Python tricks,欢迎各位Pythonistia参考。
基本
f-string
name = 'alphardex'f'Ore wa {name} desu, { 4 * 6} sai, gakusei desu.'# 'Ore wa alphardex desu, 24 sai, gakusei desu.'复制代码
三元运算符
# if condition:# fuck# else:# shitfuck if condition else shit复制代码
字符串的拼接,反转与分割
letters = ['咚', '哒', '呦', '!']''.join(letters)# '咚哒呦!'letters.reverse()# ['!', '呦', '哒', '咚']name = 'fujiwara chika'name.split(' ')# ['fujiwara', 'chika']复制代码
判断元素的存在性
'fuck' in 'fuck you'# True'slut' in ['bitch', 'whore']# False'company' in { 'title': 'Kaguya-sama: love is war', 'company': 'A1 Pictures'}# True复制代码
函数
匿名函数
类似ES6的箭头函数,函数的简化写法,配合map、filter、sorted等高阶函数食用更佳
注:在Python中,一般更倾向于用列表推导式来替代map和filter
# def foo(parameters):# return expressionfoo = lambda parameters: expression复制代码
map - 映射
numbers = [1, 2, 3, 4, 5]list(map(lambda e: e ** 2, numbers))# [1, 4, 9, 16, 25]复制代码
filter - 过滤
values = [None, 0, '', True, 'alphardex', 666]list(filter(lambda e:e, values))# [True, "alphardex", 666]复制代码
sort - 排序
tuples = [(1, 'kirito'), (2, 'asuna'), (4, 'alice'), (3, 'eugeo')]sorted(tuples, key=lambda x: x[1]) # key参数接受一个函数,并将其运用在序列里的每一个元素上# [(4, 'alice'), (2, 'asuna'), (3, 'eugeo'), (1, 'kirito')]sorted(tuples, key=lambda x: x[1], reverse=True) # reverse=True是降序# [(1, 'kirito'), (3, 'eugeo'), (2, 'asuna'), (4, 'alice')]复制代码
其他骚操作
from functools import reduce# 求1到100的积reduce(lambda x, y: x * y, range(1, 101))# 求和就更简单了sum(range(101))# 5050复制代码
扁平化列表
from functools import reduceli = [[1,2,3],[4,5,6], [7], [8,9]]flatten = lambda li: [item for sublist in li for item in sublist]flatten(li)# [1, 2, 3, 4, 5, 6, 7, 8, 9]# 或者直接用more_itertools这个第三方模块# from more_itertools import flatten# list(flatten(li))复制代码
星号和双星号
数据容器的合并
l1 = ['kaguya', 'miyuki']l2 = ['chika', 'ishigami'][*l1, *l2]# ['kaguya', 'miyuki', 'chika', 'ishigami']d1 = { 'name': 'rimuru'}d2 = { 'kind': 'slime'}{**d1, **d2}# {'name': 'rimuru', 'kind': 'slime'}复制代码
函数参数的打包与解包
# 打包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# 商:3remainder# 余:1复制代码
生成器
只要函数中包含yield关键词,这函数就是一个生成器函数
调用生成器函数时,会返回一个生成器对象
正如yield本身的意思(产出),对生成器对象调用next()会使其不断产出值,直到无值才抛出StopIteration
def gen_hello(): yield 'hello' yield 'world'gen_hello()#g = gen_hello()next(g)# 'hello'next(g)# 'world'next(g)# Traceback (most recent call last):# File " ", line 1, in # StopIteration复制代码
如果生成器函数需要产出另一个生成器生成的值,就要用到yield from
def chain(*iterables): for it in iterables: yield from its = 'ABC't = range(3)list(chain(s, t))# ['A', 'B', 'C', 0, 1, 2]复制代码
数据容器
列表
同时迭代元素与其索引
用enumerate即可
li = ['umaru', 'ebina', 'tachibana']print([f'{i+1}. {elem}' for i, elem in enumerate(li)])# ['1. umaru', '2. ebina', '3. tachibana']复制代码
元素的追加与连接
append在末尾追加元素,extend在末尾连接元素
li = [1, 2, 3]li.append([4, 5])li# [1, 2, 3, [4, 5]]li.extend([4, 5])li# [1, 2, 3, [4, 5], 4, 5]复制代码
测试是否整体/部分满足条件
all测试所有元素是否都满足于某条件,any则是测试部分元素是否满足于某条件
all([e<20 for e in [1, 2, 3, 4, 5]])# Trueany([e%2==0 for e in [1, 3, 4, 5]])# True复制代码
同时迭代2个以上的可迭代对象
用zip即可
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']复制代码
去重
利用集合的互异性
li = [3, 1, 2, 1, 3, 4, 5, 6]list(set(li))# [1, 2, 3, 4, 5, 6]sorted(set(li), key=li.index) # 此法能保留原先顺序# [3, 1, 2, 4, 5, 6]复制代码
解包
最典型的例子就是2数交换
a, b = b, a# 等价于 a, b = (b, a)复制代码
用星号运算符解包可以获取剩余的元素
first, *rest = [1, 2, 3, 4]first# 1rest# [2, 3, 4]复制代码
字典
遍历
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']复制代码
排序
import operatordata = [{ 'rank': 2, 'author': 'alphardex'}, { 'rank': 1, 'author': 'alphardesu'}]data_by_rank = sorted(data, key=operator.itemgetter('rank'))data_by_rank# [{'rank': 1, 'author': 'alphardesu'}, {'rank': 2, 'author': 'alphardex'}]data_by_rank_desc = sorted(data, key=lambda x: x['rank'], reverse=True)# [{'rank': 2, 'author': 'alphardex'}, {'rank': 1, 'author': 'alphardesu'}]复制代码
反转
d = { 'name': 'sakurajima mai', 'suit': 'bunny girl'}{v: k for k, v in d.items()}# {'sakurajima mai': 'name', 'bunny girl': 'suit'}复制代码
缺失键处理
get返回键值,如果键不在字典中,将会返回一个默认值
d = { 'name': 'okabe rintaro', 'motto': 'elpsycongroo'}d.get('job', 'mad scientist')# mad scientist复制代码
setdefault返回键值,如果键不在字典中,将会添加它并设置一个默认值
d = { 'name': 'okabe rintaro', 'motto': 'elpsycongroo'}# if 'job' not in d:# d['job'] = 'mad scientist'd.setdefault('job', 'mad scientist')# mad scientistd# {'name': 'okabe rintaro', 'motto': 'elpsycongroo', 'job': 'mad scientist'}复制代码
语言专属特性
推导式
推导式是一种快速构建可迭代对象的方法,因此凡是可迭代的对象都支持推导式
列表推导式
获取0-10内的所有偶数
even = [i for i in range(10) if not i % 2]even# [0, 2, 4, 6, 8]复制代码
字典推导式
将装满元组的列表转换为字典
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'}复制代码
生成器推导式
求0-10内的所有偶数的和
even_sum_under_10 = sum(i for i in range(11) if not i % 2)even_sum_under_10# 30复制代码
集合推导式
求所有数字的平方并去除重复元素
{x ** 2 for x in [1, 2, 2, 3, 3]}# {1, 4, 9}复制代码
装饰器
装饰器是一个可调用的对象,顾名思义它能够装饰在某个可调用的对象上,给它增加额外的功能
常用于缓存、权限校验、日志记录、性能测试、事务处理等场景
以下实现了一个简单的日志装饰器,能打印出函数的执行时间、函数名、函数参数和执行结果
import timefrom functools import wrapsdef 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 # 返回内部函数,取代被装饰的函数@clockdef 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复制代码
如果想让装饰器能接受参数,那就要再嵌套一层
import timefrom functools import wrapsDEFAULT_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# 6factorial_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进行视图的缓存等)
下划线_的几层含义
repl中暂存结果
1 + 1# 2_# 2复制代码
忽略某个变量
filename, _ = 'eroge.exe'.split('.')filename# 'eroge'for _ in range(2): print('wakarimasu')# wakarimasu# wakarimasu复制代码
i18n国际化
_("This sentence is going to be translated to other language.")复制代码
增强数字的可读性
1_000_000# 1000000复制代码
上下文管理器
用于资源的获取与释放,以代替try-except语句
常用于文件IO,锁的获取与释放,数据库的连接与断开等
# try:# f = open(input_path)# data = f.read()# finally:# f.close()with open(input_path) as f: data = f.read()复制代码
可以用@contextmanager来实现上下文管理器
from contextlib import contextmanager@contextmanagerdef open_write(filename): try: f = open(filename, 'w') yield f finally: f.close()with open_write('onegai.txt') as f: f.write('Dagakotowaru!')复制代码
静态类型注解
给函数参数添加类型,能提高代码的可读性和可靠性,大型项目的最佳实践之一
from typing import Listdef greeting(name: str) -> str: return f'Hello {name}.'def gathering(users: List[str]) -> str: return f"{ ', '.join(users)} are going to be raped."print(greeting('alphardex'))print(gathering(['Bitch', 'slut']))复制代码
多重继承
在django中经常要处理类的多重继承的问题,这时就要用到super函数
如果单单认为super仅仅是“调用父类的方法”,那就错了
在继承单个类的情况下,可以认为super是调用父类的方法(ES6里面亦是如此)
但多重继承就不一样了,因为方法名可能会有冲突,所以super就不能单指父类了
在Python中,super指的是MRO中的下一个类,用来解决多重继承时父类的查找问题
MRO是啥?Method Resolution Order(方法解析顺序)
看完下面的例子,就会理解了
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): passd = D()# enter B# enter C# A# leave C# leave Bprint(d.__class__.__mro__)# (, , , , )复制代码
首先,因为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数学向量类,里面有多个特殊方法
from math import hypotclass 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或yv.z = 1# Traceback (most recent call last):# File "", line 1, in # AttributeError: 'Vector2d' object has no attribute 'z'# 由于x属性只读,因此无法再次赋值v.x = 1# Traceback (most recent call last):# File " ", line 1, in # 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)# 7v1 = Vector2d(1, 2)v2 = Vector2d(3, 4)set([v1, v2])# {Vector2d(1.0, 2.0), Vector2d(3.0, 4.0)}复制代码
如果把Vector改造为多维向量呢?关键就是要实现序列协议(__len__和__getitem__)
协议:本质上是鸭子类型语言使用的非正式接口
不仅如此,还要实现多分量的获取以及散列化
from array import arrayimport reprlibimport mathimport numbersimport stringfrom functools import reducefrom operator import xorfrom itertools import zip_longestimport numbersfrom fractions import Fraction as Fclass 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 @ otherv = 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.0v[1,3]# Traceback (most recent call last):# File "", line 1, in # File " ", 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 " ", line 1, in # File " ", line 62, in __setattr__# AttributeError: can't set attribute 'a' to 'z' in Vectorv1 = Vector((3, 4, 5))v2 = Vector((6, 7))v1 == v2# Falseset([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复制代码
想了解所有的特殊方法可查阅,以下列举些常用的:
字符串表示形式:__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
这样在视图函数中,就能直接调用该方法进行查询,节省了不少代码
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复制代码
元类
进入元类这个概念之前,我们先回顾一下type()这个函数,不,其实它是个类
通过type(),我们可以获取一个对象所属的类,但通过help函数,发现type()居然也可以用来创建类!
type(name, bases, dict) -> a new type复制代码
name是新类的名称,bases是继承的子类,dict则是新类的属性名与其对应值的字典
class A: a = 1 def foo(self): return self.a * 2# 以上类的创建等价于A = type('A', (object, ), { 'a': 1, 'foo': lambda self: self.a * 2})复制代码
标准库collections里面有个namedtuple函数,通过传入一个类名和几个属性名,我们就能创建一个tuple表示的类
下面试着用type来创建一个namedtuple函数,支持基本的动态创建类的功能
def namedtuple(typename, field_names): try: field_names = field_names.replace(',', '').split() except AttributeError: pass finally: field_names = tuple(field_names) def __init__(self, *args, **kwargs): attrs = dict(zip(self.__slots__, args)) attrs.update(kwargs) for name, value in attrs.items(): setattr(self, name, value) def __iter__(self): yield from (getattr(self, name) for name in self.__slots__) def __repr__(self): values = ', '.join('{}={!r}'.format(*i) for i in zip(self.__slots__, self)) return f'{self.__class__.__name__}({values})' cls_attrs = dict(__slots__=field_names, __init__=__init__, __iter__=__iter__, __repr__=__repr__) return type(typename, (object, ), cls_attrs)Seirei = namedtuple('Seirei', 'name number code')origami = Seirei('Origami', 1, 'tenshi')origami# Seirei(name='Origami', number=1, code='tenshi')name, number, _ = origaminame# 'Origami'number# 1复制代码
那么什么是元类呢?
平时我们用类来创建对象,但一切类都继承了对象,说白了类也是对象,而元类就是用来创建类对象的类
说白了,元类就是制造类的工厂
'alphardex'.__class__#'alphardex'.__class__.__class__# 复制代码
通过以上的例子我们知道type就是用来创造一切类的元类,它是Python内置的元类
既然有内置的元类,也意味着你也可以自定义元类
以下实现一个元类,用来把类的所有非私有属性自动转换为大写(不已_开头的属性都是非私有的)
思路很简单:把属性和对应的值字典(attr_dict)里的非私有属性键改为大写(upper)就行了
class UpperAttrMeta(type): def __new__(cls, name, bases, attr_dict): """ __init__方法用来初始化对象并传入参数 而__new__方法专门用来创建对象(显然这里我们要创建一个类对象并定制它) """ uppercase_attr_dict = {k.upper() if not k.startswith('_') else k: v for k, v in attr_dict.items()} return super().__new__(cls, name, bases, uppercase_attr_dict)class Foo(metaclass=UpperAttrMeta): name = 'alphardex' __love = 'unknown'f = Foo()f.NAME# 'alphardex'f._Foo__love# 'unknown'复制代码
元类的最经典的用途就是ORM的实现,以django的ORM为例
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这个数字,这就是元类的作用
元类平时很少用到,如果要动态修改类的属性,可以用猴子补丁(直接修改类方法)或者类装饰器
当然,这并不代表元类没什么用,想用到它的时候自然会用到的