Python進階之道

repo:github.com/alphardex/p…html

若是說優雅也有缺點的話,那就是你須要艱鉅的工做才能獲得它,須要良好的教育才能欣賞它。 —— Edsger Wybe Dijkstrapython

筆者精心整理了許多實用的Python tricks,歡迎各位Pythonistia參考。git

基本

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:
# 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
複製代碼

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 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
複製代碼

同時迭代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
# 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進行視圖的緩存等)

下劃線_的幾層含義

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

@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這個數字,這就是元類的做用

元類平時不多用到,若是要動態修改類的屬性,能夠用猴子補丁(直接修改類方法)或者類裝飾器

固然,這並不表明元類沒什麼用,想用到它的時候天然會用到的

相關文章
相關標籤/搜索