本文適應人羣:C#
or Python3
基礎鞏固html
代碼褲子: https://github.com/lotapp/BaseCodejava
在線編程: https://mybinder.org/v2/gh/lotapp/BaseCode/masternode
在線預覽:http://github.lesschina.com/python/base/ext/基礎衍生.htmlpython
立刻快期末考試了,老師蜜月也回來了,因而有了一場跨季度的複習講課了:git
None、""、0、[]、{}
==> 假github
一、" "、[None,""]、{"":None}
==> 真算法
小明可高興了,前幾天被打擊的面目全非,這幾天老師回來了,又能夠大發神威了,因而搶先提交demo:編程
# None
if None:
print(True)
else:
print(False)
# 0爲False
if 0:
print(True)
else:
print(False)
# 空字符串
if "":
print(True)
else:
print(False)
# 空列表爲False
if []:
print(True)
else:
print(False)
# 空字典爲False
if {}:
print(True)
else:
print(False)
# 1爲True
if 1:
print(True)
else:
print(False)
# 含空格
if " ":
print(True)
else:
print(False)
if [None,""]:
print(True)
else:
print(False)
if {"":None}:
print(True)
else:
print(False)
a, b = 1, 2
max = a if a > b else b
print(max)
a, b, c = 1, 3, 2
max = a if a > b else b
max = max if max > c else c
print(max)
# 上面的那個還有一種簡寫(不推薦)
a, b, c = 1, 3, 2
max = (a if a > b else b) if (a if a > b else b) > c else c
print(max)
在Python3.x
版本中,字符串是以Unicode
編碼的
對於單個字符的編碼,Python提供了ord()
函數獲取字符的整數表示,chr()
函數把編碼轉換爲對應的字符
小潘對這塊有所研究,把小明按在桌上而後搶先提交demo:
ord('D')
ord('毒')
chr(68)
chr(27602)
print(ord('A'))
print(ord('Z'))
print(ord('a'))
print(ord('z'))
老師補充講解道:
編碼:encode()
解碼:decode()
url相關的能夠用:
urllib.parse.quote()
and urllib.parse.unquote()
urllib.parse.urlencode()
能夠直接對一個key-value
進行url
編碼
# encode() and decode()
name="毒逆天"
name_encode=name.encode("utf-8")
print(name_encode)
print(name_encode.decode("utf-8"))
# 須要導入urlib.parse
import urllib.parse
test_str="淡定"
# 對字符串進行url編碼和解碼
test_str_enode = urllib.parse.quote(test_str)
print(test_str_enode)
# urllib.parse.quote() 解碼
print(urllib.parse.unquote(test_str_enode))
# urlencode 能夠直接對一個key-value進行編碼
test_dict={"name":"毒逆天","age":23}
encode_str = urllib.parse.urlencode(test_dict)
print(encode_str)
print(urllib.parse.unquote(encode_str))
小明不樂意了,你個小潘老是搶個人風頭,看完標題就刷刷的在黑板上寫下了以下知識點:
is 是比較兩個引用是否指向了同一個對象(id()
獲得的地址同樣則相同)
== 是比較兩個對象的值是否相等
在以前講Dict的時候提了一下可變和不可變類型:http://www.javashuo.com/article/p-ftgdkctm-g.html
Func裏面又系統的說了一下:http://www.javashuo.com/article/p-obgfrmjz-g.html
對於可變不可變系列就不去複述了,下面再來幾個案例看看 值判斷和 地址判斷的概念
################ 可變類型 ################
a=[1,2,3]
b=[1,2,3]
# id不同,那is確定不同了
print(id(a))
print(id(b))
# a和b是否指向同一個地址
a is b
# a和b的值是否相同
a == b
################ 開始變化了 ################
# 讓a指向b的地址
a=b
# a和b的id同樣了
print(id(a))
print(id(b))
# a和b是否指向同一個地址
a is b
# a和b的值是否相同
a == b
################ 不可變類型 ################
a=1
b=1
# id同樣
print(id(a))
print(id(b))
a is b
a == b
# 可是你要注意,不是全部不可變類型都這樣的
f1=1.2
f2=1.2
# 聲明兩個相同值的浮點型變量,查看它們的id,發現它們並非指向同個內存地址(這點和int類型不一樣)
print(id(f1))
print(id(f2))
# 這個就不同了
# 這方面涉及Python內存管理機制,Python對int類型和較短的字符串進行了緩存
# 不管聲明多少個值相同的變量,實際上都指向同個內存地址,其餘的就沒這福利咯~
f1 is f2
f1 == f2
# 相似於for(int i=0;i<5;i++)
for i in range(5):
print(i)
#while循環通常經過數值是否知足來肯定循環的條件
#for循環通常是對能保存多個數據的變量,進行遍歷
name="https://pan.baidu.com/s/1weaF2DGsgDzAcniRzNqfyQ#mmd"
for i in name:
if i=='#':
break
print(i,end='')#另外一種寫法:print("%s"%i,end="")
print('\n end ...')
# 你指望的結果是:i = 5
for i in range(10):
if i == 5:
print("i = %d" % i)
else:
print("沒有找到")
# 當迭代的對象迭代完併爲空時,位於else的子句將執行
# 而若是在for循環中含有break時則直接終止循環,並不會執行else子句
# 正確寫法以下:
for i in range(10):
if i == 5:
print("i = %d" % i)
break
else:
print("沒有找到")
# 遍歷一個字典
test_dict={"Name":"小明","Age":23}
for k,v in test_dict.items():
print("key:%s,value:%s"%(k,v))
若是下面知識點還不熟悉的,看看以前講的~列表生成式:http://www.javashuo.com/article/p-ftgdkctm-g.html
簡寫:list(range(1, 11))
全寫:[x for x in range(1,11)]
list(range(1, 11))
[x for x in range(1,11)]
# 1~10的平方列表
[x*x for x in range(1,11)]
# 1~10之間的偶數
[x for x in range(1, 11) if x % 2 == 0]
# 數學裏面的全排列
[x + y for x in 'ABC' for y in 'AB']
# 數學裏面的座標軸
[(x,y) for x in range(1,5) for y in range(1,4)]
# (x,y,z) 通常三個嵌套就上天了
[(x,y,z) for x in range(1,5) for y in range(1,4) for z in range(1,3)]
若是要對list實現相似C#或者java那樣的下標循環怎麼辦?
這塊小明又有預習,因而在提交Code的同時大聲說道:
Python內置的enumerate
函數能夠把一個list變成索引-元素
對,這樣就能夠在for循環中同時迭代索引和元素自己
for i, item in enumerate(['A', 'B', 'C']):
print(i, item)
看到標題小明和小潘就楞了,老師當時沒講解啊,而後兩我的眼巴巴的看着老師講解:
官方文檔:https://docs.python.org/3/library/copy.html
經過=
來實現,就是把地址拷貝了一份,好比 a = b
a=[1,2,2]
b = a
print(id(a))
print(id(b))
# 再驗證
a.append(3)
# 都增長了一個3,說明的確指向同一個內存地址
print(a)
print(b)
import copy
a=[1,2,2]
b=copy.deepcopy(a)
# 指向了不一樣的內存地址
print(id(a))
print(id(b))
# 再驗證一下
a.append(3)
# b不變,說明的確指向不一樣的內存地址
print(a)
print(b)
################ 開始變化了 ################
# 以前講了嵌套列表,咱們來驗證一下
a=[1,2,2]
b=[1,2,3,a]
c=copy.deepcopy(b)
# 發現地址都不同
print(id(b))
print(id(c))
print(id(b[3]))
print(id(c[3]))
# 直觀的驗證一下
a.append(666)
# 深拷貝的確是深拷貝
print(b)
print(c)
copy只是簡單拷貝,若是拷貝內容裏面還有引用之類的,他是無論的
import copy
a=[1,2,2]
b=copy.copy(a)
# 指向了不一樣的內存地址
print(id(a))
print(id(b))
################ 開始變化了 ################
# 以前講了嵌套列表,咱們來驗證一下
a=[1,2,2]
b=[1,2,3,a]
c=copy.copy(b)
# 第一層地址不同
print(id(b))
print(id(c))
# 驗證一下
b.append(111)
# 第一層指向的不一樣地址
print(b)
print(c)
# 若是裏面還有引用,那麼就無論了
print(id(b[3]))
print(id(c[3]))
# 驗證一下
a.append(666)
# 內部引用的確沒copy新地址
print(b)
print(c)
若是拷貝的對象是不可變類型,無論深拷貝和淺拷貝以及賦值都是地址引用。但當拷貝的不可變對象含有引用類型時,只有深拷貝(deepcopy)會遞歸複製
須要注意的是:Python和Net對於值類型處理是不同的(管理方式不同致使的)
==>NET中值類型默認是深拷貝的,而對於引用類型,默認實現的是淺拷貝
a=(1,2,2)
b=a
print(id(a))
print(id(b))
a=(1,2,2)
b=copy.deepcopy(a)
print(id(a))
print(id(b))
a=(1,2,2)
b=copy.copy(a)
print(id(a))
print(id(b))
擴:當拷貝的不可變對象含有引用類型時:賦值和淺拷貝不會copy,而深拷貝(deepcopy)會遞歸複製
PS:咱們經常使用的切片至關於淺拷貝(copy.copy()
)
小明聽懂了Python的深拷貝和淺拷貝後,本着學以至用的原則,寫下了C#的實現:
先聲明一下,本機環境是Ubuntu + NetCore,歡迎貼Code補充
Code:https://github.com/lotapp/BaseCode/tree/master/netcore/3_Ext/deepcopy
賦值方法和Python
同樣,直接賦值便可
var list1 = new List<int>() { 1, 2, 2 };
var list2 = list1;
%%script csharp
// Python同樣,直接賦值便可
var list1 = new List<int>() { 1, 2, 2 };
var list2 = list1;
// 驗證一下
list1.Add(3);//咱們修改一下list1,list2也就跟着就改變了
foreach (var item in list1)
{
Console.Write(item + " ");
}
Console.WriteLine();
foreach (var item in list2)
{
Console.Write(item + " ");
}
NetCore深拷貝相關的官方文檔 public void CopyTo (T[] array);
簡單類型用最簡單的方式就能實現深拷貝了:
官方的CopyTo在這裏和這個效果同樣,可是比較麻煩,這邊就不貼了(Code裏面貼了)
var list3 = new List<int>() { 1, 2, 2 };
var list4 = new List<int>(list3);
// 驗證一下
list3.Add(3);
foreach (var item in list3)
{
Console.Write(item + " ");
}
Console.WriteLine();
foreach (var item in list4)
{
Console.Write(item + " ");
}
結果:
1 2 2 3 1 2 2
對於List<T>
再複雜點的,上面的方式就變成淺拷貝了:(相似於Python的Copy.Copy)
官方的CopyTo在這裏和這個效果同樣,可是比較麻煩,這邊就不貼了(Demo裏面貼了)
定義一個Student
public partial class Student
{
public string Name { get; set; }
public int Age { get; set; }
public override string ToString()
{
return $"Name:{Name},Age:{Age}";
}
}
淺拷貝Demo:
var list5 = new List<Student>(){
new Student { Name = "小張", Age = 22 },
new Student { Name = "小明", Age = 23 }
};
var p = new Student() { Name = "小潘", Age = 23 };
list5.Add(p);
// 淺拷貝一份
var list6 = new List<Student>(list5);
// 淺拷貝測試
// 咱們修改一下list5,list6沒有跟着改變,說明第一層的地址的確不同
list5.Add(new Student() { Name = "小胖", Age = 24 });
// 當咱們修改小潘同窗的年齡時,你們都變了,說明真的只是淺拷貝
p.Age = 24;
foreach (var item in list5)
{
Console.WriteLine(item);
}
Console.WriteLine("=============");
foreach (var item in list6)
{
Console.WriteLine(item);
}
結果:
Name:小張,Age:22 Name:小明,Age:23 Name:小潘,Age:24 Name:小胖,Age:24 ============= Name:小張,Age:22 Name:小明,Age:23 Name:小潘,Age:24
對於List<T>
的深拷貝場景,其實項目中仍是蠻常見的,那深拷貝怎麼搞呢?
先來一個簡單的實現方式,須要T
實現ICloneable
接口才行:
定義一個Person類
public partial class Person : ICloneable
{
public string Name { get; set; }
public int Age { get; set; }
//實現ICloneable的Clone方法
public object Clone()
{
return base.MemberwiseClone();//調用父類方法便可
}
public override string ToString()
{
return $"Name:{Name},Age:{Age}";
}
}
給List<T>
定義一個擴展方法:(舒適提醒:擴展方法所在的類必須是static Class哦)
public static partial class ListExt
{
// 只要T實現了ICloneable接口就能夠了
public static IEnumerable<T> DeepCopy<T>(this IEnumerable<T> list) where T : ICloneable
{
return list.Select(item => (T)item.Clone()).ToList();
}
}
來個調用加驗證:
#region 引用類型深拷貝-簡單實現方式
var oldList = new List<Person>(){
new Person(){Name="小明",Age=23},
new Person(){Name="小張",Age=22},
};
var xiaoPan = new Person() { Name = "小潘", Age = 23 };
oldList.Add(xiaoPan);
var newList = oldList.DeepCopy();
//測試
oldList.Add(new Person() { Name = "小胖", Age = 23 });
xiaoPan.Age = 24;
foreach (var item in oldList)
{
Console.WriteLine(item);
}
Console.WriteLine("========");
foreach (var item in newList)
{
Console.WriteLine(item);
}
#endregion
結果:
Name:小明,Age:23 Name:小張,Age:22 Name:小潘,Age:24 Name:小胖,Age:23 ======== Name:小明,Age:23 Name:小張,Age:22 Name:小潘,Age:23
利用System.Runtime.Serialization
序列化與反序列化實現深拷貝
先定義一個Teacher類(別忘記加 Serializable
的標籤)
[Serializable]
public partial class Teacher
{
public string Name { get; set; }
public int Age { get; set; }
public override string ToString()
{
return $"Name:{Name},Age:{Age}";
}
}
添加一個擴展方法:
public static partial class ListExt
{
// 利用System.Runtime.Serialization序列化與反序列化實現深拷貝
public static T DeepCopy2<T>(this T obj)
{
using (var stream = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(stream, obj);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
}
調用:
#region 引用類型深拷貝-序列化實現
var oldTestList = new List<Teacher>(){
new Teacher(){Name="小明",Age=23},
new Teacher(){Name="小張",Age=22},
};
var s = new Teacher() { Name = "小潘", Age = 23 };
oldTestList.Add(s);
var newTestList = oldTestList.DeepCopy2();
//測試
oldTestList.Add(new Teacher() { Name = "小胖", Age = 23 });
s.Age = 24;
foreach (var item in oldTestList)
{
Console.WriteLine(item);
}
Console.WriteLine("========");
foreach (var item in newTestList)
{
Console.WriteLine(item);
}
#endregion
結果:
Name:小明,Age:23 Name:小張,Age:22 Name:小潘,Age:24 Name:小胖,Age:23 ======== Name:小明,Age:23 Name:小張,Age:22 Name:小潘,Age:23
由於主要是說Python,Net只是簡單提一下,這邊就先到這裏了
不盡興能夠看看這篇文章,講得仍是挺全面的
咱們接着來對比學習~
# 列表生成式
[x for x in range(10)]
# 生成器寫法(Python2.x系列是用xrange)
(x for x in range(10))
遍歷方式能夠用以前的for
循環來遍歷(推薦)
也能夠用next()
或者__next__()
方法來遍歷。【C#是用MoveNext
】
generator
保存的是算法,每次調用next(xxx)
或者__next__()
,就計算出下一個元素的值,直到計算到最後一個元素
當沒有更多的元素時,拋出StopIteration
的異常
最新的Python3.7在這方面有所優化:https://www.python.org/dev/peps/pep-0479
g=(x for x in range(10))
# for來遍歷(推薦)
for i in g:
print(i)
g=(x for x in range(10))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(g.__next__()) #經過__next__也同樣取下一個
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
# 遞歸方式:求第30個數是多少
# 一、一、二、三、五、八、1三、2一、34...
def fib(n):
if n == 1 or n == 2:
return 1
else:
return fib(n - 1) + fib(n - 2)
fib(30)
# 在講yield方式以前先用循環實現一下
def fibona(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
fibona(30)
# for循環實現
def fibona(n):
a, b = 0, 1
# [0,n)
for i in range(n):
print(b)
a, b = b, a + b
fibona(30)
a, b = b, a + b
以前交換兩數的時候提過
這個至關於==>
temp_tuple = (b, a + b)
a = temp_tuple[0]
b = temp_tuple[1]
要把fibona
函數變成generator
,只須要把print(b)
改成yield b
就能夠了:
generator
在執行過程當中,遇到yield
就中斷,下次又繼續執行到yield
停下了,一直到最後
生成器的特色:
# 改爲生成器比較簡單,直接換輸出爲yield
def fibona(n):
a, b = 0, 1
# [0,n)
for i in range(n):
yield b
a, b = b, a + b
# 看看是否是生成器
g = fibona(30)
g
# 遍歷輸出(基本上都會用for來遍歷)
for i in g:
print(i)
對於函數改爲的generator來講,遇到return語句或者執行到函數體最後一行語句,就是結束generator的循環的時候
小明總結以下:
在Python中,這種一邊循環一邊計算的機制稱爲生成器:generator
每個生成器都是一個迭代器(迭代器不必定是生成器)
若是一個函數包含yield關鍵字,這個函數就會變爲一個生成器
生成器並不會一次返回全部結果,而是每次遇到yield關鍵字後返回相應結果,並保留函數當前的運行狀態,等待下一次的調用
因爲生成器也是一個迭代器,那麼它就支持next用方法來獲取下一個值(咱們平時用for來遍歷它)
推薦一篇文章,總結的很全了:(yield用法總結)
send(msg)
方法:¶其實__next__()
和send()
在必定意義上做用是類似的,區別是send()
能夠傳遞yield表達式的值進去
而__next__()
不 能傳遞特定的值。咱們能夠看作x.__next__()
和 x.send(None)
做用是同樣的
# 來個案例:
def test_send(n):
for i in range(n):
tmp = yield i
print(tmp)
g = test_send(5)
g
# 定義一個列表
test_list = []
# 把第一次yield的值放在列表中
test_list.append(g.__next__())
# 把list傳給tmp並打印(能夠理解爲把表達式右邊的 yield i 暫時換成了 test_list)
# out的內容是yield返回的值
g.send(test_list)
# 以防大家看不懂,來個簡單案例
# 你傳啥print(tmp)就給你打印啥
g.send("你好啊")
注意一種狀況,generator
剛啓動的時候,要麼不傳,要麼只能傳None
解決:要麼一開始send(None)
要麼一開始先調用一下__next()__
or next()
# 注意一種狀況,generator剛啓動的時候,要麼不傳,要麼只能傳None
def test_send(n):
for i in range(n):
tmp = yield i
print(tmp)
g = test_send(5)
g.send("dog") # TypeError: can't send non-None value to a just-started generator
# 解決:要麼一開始send(None)要麼一開始先調用一下__next()__ or next()
def test_send(n):
for i in range(n):
tmp = yield i
print(tmp)
g = test_send(5)
g.send(None)
g.send("dog")
擴:C#在遍歷generator
的時候也是先調一下MoveNext
方法
while (tmp.MoveNext())
{
Console.WriteLine(tmp.Current);
}
return
和break
的說明¶在一個generator
函數中,若是沒有return
則默認執行至函數完畢
若是在執行過程當中return
或者break
則直接拋出StopIteration
終止迭代
# break案例
def test_send(n):
for i in range(n):
if i==2:
break
yield i
g = test_send(5)
for i in g:
print(i)
# return案例
def test_send(n):
for i in range(n):
if i==2:
return "i==2"
yield i
g = test_send(5)
for i in g:
print(i)
用for
循環調用generator
時,發現拿不到generator
的return
語句的返回值
若是想要拿到返回值,必須捕獲StopIteration
錯誤,返回值包含在StopIteration的value
中
# 上面return的返回值怎麼拿呢?
g = test_send(5)
while True:
try:
tmp = g.__next__()
print(tmp)
except StopIteration as ex:
print(ex.value)
break # 必定要加break,別忘了你在死循環裏呢
def consumer():
while True:
tmp = yield
# !None就變成真了
if not tmp:
return
print("消費者:",tmp)
# 建立消費者
c = consumer()
# 啓動消費者
c.send(None)
# 生產數據,並提交給消費者
c.send("小明")
c.send("小潘")
# 生產結束,通知消費者結束,拋出StopIteration異常
c.send(None) # 使用c.close()能夠避免異常
執行流程:
send(None)
或__next__()
啓動send(msg)
消息,協程恢復執行,將接收到的數據保存並執行後續流程補全demo:
def consumer():
status = ""
while True:
tmp = yield status
if not tmp:
print("消費者已經睡覺了...")
return
print("消費者:得到商品%s號..." % tmp)
status = "ok"
def produce(c):
# 啓動消費者
c.send(None)
for i in range(1, 3):
print("生產者:出產商品%s號..." % i)
# 生產商品,並提交給消費者
status = c.send(i)
print("生產者:生產者消費狀態: %s" % status)
# c.send(None) 執行這個會引起StopIteration
c.close() # 使用close就能夠避免了(手動關閉生成器函數,後面的調用會直接返回StopIteration異常)
# 建立消費者
c = consumer()
produce(c)
# 更多能夠查看幫助文檔
def test():
yield
help(test())
from collections import Iterable
isinstance("mmd",Iterable)
isinstance((1,2),Iterable)
isinstance([],Iterable)
isinstance({},Iterable)
isinstance((x for x in range(10)),Iterable)
isinstance(1,Iterable)
a=[1,2,3]
next(a)
from collections import Iterator
isinstance([],Iterator)
isinstance((x for x in range(10)),Iterator)
Iterable
轉 Iterator
¶生成器都是Iterator
對象,但list、dict、str
雖然是Iterable
,卻不是Iterator
把list、dict、str
等Iterable
變成Iterator
可使用iter()
函數:
iter(a)
isinstance(iter([]),Iterator)
isinstance(iter({}),Iterator)
Python的Iterator
對象表示的是一個數據流,Iterator
對象能夠被next()
or__next__()
函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration
錯誤
能夠把這個數據流看作是一個有序序列,但咱們卻不能提早知道序列的長度,只能不斷經過next
函數實現按需計算下一個數據,因此Iterator
的計算是惰性的,只有在須要返回下一個數據時它纔會計算。
Iterator
甚至能夠表示一個無限大的數據流,而list
等則不行
小明總結了一下老師講解的知識點:
能夠for
循環的對象都是Iterable
類型
可使用next()
or__next__()
函數的對象都是Iterator
類型
集合數據類型如list、dict、str等是Iterable
,能夠經過iter()
函數得到一個Iterator
對象
乘着下課的時間,小明跑到黑板前,心想:「又到了C#的時候了,看我來收播一大羣眼球~」,而後開始了他的我的秀:
其實迭代器(iterator
)就是爲了更簡單的建立枚舉器(enumerator
)和可枚舉類型(enumerator type
)的方式
IEnumerator
和 IEnumerable
¶通俗話講:
能不能foreach
就看你遍歷對象有沒有實現IEnumerable
,就說明你是否是一個可枚舉類型
(enumerator type
)
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
是否是個枚舉器(enumerator
)就看你實現了IEnumerator
接口沒
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
最明顯的區別:它們兩個遍歷方式不同
// 枚舉器遍歷
var tmp = FibonaByIEnumerator(30);
while (tmp.MoveNext())
{
Console.WriteLine(tmp.Current);
}
// 可枚舉類型遍歷
foreach (var item in FibonaByIEnumerable(30))
{
Console.WriteLine(item);
}
這個咱們在2年前就說過,這邊簡單提一下(官方文檔)(Demo)
MyEnumerator文件:
public class MyEnumerator : IEnumerator
{
/// <summary>
/// 須要遍歷的數組
/// </summary>
private string[] array;
/// <summary>
/// 有效數的個數
/// </summary>
private int count;
public MyEnumerator(string[] array, int count)
{
this.array = array;
this.count = count;
}
/// <summary>
/// 當前索引(線moveNext再獲取index,用-1更妥)
/// </summary>
private int index = -1;
public object Current
{
get
{
return array[index];
}
}
/// <summary>
/// 移位
/// </summary>
/// <returns></returns>
public bool MoveNext()
{
if (++index < count)
{
return true;
}
return false;
}
/// <summary>
/// 重置
/// </summary>
public void Reset()
{
index = -1;
}
}
MyArray.cs文件
public partial class MyArray
{
/// <summary>
/// 數組容量
/// </summary>
private string[] array = new string[4];
/// <summary>
/// 數組元素個數
/// </summary>
private int count = 0;
/// <summary>
/// 當前數組的長度
/// </summary>
public int Length
{
get
{
return count;
}
}
/// <summary>
/// 添加元素
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public MyArray Add(string str)
{
//要溢出的時候擴容
if (count == array.Length)
{
string[] newArray = new string[2 * array.Length];
array.CopyTo(newArray, 0);
array = newArray;//array從新指向
}
array[count++] = str;
return this;
}
/// <summary>
/// 移除某一項
/// </summary>
/// <param name="i"></param>
/// <returns></returns>
public MyArray RemoveAt(int i)
{
for (int j = i; j < count - 1; j++)
{
array[j] = array[j + 1];
}
count--;//少了一個元素因此--
return this;
}
/// <summary>
/// 索引器
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public string this[int index]
{
get
{
return array[index];
}
set
{
array[index] = value;
}
}
}
MyArrayExt.cs文件:
public partial class MyArray: IEnumerable
{
/// <summary>
/// 枚舉器方法
/// </summary>
/// <returns></returns>
public IEnumerator GetEnumerator()
{
return new MyEnumerator(this.array, this.count);
}
}
調用:
static void Main(string[] args)
{
MyArray array = new MyArray();
array.Add("~").Add("這").Add("是").Add("一").Add("個").Add("測").Add("試").Add("。").RemoveAt(0).RemoveAt(3).RemoveAt(6);
for (int i = 0; i < array.Length; i++)
{
Console.Write(array[i]);
}
Console.WriteLine();
foreach (var item in array)
{
Console.Write(item);
}
}
結果:
這是一測試 這是一測試
小明看着班裏女生羨慕的眼神,得意的強調道:
注意一下,C#是用yield return xxx
,Python是用yield xxx
關鍵字
還記得開頭說的那句話嗎?(yield官方文檔)
其實迭代器(iterator)就是爲了更簡單的建立枚舉器(enumerator)和可枚舉類型(enumerator type)的方式
若是枚舉器和可枚舉類型仍是不理解(舉個例子)就懂了:(從遍歷方式就看出區別了)
定義一個斐波拉契函數,返回可枚舉類型
/// <summary>
/// 返回一個可枚舉類型
/// </summary>
public static IEnumerable<int> FibonaByIEnumerable(int n)
{
int a = 0;
int b = 1;
for (int i = 0; i < n; i++)
{
yield return b;
(a, b) = (b, a + b);
}
}
調用:
foreach (var item in FibonaByIEnumerable(30))
{
Console.WriteLine(item);
}
定義一個斐波拉契函數,返回一個枚舉器
/// <summary>
/// 返回一個枚舉器
/// </summary>
public static IEnumerator<int> FibonaByIEnumerator(int n)
{
int a = 0;
int b = 1;
for (int i = 0; i < n; i++)
{
yield return b;
(a, b) = (b, a + b);
}
}
調用一下:
var tmp = FibonaByIEnumerator(30);
while (tmp.MoveNext())
{
Console.WriteLine(tmp.Current);
}
利用yield
輕輕鬆鬆就建立了枚舉器和可枚舉類型
以上面那個MyArray的案例來講,有了yield咱們代碼量大大簡化:(Demo)
MyArray.cs
public partial class MyArray
{
/// <summary>
/// 數組容量
/// </summary>
private string[] array = new string[4];
/// <summary>
/// 數組元素個數
/// </summary>
private int count = 0;
/// <summary>
/// 當前數組的長度
/// </summary>
public int Length
{
get
{
return count;
}
}
/// <summary>
/// 添加元素
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public MyArray Add(string str)
{
//要溢出的時候擴容
if (count == array.Length)
{
string[] newArray = new string[2 * array.Length];
array.CopyTo(newArray, 0);
array = newArray;//array從新指向
}
array[count++] = str;
return this;
}
/// <summary>
/// 移除某一項
/// </summary>
/// <param name="i"></param>
/// <returns></returns>
public MyArray RemoveAt(int i)
{
for (int j = i; j < count - 1; j++)
{
array[j] = array[j + 1];
}
array[count - 1] = string.Empty;//add 幹掉移除的數組
count--;//少了一個元素因此--
return this;
}
/// <summary>
/// 索引器
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public string this[int index]
{
get
{
return array[index];
}
set
{
array[index] = value;
}
}
}
MyArrayExt.cs
public partial class MyArray : IEnumerable
{
/// <summary>
/// 枚舉器方法
/// </summary>
/// <returns></returns>
public IEnumerator GetEnumerator()
{
return MyEnumerator();
}
/// <summary>
/// 經過yield快速實現
/// </summary>
/// <returns></returns>
public IEnumerator<string> MyEnumerator()
{
foreach (var item in this.array)
{
yield return item;
}
}
}
而後就好了,MyEnumerator都不用你實現了:
MyArray array = new MyArray();
array.Add("~").Add("這").Add("是").Add("一").Add("個").Add("測").Add("試").Add("。").RemoveAt(0).RemoveAt(3).RemoveAt(6);
for (int i = 0; i < array.Length; i++)
{
Console.Write(array[i]);
}
Console.WriteLine();
foreach (var item in array)
{
Console.Write(item);
}
結果:
這是一測試 這是一測試
擴充一下:Python
退出迭代器用yield return
或者 yield break
,C#使用yield break
來退出迭代
作個 demo 測試下:
public static IEnumerable<int> GetValue()
{
for (int i = 0; i < 5; i++)
{
yield return i;
if (i == 2)
{
yield break;
}
}
}
調用:
static void Main(string[] args)
{
foreach (var item in GetValue())
{
Console.WriteLine(item);
}
}
輸出:
0 1 2
又到了上課時間,小明灰溜溜的跑回座位,聽老師講起了閉包的知識:
函數方面還有不懂的能夠看以前講的文檔:Function Base
函數除了能夠接受函數做爲參數外,還能夠把函數做爲結果值返回(有點相似於C++裏面的函數指針了)
來看一個可變參數求和的例子:
def slow_sum(*args):
def get_sum():
sum = 0
for i in args:
sum += i
return sum
return get_sum # 返回函數引用地址(不加括號)
a = slow_sum(1, 2, 3, 4, 5)# 返回get_sum函數的引用
print(a)# 看看引用地址
print(a())# a() 這時候纔是調用get_sum()函數
其實上面一個案例就是閉包(Closure
)了,來個定義:
在函數內部再定義一個函數,而且這個函數用到了外邊函數的變量(參數
或者局部變量
),那麼將這個函數以及用到的一些變量稱之爲閉包
通俗點說就是:內部函數使用了外部函數做用域裏的變量了,那這個內部函數和它用到的變量就是個閉包
注意:當咱們調用slow_sum()
時,每次調用都會返回一個新的函數(相同的參數也同樣)
a = slow_sum(1, 2, 3, 4)
b = slow_sum(1, 2, 3, 4)
a is b
# a()和b()的調用結果互不影響
因爲閉包引用了外部函數的局部變量,則外部函數的局部變量沒有及時釋放,因此也容易消耗內存
so ==> 除非你真正須要它,不然不要使用閉包
返回函數儘可能不要引用任何循環變量,或者後續會發生變化的變量(容易出錯)
看着小明一臉懵圈的樣子,老師說道:
新講的知識點通常都不太容易快速消化,咱們再來看個閉包的好處就理解了:
好比如今咱們要根據公式來求解,以y=ax+b
爲例,傳統方法解決:
# 定義一個y=ax+b的函數公式
def get_value(a, b, x):
return a * x + b
# 每次調用都得傳 a,b
print(get_value(2, 1, 1))
print(get_value(2, 1, 2))
print(get_value(2, 1, 3))
print(get_value(2, 1, 4))
每次調用都得額外傳a、b
的值
就算使用偏函數來簡化也不合適(畢竟已是一個新的函數了):
from functools import partial
new_get_value = partial(get_value, 2, 1)
print(new_get_value(1))
print(new_get_value(2))
print(new_get_value(3))
print(new_get_value(4))
print(new_get_value(5))
簡單總結functools.partial
的做用就是:
把一個函數的某些參數設置默認值,返回一個新的函數,而後調用新函數就省得你再輸入重複參數了
而這時候使用閉包就比較合適了,並且真的是封裝了一個通用公式了
a,b的值你能夠任意變來生成新的公式,並且公式之間還不干擾,以 y=ax²+bx+c
爲例:
def quadratic_func(a, b, c):
"""y=ax²+bx+c"""
def get_value(x):
return a * x * x + b * x + c
return get_value
# 來個簡單的:x^2+1
f1 = quadratic_func(1, 0, 1)
print(f1(0))
print(f1(1))
print(f1(2))
print(f1(3))
print(f1(4))
print(f1(5))
# 可能不太形象,咱們畫個圖看看:
import matplotlib.pyplot as plt # 導入matplotlib的pyplot模塊
# 生成x和y的值
x_list = list(range(-10, 11))
y_list = [x * x + 1 for x in x_list]
print(x_list)
print(y_list)
# 畫圖
plt.plot(x_list, y_list)
# 顯示圖片
plt.show()
# 再來個簡單的:x^2-1
f2 = quadratic_func(1, 0, -1) # 相互之間不干擾
print(f2(0))
print(f2(1))
print(f2(2))
print(f2(3))
print(f2(4))
print(f2(5))
聽完閉包老師就下課了,說什麼明天接着閉包講啥裝飾器的。
小明一愣一愣的,而後就屁顛的跑黑板前講起了C#版本的閉包:
先看看怎麼定義一個閉包,和Python同樣,用個求和函數舉例:(返回一個匿名函數)
// 有返回值就用Func,沒有就用Action
public static Func<int> SlowSum(params int[] args)
{
return () =>
{
int sum = 0;
foreach (var item in args)
{
sum += item;
}
return sum;
};
}
調用:
static void Main(string[] args)
{
var f1 = SlowSum(1, 2, 3, 4, 5);
Console.WriteLine(f1);
Console.WriteLine(f1());
}
結果:(從結果能夠看到,f1是一個函數,等你調用f1()纔會求和)
System.Func`1[System.Int32] 15
接着講 ~ 以上面的 y=ax²+bx+c
爲例,C#實現:
// 以上面的 y=ax²+bx+c 爲例,C#實現:
public static Func<double, double> QuadraticFunc(double a, double b, double c)
{
return x => a * x * x + b * x + c; // 返回一個匿名函數
}
調用:
static void Main(string[] args)
{
var func = QuadraticFunc(1, 0, 1);
Console.WriteLine(func(0));
Console.WriteLine(func(1));
Console.WriteLine(func(2));
Console.WriteLine(func(3));
Console.WriteLine(func(4));
Console.WriteLine(func(5));
}
結果:
1 2 5 10 17 26
Func<double,double>
不理解就看看定義就懂了:public delegate TResult Func<in T, out TResult>(T arg);
這部分不是很難,簡單提一下知識點便可。若是你想深究能夠==> (點 點 點)
在收穫滿滿一籮筐眼球后,小明拍拍屁股去了新開的飯店大吃一頓了...
寫在最後:還有一些內容沒寫,估計過幾天又有一篇叫 「基礎拓展」 的文章了,爲啥不一塊兒寫完呢?
其實逆天也想寫完,真寫完文章又被叫作長篇大論一百頁了 #^_^# 行了,聽取你們意見,不寫那麼長的文章,下次見~