repo:github.com/alphardex/p…html
若是说优雅也有缺点的话,那就是你须要艰巨的工做才能获得它,须要良好的教育才能欣赏它。 —— Edsger Wybe Dijkstrapython
笔者精心整理了许多实用的Python tricks,欢迎各位Pythonistia参考。git
name = 'alphardex'
f'Ore wa {name} desu, {4 * 6} sai, gakusei desu.'
# 'Ore wa alphardex desu, 24 sai, gakusei desu.'
复制代码
# if condition:
# fuck
# else:
# shit
fuck 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等高阶函数食用更佳github
注:在Python中,通常更倾向于用列表推导式来替代map和filter面试
# def foo(parameters):
# return expression
foo = lambda parameters: expression
复制代码
numbers = [1, 2, 3, 4, 5]
list(map(lambda e: e ** 2, numbers))
# [1, 4, 9, 16, 25]
复制代码
values = [None, 0, '', True, 'alphardex', 666]
list(filter(lambda e:e, values))
# [True, "alphardex", 666]
复制代码
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 reduce
li = [[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
# 商:3
remainder
# 余:1
复制代码
只要函数中包含yield关键词,这函数就是一个生成器函数express
调用生成器函数时,会返回一个生成器对象django
正如yield自己的意思(产出),对生成器对象调用next()会使其不断产出值,直到无值才抛出StopIteration缓存
def gen_hello():
yield 'hello'
yield 'world'
gen_hello()
# <generator object gen_hello at 0x0000021643310BF8>
g = gen_hello()
next(g)
# 'hello'
next(g)
# 'world'
next(g)
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# StopIteration
复制代码
若是生成器函数须要产出另外一个生成器生成的值,就要用到yield from闭包
def chain(*iterables):
for it in iterables:
yield from it
s = '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]])
# True
any([e%2==0 for e in [1, 3, 4, 5]])
# True
复制代码
用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
# 1
rest
# [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 operator
data = [{'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 scientist
d
# {'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 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
复制代码
若是想让装饰器能接受参数,那就要再嵌套一层
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进行视图的缓存等)
1 + 1
# 2
_
# 2
复制代码
filename, _ = 'eroge.exe'.split('.')
filename
# 'eroge'
for _ in range(2):
print('wakarimasu')
# wakarimasu
# wakarimasu
复制代码
_("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
@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!')
复制代码
给函数参数添加类型,能提升代码的可读性和可靠性,大型项目的最佳实践之一
from typing import List
def 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):
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数学向量类,里面有多个特殊方法
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__)
协议:本质上是鸭子类型语言使用的非正式接口
不只如此,还要实现多份量的获取以及散列化
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
复制代码
想了解全部的特殊方法可查阅官方文档,如下列举些经常使用的:
字符串表示形式:__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, _ = origami
name
# 'Origami'
number
# 1
复制代码
那么什么是元类呢?
平时咱们用类来建立对象,但一切类都继承了对象,说白了类也是对象,而元类就是用来建立类对象的类
说白了,元类就是制造类的工厂
'alphardex'.__class__
# <class 'str'>
'alphardex'.__class__.__class__
# <class 'type'>
复制代码
经过以上的例子咱们知道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这个数字,这就是元类的做用
元类平时不多用到,若是要动态修改类的属性,能够用猴子补丁(直接修改类方法)或者类装饰器
固然,这并不表明元类没什么用,想用到它的时候天然会用到的