閱讀目錄:
一、前言
二、函數定義
三、函數調用 --當做參數傳給另外一個函數
四、函數傳參
五、函數的參數分爲兩大類:形參與實參
六、可變長參數 (什麼是可變長參數? --在調用函數時,可以傳入多個實參)
七、函數調用 --在一個函數定調用另外一個函數
八、函數的遞歸調用
九、遞歸的使用 -- 二分法
一、前言
1、什麼是函數
1.1、具備某一功能的工具就是程序的函數
1.2、事先準備工具的過程稱之爲函數的定義
1.3、遇到應用場景"拿來就用"就是函數的調用
2、爲什麼要用函數
2.1、程序組織結構不清晰,可讀性差
2.2、代碼冗餘(調用一個相同的功能需要寫重複的代碼)
2.3、程序的可擴展性差(一個登陸接口被使用多次,如果登陸接口修改後,那麼需要對所有的登陸接口做修改)
3、怎麼用函數
3.1、函數的語法:
def 函數名(參數1,參數2,...):
"""
函數的文檔註釋
:param 參數1: 參數1的作用
:param 參數2: 參數1的作用
:return: 返回值的描述
"""
代碼1
代碼2
代碼3
....
return 返回值
二、函數定義:def name():
def auth(): #定義一個函數,函數內容使用同一縮進,那麼函數名爲auth,函數的內容爲同一縮進的所有值
name_a='sudada'
passwd_a='123'
name=input('pls input you name >>: ')
psd=input('pls input you passwd >>: ')
if name == name_a and psd == passwd_a:
print('login sucessfull')
else:
print('用戶名或密碼錯誤')
auth() #調用函數,即函數名加上()小括號
2.1、函數的使用必須遵循:先定義,後調用
定義函數:就相當於定義了一個變量,沒有事先定義函數,而直接使用,就相當於在引用一個不存在的變量名
在函數定定義階段只檢測語法,不執行代碼
def foo():
# xxxxxxx
if
定義階段1:定義一個函數,然後調用這個函數
def foo():
print('from foo')
bar()
foo() #調用階段
定義階段2:在定義函數的過程中,調用一個函數
def foo():
print('from foo')
bar() #調用的這個函數"bar()"沒有按順序排列,但是在函數調用時只檢測語法,不管調用是否成功
def bar():
print('from bar')
foo() #調用階段
#輸出:
>>:from foo
>>:from bar
調用階段:函數名如果不加()的話,那麼這個函數只是一個內存地址形式,加()後纔會執行這個函數
def foo(): #定義一個函數
print(foo) #如果函數foo不加()的話,那麼foo就是一個內存地址形式
# <function foo at 0x100e0d9e0>
foo() #函數foo+(),用來執行函數foo
三、函數調用 --一個函數可以當做參數傳給另外一個函數
def max2(x,y):
if x > y:
return x
else:
return y
max2(3,2)
res=max2(max2(3,2),4) #函數"max2(3,2)"運行後的值爲"3",那麼此處就把函數"max2(3,2)"的返回值"3"傳給了max2(3,4).
print(res)
>>:4
def foo():
print('from foo')
print(foo)
def bar(func): #func=foo
print(func) #打印func就相等於打印函數foo的內存地址
func() #func()就等於執行函數foo()[得到的值是一樣的]
bar(foo) #執行函數bar時傳參數爲"foo(foo爲函數),那麼func接收到的值就爲函數'foo'的內存地址"
>>:<function foo at 0x000002B418DFAD08> #這是執行print(func) 得到的
>>:from foo #這是執行func() 得到的,運行func()就相當於運行了foo()
>>:<function foo at 0x000002B418DFAD08> #這是執行print(foo)得到的
四、函數傳參
4.1、無參 (什麼時候定義無參函數? 函數內的功能被寫死了,函數體的邏輯不依賴於參數傳進來的值(也就是不需要傳參))
def psy():
print('from pay') #函數內定義的值
def check():
print('from check') #函數內定義的值
def transfer():
print('from transfer') #函數內定義的值
def run():
print(""" #以下全部爲函數內定義的值
1 支付
2 查詢
3 轉賬
""")
choic=input('pls input you number >>: ').strip()
if choic == '1':
psy() #調用函數psy
elif choic == '2':
check() #調用函數check
elif choic == '3':
transfer() #調用函數transfer
run()
4.2、有參 (什麼時候定義有參函數? 函數代碼的邏輯需要外部傳參來做判斷)
def max2(x,y):
if x > y:
print(x)
else:
print(y)
max2(30,20) #這一步是執行函數,也就是把參數傳進了函數內並執行函數,得到值
>>:30 #得到30,因爲30比20大
4.3、pass 空函數
def login():
pass #返回空值,即即使運行這個函數也不會報錯
5、return 函數返回值 函數調用--語句形式
def max2(x,y):
if x > y:
return x
else:
return y
print(max2(3,5)) #這裏獲取return的返回值
#輸出
>>:5
return 例子: 函數調用---表達式形式
def max2(x,y):
if x > y:
return x
else:
return y
max2(3,2) #這裏取出函數內經過比較之後的值"3",那麼函數"max2(3,2)"實際上就爲"3"
res=max2(3,2) * 12 #給取出的值定義一個變量.然後對這個值做運算
print(res)
>>:36
return 返回值沒有類型限制
def foo():
return 1,2,'aaa',{'x':3}
res=foo()
print(res)
>>:(1, 2, 'aaa', {'x': 3}) #返回值的類型是"元組"
return 返回值沒有個數限制,可以一次返回多個值,用逗號隔開
def foo():
return 2,'aaa',{'x':3}
x,y,z=foo()
print(x,y,z)
>>:2 aaa {'x': 3}
函數內 沒有return 時,默認返回值爲"None"
def foo():
print('---------------> ')
res=foo()
print(res)
>>:--------------->
>>:None
return 注意點:
1、return 會立刻結束函數
2、 函數內可以寫多個return,但是隻要執行了一個return,那麼函數就會結束,並且會把return後面的返回值當做這個本次函數調用的返回值
3、return 返回值沒有類型限制
4、return 返回值沒有個數限制,可以一次返回多個值,用逗號隔開
5、函數內 沒有return 時,默認返回值爲"None"
五、函數的參數分爲兩大類:形參與實參
1.形參:指的是在定義函數時括號定義的參數,形參可理解爲變量名
形參的種類:
1.位置形參
2.默認形參
2.實參:指的是在調用函數時括號傳入的值,實參可理解爲變量值
實參的種類:
1.位置實參
2.關鍵字實參
6.1、位置參數
1.位置實參
2.位置形參
位置形參:在定義函數時,按照從左到右的順序依次定義的參數
特性:位置形參必須被傳值,多一個不行少一個也不行
位置實參:在調用函數時,按照從左到右的順序依次傳入的值
特點:與形參一一對應
def foo(x,y,z) #位置形參必須被傳值,多一個或少一個都不行
print(x,y,z)
foo(1,2,3) #位置實參-在調用函數時與形參一一對應
缺點:在調用函數時,必須記住形參的位置,必須一一對應,否則容易混亂
6.2、關鍵字參數
1.關鍵字實參
關鍵字實參:在調用函數時,按照key=value的形式定義的實參,稱爲關鍵字實參
特點:可以完全打亂順序,但仍然能指名道姓地爲指定的參數傳值
def foo(x,y,z)
print(x,y,z)
foo(y=2, x=1, z=3) #把實參指定給某一個形參
6.3、位置參數與關鍵字參數混合應用 #位置實參必須在關鍵字實參之前
def foo(x,y,z):
print(x,y,z)
foo(1, y=2, z=3) #位置實參必須在關鍵字實參之前
>>:1 2 3
foo(y=2,1,z=3) #報錯,位置實參必須要在關鍵字實參的前面
foo(1,y=2,z=3,z=5) #報錯,形參不能重複賦值(同一個形參只能被賦值一次)
6.4、默認參數(指的是形參)-在定義函數時,就已經被賦值的參數,稱爲默認形參(在調用時可以不用傳值,傳值後默認參數將被覆蓋)
def foo(x,y=10): #函數默認指定了參數時
print(x,y)
foo(1) #在定義階段只爲x傳值就可以了(y可以不用傳值)
>>:1 10
foo(1,100) #當然也可以爲y重新傳值,這裏爲y重新傳值爲100,那麼執行函數得到y=100
>>:1 100
6.5、位置形參與默認形參的應用:
1、大多數場景值都固定不變時需要定義成默認形參
def foo(user,passwd,gender='male'): #在這裏定義一個默認參數gender='male'
print(user)
print(passwd)
print(gender)
foo('aaa','123')
foo('bbb','123')
foo('ccc','123',female) #那麼ccc的性別不爲male時,才需要重新定義(大多數場景都爲male)
2、大多數場景值都需要改變時需要定義成位置形參
3、默認形參必須跟在位置形參的後面
def foo(x=10,y): #默認形參必須跟在位置形參的後面,否則報錯
#def foo(x,y=10): #正確寫法
print(x)
print(y)
foo(10,11)
4、默認形參的值只在定義階段被賦值一次(固定死了),定義之後的改變不受影響(除非手動傳參,傳值後默認參數將被覆蓋)
m=10
def foo(x,y=m): #在函數的定義階段時y=m,那麼y就被賦值了一次
print(x)
print(y)
m=111111111111 #m=11111111111111時,y的值不會改變,因爲y只能被賦值一次
foo(1) #如果在調用函數的時候傳值,那麼默認參數將會被替換
>>:1
>>:10
5、默認形參的值應該設置成不可變類型
def func(name,hobby,l=None): #這裏 l=None,通過判斷用戶是否傳值來定義hobby
if l is None: #如果傳值的話,那麼就把對應的值加到hobby裏面
l=[]
l.append(hobby)
print(name,l)
func('sudada','w') #定義一個愛好,那麼就保存一個愛好
func('sudada','read',['aa','bbb','ccc']) #定義一個愛好後,在追加多個愛好,那麼所有的愛好都將被保存
>>:sudada ['w']
>>:sudada ['aa', 'bbb', 'ccc']
六、可變長參數 (什麼是可變長參數? --在調用函數時,可以傳入多個實參)
而實參的兩種形式:1 位置實參 2 關鍵字實參
所以對應的,形參也必須對應兩種解決方案,專門用於接收溢出位置實參和溢出的關鍵字實參
6.1、* 表示:接收溢出的位置實參,並存成元組的形式,然後賦值給*後面跟的那個變量名。
用法1:在形參中用*
def foo(x,y,*z): *=(3, 4, 5),*會把值賦值給z,那麼z=(3, 4, 5)
print(x,y)
print(z)
foo(1,2,3,4,5)
>>:1 2
>>:(3, 4, 5) # z的值爲(3, 4, 5)
用法2:在實參中用* 打散<==>把*號內的值都轉換成位置實參
def foo(x,y,*z):
print(x,y)
print(z)
foo(1,2,*(3,4,5,6)) #foo(1,2,*(3,4,5,6))打散後foo(1,2,3,4,5,6)
>>:1 2 #x y 的值分別爲:1 2
>>:(3, 4, 5, 6) #z 的值爲(3, 4, 5, 6)
foo(1,2,*'hello') #foo(1,2,*'hello')打散後foo(1,2,'h','e','l','l','o')
>>:1 2 #x y 的值分別爲:1 2
>>:('h', 'e', 'l', 'l', 'o') #z 的值爲('h', 'e', 'l', 'l', 'o')
注意點:*號在實參時,不管是什麼樣的類型都需要打散然後在處理(如下例子)
def foo(x,y)
print(x)
print(y)
foo(1,*(2,3,4,5)) #打散後爲foo(1,2,3,4,5),因爲形參只有x,y,而實參有1,2,3,4,5,傳入的值太多報錯
foo(1,(2,3,4,5)) #這樣就沒毛病,1是一個參數,(2,3,4,5)是一個參數
小應用:求和函數
def my_sum(*args):
res=0
for n in args:
# res=res+n
res+=n
return res
res1=my_sum(1,2,3,4,5)
print(res1)
>>:15
6.2、**表示:接收溢出關鍵字實參 以 {key:value} 的形式
用法1:在形參中用**
def foo(x,y,**z):
print(x)
print(y)
print(z)
foo(1,2,a=1,b=2,c=3,d=4) #1賦值給x,2賦值給y,"a=1,b=2,c=3,d=4"賦值給**
>>:1 #x=1
>>:2 #y=2
>>:{'a': 1, 'b': 2, 'c': 3, 'd': 4} #由於"a=1,b=2,c=3,d=4"賦值給**,那麼z={'a': 1, 'b': 2, 'c': 3, 'd': 4}
用法2:在實參中用**
def foo(x,y,**z):
print(x)
print(y)
print(z)
foo(1,2,**{'a':1,'b':2,'c':3,'d':4}) #1賦值給x,2賦值給y,{'a':1,'b':2,'c':3,'d':4}賦值給**
>>:1 #x=1
>>:2 #y=2
>>:{'a': 1, 'b': 2, 'c': 3, 'd': 4} #z={'a': 1, 'b': 2, 'c': 3, 'd': 4}
foo(1,**{'a':1,'b':2,'c':3,'d':4},y=5) #打散後得到foo(1,a=1,b=2,c=3,d=4,y=5),其中1賦值給x,y=5,剩下的值都賦值給**
>>:1 #x=1
>>:5 #y=2
>>:{'a': 1, 'b': 2, 'c': 3, 'd': 4} #z={'a': 1, 'b': 2, 'c': 3, 'd': 4}
小例子:把字典d的值一次傳給x,y,z
def foo(x,y,z):
print(x)
print(y)
print(z)
d={'x':1,'y':2,'z':3}
foo(**d) #打散後foo(x=1,y=2,z=3) 相當於: foo(**{'x':1,'y':2,'z':3})
>>:1
>>:2
>>:3
6.3、*(接收溢出的位置實參)與**(接收溢出的關鍵字實參)的混合用法:
def foo(*x,**y):
print(x)
print(y)
foo(1,2,3,4,5,a=111,b=222,c=333) #1,2,3,4,5轉換成元組的形式賦值給了x,a=111,b=222,c=333被轉換成字典的形式賦值給了y
>>:(1, 2, 3, 4, 5) #x=(1, 2, 3, 4, 5)
>>:{'a': 111, 'b': 222, 'c': 333} #y={'a': 111, 'b': 222, 'c': 333}
小例子: 通過給wrapper函數傳參,並把傳參的值轉嫁與函數wrapper內的index函數。
wrapper函數的格式爲:def wrapper(*args,**kwargs):
wrapper內部函數index想要原封不動的接收到wrapper函數的參數形式,index函數的格式也得爲:index(*args,**kwargs):
def index(name,sex):
print('welcome %s age is %s' %(name,sex))
def wrapper(*args,**kwargs): #args=(1,2,3),kwargs={'a':1,'b':2}
index(*args,**kwargs) #index(1,2,3,a=1,b=2)
wrapper((1,2,3),{'a':1,'b':2}) #給函數wrapper傳參,原封不動的給了函數index,就相當於給函數index傳參
升級版:args表示:接收溢出的位置實參,kwargs表示:接收溢出的關鍵字實參
def index(name,gender): #位置形參有2個值
print('welecom %s gender is %s' %(name,gender))
def wrapper(*args,**kwargs): #wrapper函數可以接受任意值 #args=('alex','male') kwargs={}
index(*args,**kwargs) #index接受wrapper函數的值 #index(*('alex','male'),**{}) #index('alex','male')
wrapper('alex','male')
>>:welecom alex gender is male #得到的值就是wrapper函數傳參的值。wrapper函數傳參時必須遵循index函數的格式
七、函數調用
7.1、函數的嵌套調用(在一個函數定調用另外一個函數) --例子:比較三個數的大小
def max2(x,y): #比較2個數大小的函數
if x > y:
return x
else:
return y
def max3(x,y,m): #比較3個數大小的函數
res1=max2(x,y) #在max3函數內調用函數max2
res2=max2(res1,m)
return res2
print(max3(111,45,99))
>>:111
7.2、函數的嵌套定義(在一個函數內定義另外一個函數)
def func1():
print('from func1')
def func2(): #在函數func1內定義函數func2
print('from func2')
print(func2)
func2()
func1()
>>:from func1 #調用函數func1
>>:<function func1.<locals>.func2 at 0x000002410F5FA8C8> #函數func2的內存地址
>>:from func2 #調用函數func2
小例子:比較3個數的大小
def max(x,y):
if x > y:
return x
else:
return y
def max2(x,y,z):
res1=max(x,y)
res2=max(res1,z)
return res2
rrr=max2(1,222,33)
print(rrr)
>>>:222
八、函數的遞歸調用
指的是在調用一個函數的過程,又直接或者間接地調用該函數本身,稱之爲函數的遞歸調用。
遞歸的本質就是一個重複的過程,但是每進入下一次遞歸調用時,問題的規模都應該有所減少。
8.1、遞歸的2個階段:
回溯:一層一層的調用下去,回溯階段一定要有一個明確的結束條件 。
回溯階段一定要有一個明確的結束條件,並且每一次回溯問題的規模都應該減少(否則就變成了單純的重複,沒有任何意義)
回溯階段舉例:
a=b+1
b=c+1
c=d+1
d=e+1
e=5
遞推:結束當前層的調用,進而往前一層一層的結束。
遞推階段舉例:由例1反推得到
e=5
d=e+1 = 5+1=6
c=d+1 = 6+1=7
b=c+1 = 7+1=8
a=b+1 = 8+1=9
例子:回溯與遞歸的使用,拿到第5個人的年齡
思路是:
當n>1時: age(n)=age(n-1)+10
當n=1時: age(n)=18
def age(n):
if n == 1:
return 18
if n > 1:
return age(n-1)+10 # 重複調用函數age(n),當n爲5時:+10, 當n爲4時:+10, 當n爲3時:+10, 當n爲2時:+10,當n爲1時:=18
print(age(5))
過程分析:
例子:將列表裏面的值依次取出
思路1:
l=[1,[2,[3,[4,[5,[6,[7,[8,[9,]]]]]]]]]
for n in l:
print(n)
>>: 1
>>: [2, [3, [4, [5, [6, [7, [8, [9]]]]]]]]
由於l的值爲列表裏面包含列表,所有for循環無法一步循環拿到所有值!
思路2:在思路1的基礎上~
l=[1,[2,[3,[4,[5,[6,[7,[8,[9,]]]]]]]]]
def tell(l):
for item in l:
if type(item) is not list: # 通過判斷拿到的值是否爲list類型
print(item) # 如果不是列表的話,那就取出值
else:
tell(item) # 如果還是列表的話,那就繼續循環。
tell(l)
1
2
3
4
5
6
7
8
9
九、遞歸的使用 -- 二分法
二分法:把列表從中間拆分爲2段[左邊,右邊],然後判斷,如果不符合要求,在[左邊,右邊]拆分爲2段,然後再次判斷
需求:給出一個數字,判斷這個數字是否存在列表裏面(列表的數字是從小到大依次排列的)
l=[1,11,57,89,90,99,100,123,155,188,199,230,255]
def search(num,list):
print(list)
mid_index=len(list) // 2 #把列表長度除以2
if num > list[mid_index]: #list[mid_index]按索引取值,取到的值爲列表的中間的一個值,如果num大於這個值,那麼num的值一定在中間值的右邊
#in the right
list=list[mid_index+1:] #把中間值往右~~最後的值,這一段數字放在一個列表裏面[右邊列表]
search(num,list) #然後再次把值傳入,判斷num是否在"右邊的列表"裏
elif num < list[mid_index]:
#in the left
list=list[:mid_index] #把中間值往左~~第一個值,這一段數字放在一個列表裏面[左邊列表]
search(num,list) #然後再次把值傳入,判斷num是否在"左邊的列表"裏
else:
print('find it:',list)
search(123,l)
# 這裏可以看到,一共查找了4次,就找到了123這個數字
[1, 11, 57, 89, 90, 99, 100, 123, 155, 188, 199, 230, 255]
[123, 155, 188, 199, 230, 255]
[123, 155, 188]
[123]
find it: [123]
十、max函數,取一組數字或者字符裏面的最大值 -- 能被for循環循環的類型同樣也能被max()循環
max的本質原理就是通過next(g)的方式不斷取值,然後拿到最大值。
一、元組:
res=max((1,2,3,4,5))
print(res)
5
二、列表
res1=max(['s','b','c','d'])
print(res1)
s
三、字典取值,比較key的大小
dic={
'aaa':111,
'bbb':222,
'ccc':333,
}
print(max(dic))
ccc
3.1、字典取值,如何比較value的大小?
dic={
'aaa':1111,
'bbb':222,
'ccc':333,
}
def func(k):
return dic[k]
print(max(dic,key=func))
aaa
max函數本質是通過next()的方式取值,默認針對字典的取值是字典的"key",如何改變針對字典的默認取值呢?
1、通過添加參數"key"的方式(key對應的值是一個功能):參數"key"決定了next()這個功能的比較依據。
max(dic,key=func)代碼過程分析:
1、先next(dic)取到字典的一個key("aaa"),然後把這個key("aaa")當做一個實參傳值給函數func,然後執行函數func的代碼。
2、將函數func的返回值作爲比較依據。
3、max函數就會把"函數func的返回值"最爲比較依據,然後比較大小。
4、最後不會打印出最大的那個值(value),而是打印出"value"對應的"key"。
3.2、或者使用匿名函數的方式取出字典裏面value最大值對應的key
print(max(dic,key=lambda x:dic[x]))
aaa
小例子:求a.txt文件裏面最長行的長度 -- 方法:先求出每一行的長度,然後使用max()函數比較大小
with open('a.txt','r',encoding='utf-8') as f:
num=(len(line) for line in f)
print(max(num)) # 這裏的num就相當於一個迭代器,使用max(num)就相當於使用for循環取最大值
28
# 簡寫方法:
# with open('a.txt','r',encoding='utf-8') as f:
# print(max([len(line) for line in f]))
十一、匿名函數 lambda ,匿名函數一般只使用一次
1、匿名函數不會單獨使用,會與其他函數配合使用
2、匿名函數的精髓在於沒有名字,如果沒有名字,那麼意味着用一次就立即回收,所以匿名函數的應用場景僅用於只是用一次的場景。
匿名函數的例子:
普通函數寫法:
def func(x,y):
return x+y
匿名函數寫法:
res=(lambda x,y:x+y)(1,2)
print(res)
3
註釋說明: lambda x,y:x+y
lambda #固定寫法
x,y #就相當於def func(x,y),接收2個傳參
:x+y #就相當於return x+y,匿名函數拿到的就是一個返回值
10.1、匿名函數的使用 -- 配合使用的內置函數,max,min,sorted,filter,map
1、求出一個字典裏面value值最大的一個key --- 取最大值用max,取最小值用min
思考:我們已經知道如何求出一個字典裏面key的最大值(max函數),那麼如何取出一個字典裏面value值最大的一個key呢?
dic={
'aaa':1111,
'bbb':222,
'ccc':333,
}
# 使用普通函數的方式
def func(k):
return dic[k]
print(max(dic,key=func))
aaa
# 使用匿名函數的方式
print(max(dic,key=lambda x:dic[x]))
aaa
2、sorted 排序功能,默認從小到大排序
比較列表裏字符值的大小,然後從小到大排序
print(sorted([1,9,7,5]))
[1, 5, 7, 9]
根據字典裏面value值的大小比較來排序對應的key
dic={
'aaa':1111,
'bbb':222,
'ccc':333,
}
print(sorted(dic,key=lambda x:dic[x]))
['bbb', 'ccc', 'aaa']
# 反過來比較,加一個reverse=True
print(sorted(dic,key=lambda x:dic[x],reverse=True))
['aaa', 'ccc', 'bbb']
3、map映射
需求,將列表裏面的每個元素的後面都加上字符"_sb"
name=['aaa','bbb','ccc'] # name是一個可迭代對象
res=map(lambda x:x+'_sb',name) # map(lambda x:x+'_sb',name) 拿到一個迭代器對象
print(list(res)) # 使用list(res)實際上就是使用迭代取值的方式把res裏面的值全部都取出來
['aaa_sb', 'bbb_sb', 'ccc_sb']
# 這樣修改列表裏面的值,不會佔用內存空間。
方法2:使用for循環的方式取值
l=[n+"_sb" for n in name]
print(l)
['aaa_sb', 'bbb_sb', 'ccc_sb']
4、filter
需求:將列表內字符不包含"_sb"結尾的值取出
name=['aaa_sb','bbb_sb','ccc','ddd_sb']
res=filter(lambda x:x.endswith('sb'),name) # filter 會得到name的迭代器對象obj,然後next(obj),將得到的值傳給filter第一個參數指定的函數,並且將函數返回值爲True的那個值留下
print(list(res))
['aaa_sb', 'bbb_sb', 'ddd_sb']
方法2:使用for+if判斷的方式取值
l=[n for n in name if n.endswith('sb')]
print(l)
['aaa_sb', 'bbb_sb', 'ddd_sb']