最近才狠下心來 準備做一個自己的博客 原先FuckBlog項目由於後端小夥伴加班而擱置,因此 作爲團隊PM的我自己也要開始做技術方面了,準備自己先寫一個博客看看。
備註- ORM全稱:object related mapping 對象關係映射
首先,我覺得數據庫操作不封裝是很傻比的。原來我寫了一個數據庫導入工具,全篇導出都是sql語句 什麼增刪改查都他媽齊活的在各個py裏面跑來跑去。給大家上一張圖:
大家體會到這種亂跑的辛酸了麼 從那次失敗的架構我就說:再不用orm
我他媽就是一傻逼
其次就是不安全,我這裏再給大家舉一個反例 使我們FuckBlog
項目裏面 後端小夥伴採用了一個異常簡單的後臺模板 我在查看代碼的時候 發現了問題 我們看用戶驗證的代碼:
看到了麼 傳輸的用戶名密碼 居然是直接放進sql語句拼接的。大家知道怎麼構造 password來達到我們想要的任意用戶名都可以登錄的效果了麼 有幾個月了 我簡單說一下思路 首先引號閉合 閉合之後 然後在+ or +構造萬能查詢語句 只要能查詢成功,就到讓session進行賦值。
最後呢 就是在web中 因爲:
一處異步調用 處處異步調用
我們在查詢數據庫中肯定不能因爲數據量過大而放棄其他的請求 這樣效率非常慢所以這方面的控制 以及上文所述的問題 需要我們去封裝 而方便我們的調用和請求。因此我們需要自己寫一個orm
工具類 去完成異步的增刪改查
ORM
編寫的一些難點
我跟着廖雪峯大神的博客 來學習 期間也看了不少人的跑通代碼 自己的疑問點期初看的時候也很多當然 自己慢慢一點一點啃 啃幾天就會發現豁然開朗。
代碼加上測試一共三百多行,非常簡潔,當然我估計註釋就能佔到八九十行,包括別人的也包括自己第一篇學習的困惑和不瞭解。也算是比較實用吧。 我先貼我第一版學習跑通的代碼 然後呢 我會慢慢在後面進行闡述。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
|
# -*- coding: utf-8 -*-
# 2017/1/4 11:02
""
"
-------------------------------------------------------------------------------
Function: 封裝的ORM工具類
Version: 1.0
Author: SLY
Contact: [email protected]
code is far away from bugs with the god animal protecting
┏┓ ┏┓
┏┛┻━━━┛┻┓
┃ ┃
┃ ┳┛ ┗┳ ┃
┃ ┻ ┃
┗━┓ ┏━┛
┃ ┗━━━┓
┃ 神獸保佑 ┣┓
┃ 永無BUG! ┏┛
┗┓┓┏━┳┓┏┛
┃┫┫ ┃┫┫
┗┻┛ ┗┻┛
-------------------------------------------------------------------------------
"
""
import
sys
import
asyncio
import
logging
logging
.
basicConfig
(
level
=
logging
.
INFO
)
# 一次使用異步 處處使用異步
import
aiomysql
def
log
(
sql
,
args
=
(
)
)
:
logging
.
info
(
'SQL:%s'
%
sql
)
@
asyncio
.
coroutine
def
create_pool
(
loop
,
*
*
kw
)
:
logging
.
info
(
' start creating database connection pool'
)
global
__pool
# 理解這裏的yield from 是很重要的
__pool
=
yield
from
aiomysql
.
create_pool
(
host
=
kw
.
get
(
'host'
,
'localhost'
)
,
port
=
kw
.
get
(
'port'
,
3306
)
,
user
=
kw
[
'user'
]
,
password
=
kw
[
'password'
]
,
db
=
kw
[
'db'
]
,
charset
=
kw
.
get
(
'charset'
,
'utf8'
)
,
autocommit
=
kw
.
get
(
'autocommit'
,
True
)
,
maxsize
=
kw
.
get
(
'maxsize'
,
10
)
,
# 目前不清楚這個minsize是什麼鬼
minsize
=
kw
.
get
(
'minsize'
,
1
)
,
loop
=
loop
)
@
asyncio
.
coroutine
def
destroy_pool
(
)
:
global
__pool
if
__pool
is
not
None
:
__pool
.
close
(
)
yield
from
__pool
.
wait_closed
(
)
# 我很好奇爲啥不用commit 事務不用提交麼
@
asyncio
.
coroutine
def
select
(
sql
,
args
,
size
=
None
)
:
log
(
sql
,
args
)
global
__pool
# 666 建立遊標
# -*- yield from 將會調用一個子協程,並直接返回調用的結果
# yield from從連接池中返回一個連接
with
(
yield
from
__pool
)
as
conn
:
cur
=
yield
from
conn
.
cursor
(
aiomysql
.
DictCursor
)
yield
from
cur
.
execute
(
sql
.
replace
(
'?'
,
'%s'
)
,
args
)
if
size
:
rs
=
yield
from
cur
.
fetchmany
(
size
)
else
:
rs
=
yield
from
cur
.
fetchall
(
)
yield
from
cur
.
close
(
)
logging
.
info
(
'rows have returned %s'
%
len
(
rs
)
)
return
rs
# 封裝INSERT, UPDATE, DELETE
# 語句操作參數一樣,所以定義一個通用的執行函數
# 返回操作影響的行號
# 我想說的是 知道影響行號有個叼用
@
asyncio
.
coroutine
def
execute
(
sql
,
args
,
autocommit
=
True
)
:
log
(
sql
)
global
__pool
with
(
yield
from
__pool
)
as
conn
:
try
:
# 因爲execute類型sql操作返回結果只有行號,不需要dict
cur
=
yield
from
conn
.
cursor
(
)
# 順便說一下 後面的args 別掉了 掉了是無論如何都插入不了數據的
yield
from
cur
.
execute
(
sql
.
replace
(
'?'
,
'%s'
)
,
args
)
yield
from
conn
.
commit
(
)
affected_line
=
cur
.
rowcount
yield
from
cur
.
close
(
)
print
(
'execute : '
,
affected_line
)
except
BaseException
as
e
:
raise
return
affected_line
# 這個函數主要是把查詢字段計數 替換成sql識別的?
# 比如說:insert into `User` (`password`, `email`, `name`, `id`) values (?,?,?,?) 看到了麼 後面這四個問號
def
create_args_string
(
num
)
:
lol
=
[
]
for
n
in
range
(
num
)
:
lol
.
append
(
'?'
)
return
(
','
.
join
(
lol
)
)
# 定義Field類,負責保存(數據庫)表的字段名和字段類型
class
Field
(
object
)
:
# 表的字段包含名字、類型、是否爲表的主鍵和默認值
def
__init__
(
self
,
name
,
column_type
,
primary__key
,
default
)
:
self
.
name
=
name
self
.
column_type
=
column_type
self
.
primary_key
=
primary__key
self
.
default
=
default
def
__str__
(
self
)
:
# 返回 表名字 字段名 和字段類型
return
"<%s , %s , %s>"
%
(
self
.
__class__
.
__name__
,
self
.
name
,
self
.
column_type
)
# 定義數據庫中五個存儲類型
class
StringField
(
Field
)
:
def
__init__
(
self
,
name
=
None
,
primary_key
=
False
,
default
=
None
,
ddl
=
'varchar(100)'
)
:
super
(
)
.
__init__
(
name
,
ddl
,
primary_key
,
default
)
# 布爾類型不可以作爲主鍵
class
BooleanField
(
Field
)
:
def
__init__
(
self
,
name
=
None
,
default
=
None
)
:
super
(
)
.
__init__
(
name
,
'Boolean'
,
False
,
default
)
# 不知道這個column type是否可以自己定義 先自己定義看一下
class
IntegerField
(
Field
)
:
def
__init__
(
self
,
name
=
None
,
primary_key
=
False
,
default
=
0
)
:
super
(
)
.
__init__
(
name
,
'int'
,
primary_key
,
default
)
class
FloatField
(
Field
)
:
def
__init__
(
self
,
name
=
None
,
primary_key
=
False
,
default
=
0.0
)
:
super
(
)
.
__init__
(
name
,
'float'
,
primary_key
,
default
)
class
TextField
(
Field
)
:
def
__init__
(
self
,
name
=
None
,
default
=
None
)
:
super
(
)
.
__init__
(
name
,
'text'
,
False
,
default
)
# class Model(dict,metaclass=ModelMetaclass):
# -*-定義Model的元類
# 所有的元類都繼承自type
# ModelMetaclass元類定義了所有Model基類(繼承ModelMetaclass)的子類實現的操作
# -*-ModelMetaclass的工作主要是爲一個數據庫表映射成一個封裝的類做準備:
# ***讀取具體子類(user)的映射信息
# 創造類的時候,排除對Model類的修改
# 在當前類中查找所有的類屬性(attrs),如果找到Field屬性,就將其保存到__mappings__的dict中,同時從類屬性中刪除Field(防止實例屬性遮住類的同名屬性)
# 將數據庫表名保存到__table__中
# 完成這些工作就可以在Model中定義各種數據庫的操作方法
# metaclass是類的模板,所以必須從`type`類型派生:
class
ModelMetaclass
(
type
)
:
# __new__控制__init__的執行,所以在其執行之前
# cls:代表要__init__的類,此參數在實例化時由Python解釋器自動提供(例如下文的User和Model)
# bases:代表繼承父類的集合
# attrs:類的方法集合
def
__new__
(
cls
,
name
,
bases
,
attrs
)
:
# 排除model 是因爲要排除對model類的修改
if
name
==
'Model'
:
return
type
.
__new__
(
cls
,
name
,
bases
,
attrs
)
# 獲取table名稱 爲啥獲取table名稱 至於在哪裏我也是不明白握草
table_name
=
attrs
.
get
(
'__table__'
,
None
)
or
name
logging
.
info
(
'found table: %s (table: %s) '
%
(
name
,
table
_name
)
)
# 獲取Field所有主鍵名和Field
mappings
=
dict
(
)
fields
=
[
]
primaryKey
=
None
# 這個k是表示字段名
for
k
,
v
in
attrs
.
items
(
)
:
if
isinstance
(
v
,
Field
)
:
logging
.
info
(
'Found mapping %s===>%s'
%
(
k
,
v
)
)
# 注意mapping的用法
mappings
[
k
]
=
v
if
v
.
primary_key
:
logging
.
info
(
'fond primary key hahaha %s'
%
k
)
# 這裏很有意思 當第一次主鍵存在primaryKey被賦值 後來如果再出現主鍵的話就會引發錯誤
if
primaryKey
:
raise
RuntimeError
(
'Duplicated key for field'
)
primaryKey
=
k
else
:
fields
.
append
(
k
)
if
not
primaryKey
:
raise
RuntimeError
(
'Primary key not found!'
)
# w下面位字段從類屬性中刪除Field 屬性
for
k
in
mappings
.
keys
(
)
:
attrs
.
pop
(
k
)
# 保存除主鍵外的屬性爲''列表形式
# 這一句的lambda表達式沒懂
escaped_fields
=
list
(
map
(
lambda
f
:
'`%s`'
%
f
,
fields
)
)
# 保存屬性和列的映射關係
attrs
[
'__mappings__'
]
=
mappings
# 保存表名
attrs
[
'__table__'
]
=
table_name
# 保存主鍵名稱
attrs
[
'__primary_key__'
]
=
primaryKey
# 保存主鍵外的屬性名
attrs
[
'__fields__'
]
=
fields
# 構造默認的增刪改查 語句
attrs
[
'__select__'
]
=
'select `%s`, %s from `%s` '
%
(
primaryKey
,
', '
.
join
(
escaped_fields
)
,
table_name
)
attrs
[
'__insert__'
]
=
'insert into `%s` (%s, `%s`) values (%s) '
%
(
table_name
,
', '
.
join
(
escaped_fields
)
,
primaryKey
,
create_args_string
(
len
(
escaped_fields
)
+
1
)
)
attrs
[
'__update__'
]
=
'update `%s` set %s where `%s` = ?'
%
(
table_name
,
', '
.
join
(
map
(
lambda
f
:
'`%s`=?'
%
(
mappings
.
get
(
f
)
.
name
or
f
)
,
fields
)
)
,
primaryKey
)
attrs
[
'__delete__'
]
=
'delete `%s` where `%s`=?'
%
(
table_name
,
primaryKey
)
return
type
.
__new__
(
cls
,
name
,
bases
,
attrs
)
# 定義ORM所有映射的基類:Model
# Model類的任意子類可以映射一個數據庫表
# Model類可以看作是對所有數據庫表操作的基本定義的映射
# 基於字典查詢形式
# Model從dict繼承,擁有字典的所有功能,同時實現特殊方法__getattr__和__setattr__,能夠實現屬性操作
# 實現數據庫操作的所有方法,定義爲class方法,所有繼承自Model都具有數據庫操作方法
class
Model
(
dict
,
metaclass
=
ModelMetaclass
)
:
def
__init__
(
self
,
*
*
kw
)
:
super
(
Model
,
self
)
.
__init__
(
*
*
kw
)
def
__getattr__
(
self
,
key
)
:
try
:
return
self
[
key
]
except
KeyError
:
raise
AttributeError
(
"'Model' object have no attribution: %s"
%
key
)
def
__setattr__
(
self
,
key
,
value
)
:
self
[
key
]
=
value
def
getValue
(
self
,
key
)
:
# 這個是默認內置函數實現的
return
getattr
(
self
,
key
,
None
)
def
getValueOrDefault
(
self
,
key
)
:
value
=
getattr
(
self
,
key
,
None
)
if
value
is
None
:
field
=
self
.
__mappings__
[
key
]
if
field
.
default
is
not
None
:
value
=
field
.
default
(
)
if
callable
(
field
.
default
)
else
field
.
default
logging
.
info
(
'using default value for %s : %s '
%
(
key
,
str
(
value
)
)
)
setattr
(
self
,
key
,
value
)
return
value
@
classmethod
# 類方法有類變量cls傳入,從而可以用cls做一些相關的處理。並且有子類繼承時,調用該類方法時,傳入的類變量cls是子類,而非父類。
@
asyncio
.
coroutine
def
find_all
(
cls
,
where
=
None
,
args
=
None
,
*
*
kw
)
:
sql
=
[
cls
.
__select__
]
if
where
:
sql
.
append
(
'where'
)
sql
.
append
(
where
)
if
args
is
None
:
args
=
[
]
orderBy
=
kw
.
get
(
'orderBy'
,
None
)
if
orderBy
:
sql
.
append
(
'order by'
)
sql
.
append
(
orderBy
)
# dict 提供get方法 指定放不存在時候返回後學的東西 比如a.get('Fuck',None)
limit
=
kw
.
get
(
'limit'
,
None
)
if
limit
is
not
None
:
sql
.
append
(
'limit'
)
if
isinstance
(
limit
,
int
)
:
sql
.
append
(
'?'
)
args
.
append
(
limit
)
elif
isinstance
(
limit
,
tuple
)
and
len
(
limit
)
==
2
:
sql
.
append
(
'?,?'
)
args
.
extend
(
limit
)
else
:
raise
ValueError
(
'Invalid limit value : %s '
%
str
(
limit
)
)
rs
=
yield
from
select
(
' '
.
join
(
sql
)
,
args
)
return
[
cls
(
*
*
r
)
for
r
in
rs
]
@
classmethod
@
asyncio
.
coroutine
def
findNumber
(
cls
,
selectField
,
where
=
None
,
args
=
None
)
:
''
'find number by select and where.'
''
sql
=
[
'select %s __num__ from `%s`'
%
(
selectField
,
cls
.
__table__
)
]
if
where
:
sql
.
append
(
'where'
)
sql
.
append
(
where
)
rs
=
yield
from
select
(
' '
.
join
(
sql
)
,
args
,
1
)
if
len
(
rs
)
==
0
:
return
None
return
rs
[
0
]
[
'__num__'
]
# 這個黑魔法我還在研究呢~
@
classmethod
@
asyncio
.
coroutine
def
find
(
cls
,
primarykey
)
:
''
'find object by primary key'
''
rs
=
yield
from
select
(
'%s where `%s`=?'
%
(
cls
.
__select__
,
cls
.
__primary_key__
)
,
[
primarykey
]
,
1
)
if
len
(
rs
)
==
0
:
return
None
return
cls
(
*
*
rs
[
0
]
)
@
classmethod
@
asyncio
.
coroutine
def
findAll
(
cls
,
*
*
kw
)
:
rs
=
[
]
if
len
(
kw
)
==
0
:
rs
=
yield
from
select
(
cls
.
__select__
,
None
)
else
:
args
=
[
]
values
=
[
]
for
k
,
v
in
kw
.
items
(
)
:
args
.
append
(
'%s=?'
%
k
)
values
.
append
(
v
)
rs
=
yield
from
select
(
'%s where %s '
%
(
cls
.
__select__
,
' and '
.
join
(
args
)
)
,
values
)
return
rs
@
asyncio
.
coroutine
def
save
(
self
)
:
args
=
list
(
map
(
self
.
getValueOrDefault
,
self
.
__fields__
)
)
print
(
'save:%s'
%
args
)
args
.
append
(
self
.
getValueOrDefault
(
self
.
__primary_key__
)
)
rows
=
yield
from
execute
(
self
.
__insert__
,
args
)
if
rows
!=
1
:
print
(
self
.
__insert__
)
logging
.
warning
(
'failed to insert record: affected rows: %s'
%
rows
)
@
asyncio
.
coroutine
# 顯示方言錯誤是什麼鬼。。。
def
update
(
self
)
:
args
=
list
(
map
(
self
.
getValue
,
self
.
__fields__
)
)
args
.
append
(
self
.
getValue
(
self
.
__primary_key__
)
)
rows
=
yield
from
execute
(
self
.
__update__
,
args
)
if
rows
!=
1
:
logging
.
warning
(
'failed to update record: affected rows: %s'
%
rows
)
@
asyncio
.
coroutine
def
remove
(
self
)
:
args
=
[
self
.
getValue
(
self
.
__primary_key__
)
]
rows
=
yield
from
execute
(
self
.
__updata__
,
args
)
if
rows
!=
1
:
logging
.
warning
(
'failed to remove by primary key: affected rows: %s'
%
rows
)
if
__name__
==
"__main__"
:
class
User
(
Model
)
:
id
=
IntegerField
(
'id'
,
primary_key
=
True
)
name
=
StringField
(
'username'
)
email
=
StringField
(
'email'
)
password
=
StringField
(
'password'
)
#創建異步事件的句柄
loop
=
asyncio
.
get_event_loop
(
)
#創建實例
@
asyncio
.
coroutine
def
test
(
)
:
yield
from
create_pool
(
loop
=
loop
,
host
=
'localhost'
,
port
=
3308
,
user
=
'sly'
,
password
=
'070801382'
,
db
=
'test'
)
yield
from
user
.
save
(
)
r
=
yield
from
User
.
find
(
'11'
)
print
(
r
)
r
=
yield
from
User
.
findAll
(
)
print
(
1
,
r
)
r
=
yield
from
User
.
findAll
(
id
=
'12'
)
print
(
2
,
r
)
yield
from
destroy_pool
(
)
loop
.
run_until_complete
(
test
(
)
)
loop
.
close
(
)
if
loop
.
is_closed
(
)
:
sys
.
exit
(
0
)
|
asyncore
和 aiomysql
這兩個庫假設我們調用數據庫的請求不知一個,比如用戶小王訪問我們網站,獲取你以前的文章,那麼東北社會你關哥也在同一時間訪問我們網站,而文章比較多,小王的請求需要5秒才能返回結果。採用同步的話,那社會你關哥 就要等着小王請求王城才能進行請求(獲得服務器關注)麼? 顯然不行,因此需要採用異步的收發請求 。而Python 中實現這個的就是asyncore 他封裝了HTTP UDP SSL 的異步協議 可以讓單線程 也可以異步收發請求。(如果你想自己實現套字節的異步收發返回,可以小小參考一下我的這篇網絡腳本編寫)
那aiohttp是什麼鬼 aiomysql
又是什麼叼東西。他們都是基於asyncore
實現的異步http庫 異步mysql 庫 調用他們就可以實現異步請求在http 和 mysql 上。記住:一處異步 處處異步
引用日誌模塊logging
沒有什麼好說的 需要注意的是 在我們編寫服務端的時候 良好的日誌記錄習慣很重要 不要被pycharm
慣壞了 動不動print
大法 debug
大法 另外我想補充的是:logging
這個吊玩兒,在多線程記錄的時候會出錯,需要改寫其中的某些方法,別問我是如何知道的。對了,
level=logging.INFO
這個INFO大寫。
*args
和 **kwargs
主要用於函數定義。 **kw
就是**kwargs
的縮寫,可以傳遞數量不一的變量,他們的核心是前面的星號 一顆星和兩顆星 而不是後面的args 你寫成*fuckyou 都沒事。只不過是約定俗稱而已。那麼這個屌絲東西怎麼用呢?我們看一段示例代碼:(我改自gitbook Python進階)
1
2
3
4
5
6
|
def
test_var_args
(
f_arg
,
*
fuckyou
)
:
print
(
"first normal arg:"
,
f_arg
)
for
arg
in
fuckyou
:
print
(
"another arg through *fuckyou:"
,
arg
)
test_var_args
(
'yasoob'
,
'python'
,
'變量'
,
'test'
,
111
)
|
輸出結果:
我們再來看**kwargs
**kwargs
:該參數允許你將不定長度的鍵值對, 作爲參數傳遞給一個函數。 如果你想要在一個函數裏處理帶名字的參數, 你應該使用**kwargs
。
你肯定回想 啥叫鍵值對 帶名字的參數又是什麼鬼 我們來看這個例子:
1
2
3
4
5
|
def
greet_me
(
*
*
kwargs
)
:
for
key
,
value
in
kwargs
.
items
(
)
:
print
(
"{0} ==> {1}"
.
format
(
key
,
value
)
)
|
怎麼樣現在傳的時候是不是有點小明白 就像傳遞一個字典一樣,比如這樣:
1
2
3
4
5
|
def
greet_me
(
*
*
fuckhahahhaa
)
:
for
key
,
value
in
my_info
.
items
(
)
:
print
(
"{0} ==> {1}"
.
format
(
key
,
value
)
)
greet_me
(
*
*
my_info
)
|
主要傳進去my_info
是一個字典就好了。
一般來講傳遞的順序是 var *args **kwargs
這裏面就順便提一下,字典的get方法用法是get(key,value) 如果沒有key對應的鍵值 則返回設定value
比如
1
2
3
4
5
6
7
|
def
greet_me
(
*
*
fuckhahah
)
:
for
key
,
value
in
my_info
.
items
(
)
:
print
(
"{0} ==> {1}"
.
format
(
key
,
value
)
)
print
(
my_info
.
get
(
key
)
)
print
(
'Fuck'
,
'None'
)
greet_me
(
*
*
my_info
)
|
返回就是:
email ==> [email protected]
[email protected]
Fuck None
name ==> sly
sly
Fuck None
yield
和 yield from
語法
我們都知道yield
或者說對yield
比較熟悉 因爲我們教學的案例常常就是 yield result 當一個 生成器(英文:generator)。但是我們爲了弄清楚這一系列的東西 我們需要首先弄清楚迭代器 生成器這些鬼東西。在這方面 除了官方文檔 我還查閱了 這個 這個還有這個 當然 IBM的也不錯 等等 我就不一一列出了。
我們爲啥需要生成器?假設我們需要處理草榴的所有用戶信息,如果一次性就處理1000萬的用戶信息列表非常耗費內存,我們的本地電腦內存不夠用,那麼就需要一個分批處理草榴用戶信息生成器的東西,當我們調用一次的時候返回1000用戶信息列表即可 ,下一次調用就返回uid在1001-2000的用戶就好了。但是你可能會問:函數裏面的return咋知道你是下一次調用還是第三次第四次調用 給你返回你想要的值呢?而這就是yield 存在的神奇方法,它能夠記住。
而正因爲yield 存在才使得我們不需要擁有存儲 1000萬草榴用戶信息列表,就能逐步得到這些信息。內存佔用極小。
那麼可能會有人問:適用於生成器的對象自身需要可迭代能力麼 也就是說我必須要是一個字典列表啥的才行麼?
事實上生成器調用的對象自身必須擁有可迭代能力,之所以你認爲傳遞列表或者字典是你把迭代在Python裏面的識別狹義化了。
什麼是可迭代
?什麼東西可以記住當前調用位置?
可迭代是指一種可以在容器
中逐個提取元素的能力。
(容器:將多個元素組合在一起的數據結構,常見的容器有 dict list tuple str 文本流 以及他們這些的變形比如OrderedDict)
迭代器
的內部狀態可以記住當前調用位置
Python是如何識別一個對象擁有可迭代能力?
一個可迭代對象必須具備:
__iter__()
(爲啥該命名方法前後有__因爲這是Python特殊方法,以示區分,在OOP編程的後續會給大家講到)
而迭代器
呢則必須具備以下特殊方法:2.x是next()
__iter__()
__next__()
我們來看廖雪峯在IBM workplace上給的yield 斐波拉切數列案例
1
2
3
4
5
6
7
8
9
10
11
|
def
fab_yield
(
max
)
:
n
,
a
,
b
=
0
,
0
,
1
while
n
&
lt
;
max
:
yield
b
# print b
a
,
b
=
b
,
a
+
b
n
=
n
+
1
f
=
fab_yield
(
100
)
print
(
type
(
f
)
)
for
i
in
f
:
print
(
i
)
|
你最終會發現這個yield 讓返回的對象變成了 generator 一個生成器。 它讓你關注的點更多的在算法實現上,而非存儲上面。
那麼我們該如何自己去實現這個迭代器呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class
Fab
(
object
)
:
def
__init__
(
self
,
max
)
:
self
.
max
=
max
self
.
n
,
self
.
a
,
self
.
b
=
0
,
0
,
1
def
__iter__
(
self
)
:
return
self
def
__next__
(
self
)
:
if
self
.
n
&
lt
;
self
.
max
:
r
=
self
.
b
self
.
a
,
self
.
b
=
self
.
b
,
self
.
a
+
self
.
b
self
.
n
=
self
.
n
+
1
return
r
raise
StopIteration
(
)
|
調用和擁有yield 的函數一樣,同上。
如果還有疑問或者我的描述有錯誤的可以在博客評論中提出(mail寫好我回復你會收到郵件~)
解釋了yield
我們來看一下 yield from
關於yield from
這個Python 3.3 才支持的東西我們看一下:
首先,yield from 是爲了解決什麼問題而出現的。
看到上文我們發現一個問題,yield 不能脫離代碼單獨出來用,在一個生成器中調用另外一個生成器想要用,咋辦 那麼我們就需要yield from
yield from
的的歷史可以參考 這個PEP:
總之大意是原本的yield語句只能將CPU控制權還給直接調用者,當你想要將一個generator或者coroutine裏帶有yield語句的邏輯重構到另一個generator(原文是subgenerator)裏的時候,會非常麻煩,因爲外面的generator要負責爲裏面的generator做消息傳遞;所以某人有個想法是讓python把消息傳遞封裝起來,使其對程序猿透明,於是就有了
yield from
。
對於簡單的迭代器,yield from iterable
本質上等於for item in iterable: yield item
的縮寫版 yield from
允許子生成器直接從調用者接收其發送的信息或者拋出調用時遇到的異常,並且返回給委派生產器一個值
那麼在本次學習orm的過程中你就把他理解爲異步執行即可 先別管原理了。
如果大家仍舊很感興趣 可以參考這篇文章 我能力有限 就不畫蛇添足了。
這個就簡單說一下 前面一條槓 就是非公開變量 兩條槓私有變量 一條槓能調用 兩條槓就不允許外部調用。不像Java 那樣有什麼private 什麼的 別再和我說不能阻止外部調用很不安全 私有變量 不可以 You are Adult.
__str__ __init__ __name__ 這些都是特殊方法(或者魔術方法) 內置的 詳細幹嘛自己去Python官網看。
@asyncore.coroutine
是什麼鬼?它能夠將一個生成器generator標記爲coroutine類型,然後扔進Eventloop 異步執行。
這裏我們需要回去複習廖雪峯大神關於asyncore的講解。(這告訴我們 翹課早晚是要還的 不管你是網課還是自學)
1
2
3
4
|