有的UE画不好PPT,好的测试却会用Python加薪
本文由腾讯WeTest授权发布
作者:jhouyang,腾讯资深后台开发工程师。
链接:http://wetest.qq.com/lab/view/113.html
本文由腾讯WeTest授权发布,如需转载请联系腾讯WeTest获得授权。
WeTest导读
想要升职加薪,强大的专业能力很重要,好的UE要有能力设计好一款PPT,设计不好就要被嘘。那好的开发者呢?好的一些开发者会靠一手娴熟的Python技巧来升职加薪。本文作者jhouyang早年接触多年Python,通过本文记录早年Python的经验之谈,供大家交流学习。
百度UE总监在2016年国际体验设计大会上演讲被嘘一事已经闹得沸沸扬扬,其被嘘的原因除了主线内容偏题、格局太low、表达方式糟糕以及插播广告以外,其PPT的设计水准和其UE总监的身份严重不符成为了大家最为不满的一点。
其PPT的标题没有居中,标点符号缺失,色彩搭配硬伤等PPT设计中的低级错误让人们对他的专业能力产生了巨大的怀疑。(具体情况大家可以自行搜索“如何评价百度大 UE 总监刘超在 2016 国际体验设计大会的演讲?”)
说到这里,小编想表达的是,想要升职加薪,强大的专业能力很重要,好的UE要有能力设计好一款PPT,那好的测试呢?
好的开发者一般会用Python来装逼。
1、 三目操作符
学过C语言的同学,或者看过《C语言从入门到放弃》这本书的同学一定不会对“三目操作符”这种画面感十足的操作符感到陌生。
然而,直到我完成Python小学一年级课程的时候才发现原来Python也有类似的东西。比如:
def foo(val): if val == 42: return "you are a programmer!" else : return "you are a manong!" |
或者你也可以这样写:
def foo(val):
return
"you are a programmer!"
if
val == 42
else
"you are a manong!"
友情提示:此特技要小心使用。否则容易暴露你大师的本质。不要像撸主当年,把所有的if/else都改成神都看不懂的 “do xxx if xx else xxx”句式。
2、会叫的都是鸭子
讲道理的话,这个应该是一个坑,而非装逼技巧。由于撸主当年是从C++党转学Python的,刚开始写代码总喜欢这样:
12 if
isinstance
(
'c'
, CPlusPlus):
assert
(
isinstance
(
'python'
, CPlusPlus))
更变态的时候甚至这样:
1 assert
(
type
(
'C++'
)
=
=
CPlusPlus))
聪明的你可能会问撸主:“type和isinstance有什么区别啊?”。可你想不到的是撸主会回答你:“自己google去“,然后留给你一个傲娇的背影。
等等!撸主你说这玩意跟“鸭子”有什么关系。事实是酱紫的,有一天撸主正在琢磨“开电脑用左手开机好,还是右手开机好,还是双手齐上好”这个宇宙终极问题时,一位高年级的大哥哥过来告诉我:“没必要这样,没必要到处assert isinstance 这种。因为Python是鸭子类型。会叫的都是鸭子。”
然后撸主问他“你会叫吗?”
友谊的小船说翻就翻……
“什么?你不知道鸭子类型?不会自己google吗?”
3、内建函数和lamda
比如如下一个简单的例子:我需要将一个字符串列表中所有满足包含“result”字段的字符串筛选出来。
低年级的同学可能会这么做:
1 def
foo(xxx): result_list
=
list
()
for
ele
in
org_list:
if
"result"
in
ele: result_list.append(ele)
BUT,这样做“一点都不酷!”。要酷还得靠装逼:
1 result_list
=
filter
(
lambda
ele :
"result"
in
ele, org_list)
不信的话,你就去试试咯:
1 map
(
lambda
ele : ele
+
1
, (
1
,
2
,
3
,
4
))
reduce
(
lambda
x,y:x
+
y,
range
(
3
))
4、 iterator和generator
这俩哥们看着很像、读着也很像。
其实不难区分,generator返回不用return,而用yield;所以你凡是看到yield的地方,很可能就是generator这装逼犯。
iterator呢,就是实现了next()方法和iter,iter方法返回它自己。而当你调用next()方法时,会返回一个值。通常,这个next值会由generator产生。
换种说法,你也可以这样理解,generator是用来生成iterator的。
闲话少说,举个栗子。
如果高年级的同学问你:小学生?怎么保证埃希不空大?你一定要回答他,最简单的generator是这样的:
generator=(i for i in xrange(0, 3))
纳尼?yield呢?哦,你要yield啊,那就这样写:
1 def
generator():
for
i
in
range
(
3
):
yield
i
你看,我顺便又介绍了一道幼儿园考题:range和xrange的区别。
那么iterator呢?都说了generator就是用来生成iterator的啦。
12 generator.
next
()
/
/
第一个
generator().
next
()
/
/
第二个
上面只是一个简单的示例,这段简单的代码在实际的工作中并没有什么卵用。
那么它应用的场景有吗?下一次返回的结果依赖于上一次返回的结果。因为yield的作用是每次函数调用执行到这里就停止了,下次调用从yield后面的语句开始。
比如说树的遍历之类的。
5、 bind
bind这种东西简直是为撸主这种懒人量身定制的,想想啊:你写一个好多好多参数的函数,没事你bind一个参数,又变成了函数A,你再换种姿势bind,又变成了函数B。
用过C++ boost库,或者对C++1x有所了解的童鞋可能知道我说的是什么了。如果没了解过,请参考上面一段话自行脑补。
其他的小节撸主介绍的都是些Python内置函数,或者’__’开头的内置方法等。这个小节撸主准备介绍一条裤。
你可能会问:撸主撸主,裤跟bind有什么关系?别急嘛,骚年。这个库是functools,里面有好几个函数都棒棒哒哦。不过这里撸主只准备介绍跟bind相似的partial方法。
看看它的用法,是不是跟bind很像?
12345 def
add(a, b):
return
a
+
b
plus2
=
functools.partial(add,
2
)
plus3
=
functools.partial(add,
3
)
....
6、 修饰器
decorator
有时候撸代码撸累了,想发发微信,找个人帮忙撸代码。你可以试试这样:@xxx 帮我打个日志。XXX就帮你打日志了。
1234567 def
log(func):
def
wrap(
*
arg,
*
*
kargs):
start
=
time.time()
func(
*
arg,
*
*
kargs)
end
=
time.time()
print
" time = %.6f sec"
%
(end
-
start)
return
wrap
然后你想要打日志,又懒得撸代码了,就这样:
1 @log
def
foo(arg):
# do something
以下是干货,容易着火。
修饰器的本质就是对函数做些修饰,然后返回一个函数(callable object)。也就是所谓的高阶函数。
因此上面的式子不用语法糖直白的写出来就是:
1 foo
=
log(foo)
看到没,foo其实就是一个log返回的callable object wrap的别名。
举个栗子,如果需要这样的修饰器,我们应该怎么写呢?
123 @decro
(
1
,
2
)
def
foo(
*
args,
*
*
kargs):
pass
先翻译一下,先把(1,2)传给decro,然后把foo传给decro;然后你返回给我一个能接受(*args,**kargs)参数的函数:
1 foo
=
decro(
1
,
2
)(foo)
一定要记住,foo是一个callable object。事情就变得很简单了:
123456 def
decro(
*
args,
*
*
kargs):
# 1,2
def
wrap(func):
# foo
def
_(
*
args,
*
*
kargs):
# foo 也必须是一个callable
func(
*
args,
*
*
kargs)
return
_
return
wrap
哪里不会点哪里,就是这么简单。装逼技能Decorator GET!
wraps
到这里就结束了?如果到这里就结束了,高年级的同学知道了一定又会回来鄙视我们:你试试打印下foo函数,看看是什么?你试着打印一下:
1 >>> foo=
"
" at="
" 0x7f4875b77398="
"><
/
function>
天啦噜!不是foo么,怎么变成”_”了?
别急,这个时候你需要前一节提到的那条裤了:functools,然后对你的decorator做一点小小的改动:
123456789 from
functools
import
wraps
def
decro(
*
args,
*
*
kargs):
def
wrap(func):
# 看这里看这里
@wraps
(func)
def
_(
*
args,
*
*
kargs):
func(
*
args,
*
*
kargs)
return
_
return
wrap
然后foo就变成了:
1 >>> foo=
"
" at="
" 0x7f4875b77938="
"><
/
function>
沿着修饰器继续深挖,你可以挖出所谓的函数式编程、闭包一大堆看起来很高大上的概念。可是由于撸主水平有限,只好劳烦您自行google啦。
7、 descriptor
至于描述器,多的不多说了。直接看看使用前后效果对比图:
缘起
当时情况是这样的,撸主正在看clang Python binding的代码,看到这么一段:
123456789101112 class
CachedProperty(
object
):
def
__init__(
self
, wrapped):
self
.wrapped
=
wrapped
try
:
self
.__doc__
=
wrapped.__doc__
except
:
passdef __get__(
self
, instance,
type
=
None
):
if
instance
is
None
:
return
self
value
=
self
.wrapped(instance)
setattr
(instance,
self
.wrapped.__name__, value)
return
value
当时我的心情是这样的:
内置方法
有点流弊啊!虽然我看不懂,但是警察叔叔早就告诉过我“外事不决问google“啊。
1、Python有三个内置函数,set、get、delete;
2、 只定义get方法,非数据描述器(non-data descriptor);
3、 定义了delete 或者 set 方法的叫做数据描述器(data descriptor);
“虽然不知道上面的话在说什么,但是感觉好厉害的样子”。嗯,不明白没关系,不明白才好装X嘛。按照Python的尿性,凡是装X的技巧都离不开Python的内置方法,也就是’‘开头、’‘结尾的方法。由于delete不常用,俺们只要记住get和set这个东西好了。
dict
咱么先从字典说起。看下面一个例子:
123456789 class
Test(
object
): passTest.a
=
'a'
Test.b
=
'b'
t
=
Test()
t.c
=
'c'
t.b
=
'tb'
setattr
(t,
'setkey'
,
'val'
)
print
Test.__dict__
print
t.__dict__print t.aprint t.c
print
t.b
print
t.setkey
1234 {
'a'
:
'b'
,
'__module__'
:
'__main__'
,
'b'
:
'b'
,
'__dict__'
: '__dict__'
=
"
" of="
" 'test'="
" objects="
">, '__weakref__': " of="
" 'test'="
" objects="
">,
'__doc__'
:
None
}
# 这是Test.__dict__
{
'c'
:
'c'
,
'b'
:
'tb'
,
'setkey'
:
'val'
}
# 这是t.__dict__a # t.ac # t.c
tb
# t.b
val
# t.setkey
这个例子说明几点:
1、对象和类都有个字典:dict2、在对象中查找不到的属性,会从类中查找 (t.a)
3、对象中属性的优先级高于类中的优先级 (t.b)
4、设置属性(set)的时候,会在对应的dict里增加元素 (Test.a =xxx, t.b = xxx)
setattr 跟 ‘=’ 操作符,操作对象的属性时,看起来作用是一样的。
5、通常的情况下,属性查找如此简单:先找对象的dict,然后再找类的dict;都找不到就抛异常;
属性查找
当加入所谓的descriptor的时,事情变得稍微复杂了一点点。对于一个对象的属性,新的顺序是:
1、Python自动属性 (Python自动生成的属性,比如doc等)
2、 在类(及其祖先类)的dict中查找data descriptor,如果存在,返回data descriptor中get方法调用的结果
3、在对象的dict中查找
b、在类(及其祖先类)的dict中查找普通属性
这样,在原来的属性查找顺序上,我们加上了non-data descriptor和data descriptor,分别插在2、4的位置上。
回到descriptor
A:“你说的这么多,跟descriptor有什么关系?“
B: “对啊,对啊,一点都没教会我装逼!”
别急,骚年。
回到1)中的例子,然后再看看2)中的定义,有木有发现,这其实就是一个descriptor。这个CacheProperty有什么作用呢? 看下面一个使用场景(这个栗子也来自clang python binding):
1234567 class
Config:
... @CachedProperty
def
lib(
self
):
lib
=
self
.get_cindex_library()
register_functions(lib,
not
Config.compatibility_check)
Config.loaded
=
True
return
lib
Config定了一个lib方法,这个方法做一些相对耗时的操作才能获得我们想要的lib对象。比如说加载配置文件:有没有办法可以做到只在第一次调用的时候加载配置文件,其他的时候都从缓存里读呢?
看CacheProperty 的实现:
1 、首先它是一个non-data descriptor;
2、第一次的时候,按照4)所述的查找顺序,
由于Config和其对象的dict中没有lib,它会走到第4步;
然后开始执行get方法,该方法调用lib方法,计算出lib的值value=self.wrapped(instance) (这个wrap方法在CacheProperty init方法中被设置self.wrapped = wrapped, 此时wrapped就是lib方法);
随后调用setattr方法,将计算到的值set到对象的dict中
3、之后调用的时候,由于对象的dict中已经有这个key了。直接返回对应的值就可以了。
这只是个non-data descriptor的例子,它验证了属性查找中non-data descriptor顺序的正确性。
对于data descriptor属性查找,可以参看下面一个例子:
123456789101112 class
DataDesc(
object
):
def
__init__(
self
, obj):
print
'obj'
, obj
self
.obj
=
obj
def
__set__(
self
, obj, val):
print
'set called'
, obj, val
self
.obj.__name__
=
val
def
__get__(
self
, obj,
type
=
None
):
print
'__get__'
, obj, typeclass Test(
object
): @DataDesc
def
foo(
self
):
print
'foo'
def
func(
self
):
print
'func'
t
=
Test()
print
t
t.foo
=
'c'
t.__dict__[
'foo'
]
=
'c'
print
t.__dict__print t.foo
当然,这里还有几个细节没有介绍: setattr, getattr 甚至于getattribute, getattr。当你弄明白了所谓的描述器,这些东西都很简单啦!随随便便google一大堆。
8、 深度魔法–metaclass
说实话,一开始让我介绍metaclass,我是拒绝的。因为装逼,有的时候还是要站在巨人的肩膀上。
喏,链接在此,拿走不谢。
http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python
纳尼,你还问我要中文翻译版?
http://blog.jobbole.com/21351/
9、一本书
如果你只学到第8点,可能高年级的同学。还会挑战你:你那么腻害,你知道GIL么?
你可能会问:什么是GIL?然后高年级的同学肯定会鄙视你:你连GIL都不知道,肯定没看过源码吧。
你知道我只是介绍装逼特技,so,指望我这种水平去写个Python源码剖析出来是不现实的。不过我可以推书啊《Python源码剖析:深度探索动态语言核心技术》
据说下雨天,看书跟看源码更配哦。
看完此书,你的装逼领域又可以扩展到C了哦。还可以学会一点怎么让C也面向对象,告别汪星人了。
自此,妈妈再也不用担心学C的找不到对象啦!
由于水平有限,文章缪误之处,请不吝指出!多谢!