python-is-cool
我不知道存在或太害怕使用的Python功能的温和指南。随着我学到的更多信息和变得不那么懒惰,这将被更新。
这使用python> = 3.6。
GitHub在渲染Jupyter笔记本上有问题,因此我在此处复制了内容。我仍然保留笔记本,以防您要克隆并在计算机上运行它,但是也可以单击下面的活页夹徽章并将其运行在浏览器中。
1。lambda,地图,过滤,减少
Lambda关键字用于创建内联函数。下面的functionsSquare_fn和square_ld是相同的。
def square_fn ( x ):
return x * x
square_ld = lambda x : x * x
for i in range ( 10 ):
assert square_fn ( i ) == square_ld ( i )它的快速声明使lambda函数非常适合在回调中使用,并且何时将功能作为参数传递给其他函数。与MAP,过滤和减少等功能结合使用时,它们特别有用。
MAP(FN,ITOBLE)将FN应用于峰值的所有元素(例如,set,set,dictionary,tuple,string),并返回映射对象。
nums = [ 1 / 3 , 333 / 7 , 2323 / 2230 , 40 / 34 , 2 / 3 ]
nums_squared = [ num * num for num in nums ]
print ( nums_squared )
== > [ 0.1111111 , 2263.04081632 , 1.085147 , 1.384083 , 0.44444444 ]这与使用带有回调函数的MAP调用相同。
nums_squared_1 = map ( square_fn , nums )
nums_squared_2 = map ( lambda x : x * x , nums )
print ( list ( nums_squared_1 ))
== > [ 0.1111111 , 2263.04081632 , 1.085147 , 1.384083 , 0.44444444 ]您还可以使用多个峰值的地图。例如,如果要计算带有真正标签标签的简单线性函数f(x)= ax + b的平方平方误差,这两种方法是等效的:
a , b = 3 , - 0.5
xs = [ 2 , 3 , 4 , 5 ]
labels = [ 6.4 , 8.9 , 10.9 , 15.3 ]
# Method 1: using a loop
errors = []
for i , x in enumerate ( xs ):
errors . append (( a * x + b - labels [ i ]) ** 2 )
result1 = sum ( errors ) ** 0.5 / len ( xs )
# Method 2: using map
diffs = map ( lambda x , y : ( a * x + b - y ) ** 2 , xs , labels )
result2 = sum ( diffs ) ** 0.5 / len ( xs )
print ( result1 , result2 )
== > 0.35089172119045514 0.35089172119045514请注意,映射和过滤器返回的对象是迭代器,这意味着其值未存储,而是根据需要生成。打电话给sum(差异)后,差异变为空。如果要将所有元素保留在差异中,请使用列表(diffs)将其转换为列表。
滤波器(fn,iToble)的工作方式与映射相同,只是fn返回布尔值和滤波器返回FN返回true的所有元素的所有元素。
bad_preds = filter ( lambda x : x > 0.5 , errors )
print ( list ( bad_preds ))
== > [ 0.8100000000000006 , 0.6400000000000011 ]当我们想迭代操作员将操作员应用于列表中的所有元素时,将使用redair(fn,iTable,Initializer)。例如,如果我们要计算列表中所有元素的产品:
product = 1
for num in nums :
product *= num
print ( product )
== > 12.95564683272412这相当于:
from functools import reduce
product = reduce ( lambda x , y : x * y , nums )
print ( product )
== > 12.95564683272412注意Lambda功能的性能
Lambda功能是一次使用。每次调用lambda X:dosomething(x)时,必须创建函数,如果您多次调用lambda x:dosomething(x)多次(例如,当您将其传递到Replay中时)。
当您将名称分配到lambda函数中,如fn = lambda x:dosomething(x)时,其性能比使用def定义的同一函数稍慢,但是差异可以忽略不计。请参阅此处。
即使我觉得Lambdas很酷,我个人建议您在为清晰起见时使用命名功能。
2。列表操纵
Python列表非常酷。
2.1解箱
我们可以通过这样的每个元素解开列表:
elems = [ 1 , 2 , 3 , 4 ]
a , b , c , d = elems
print ( a , b , c , d )
== > 1 2 3 4我们还可以打开这样的清单:
a , * new_elems , d = elems
print ( a )
print ( new_elems )
print ( d )
== > 1
[ 2 , 3 ]
42.2切片
我们知道我们可以使用[:: - 1]扭转列表。
elems = list ( range ( 10 ))
print ( elems )
== > [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]
print ( elems [:: - 1 ])
== > [ 9 , 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1 , 0 ]语法[x:y:z]的意思是“从索引x到索引y,将列表的每个zth元素删除”。当z为负时,它表示向后移动。当未指定X时,它将默认为列表的第一个元素,以您遍历列表的方向。当未指定Y时,它将默认为列表的最后一个元素。因此,如果我们想获取列表的每个第2个要素,我们使用[:: 2]。
evens = elems [:: 2 ]
print ( evens )
reversed_evens = elems [ - 2 :: - 2 ]
print ( reversed_evens )
== > [ 0 , 2 , 4 , 6 , 8 ]
[ 8 , 6 , 4 , 2 , 0 ]我们还可以使用切片来删除列表中的所有均匀数字。
del elems [:: 2 ]
print ( elems )
== > [ 1 , 3 , 5 , 7 , 9 ]2.3插入
我们可以将列表中元素的值更改为另一个值。
elems = list ( range ( 10 ))
elems [ 1 ] = 10
print ( elems )
== > [ 0 , 10 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]如果我们想用多个元素以索引替换元素,例如,将值1替换为3个值20、30、40:
elems = list ( range ( 10 ))
elems [ 1 : 2 ] = [ 20 , 30 , 40 ]
print ( elems )
== > [ 0 , 20 , 30 , 40 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]如果我们想在索引0处的元素0和索引1的元素之间插入3个值0.2、0.3、0.5:
elems = list ( range ( 10 ))
elems [ 1 : 1 ] = [ 0.2 , 0.3 , 0.5 ]
print ( elems )
== > [ 0 , 0.2 , 0.3 , 0.5 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]2.4扁平
我们可以使用sum弄平列表的列表。
list_of_lists = [[ 1 ], [ 2 , 3 ], [ 4 , 5 , 6 ]]
sum ( list_of_lists , [])
== > [ 1 , 2 , 3 , 4 , 5 , 6 ]如果我们有嵌套列表,我们可以递归将其弄平。这是Lambda功能的另一个美 - 我们可以在与其创建的同一行中使用它。
nested_lists = [[ 1 , 2 ], [[ 3 , 4 ], [ 5 , 6 ], [[ 7 , 8 ], [ 9 , 10 ], [[ 11 , [ 12 , 13 ]]]]]]
flatten = lambda x : [ y for l in x for y in flatten ( l )] if type ( x ) is list else [ x ]
flatten ( nested_lists )
# This line of code is from
# https://gith*ub.**com/sahands/python-by-example/blob/master/python-by-example.rst#flattening-lists2.5列表与生成器
为了说明列表和生成器之间的区别,让我们看一个从令牌列表中创建n-grams的示例。
创建n-grams的一种方法是使用滑动窗口。
tokens = [ 'i' , 'want' , 'to' , 'go' , 'to' , 'school' ]
def ngrams ( tokens , n ):
length = len ( tokens )
grams = []
for i in range ( length - n + 1 ):
grams . append ( tokens [ i : i + n ])
return grams
print ( ngrams ( tokens , 3 ))
== > [[ 'i' , 'want' , 'to' ],
[ 'want' , 'to' , 'go' ],
[ 'to' , 'go' , 'to' ],
[ 'go' , 'to' , 'school' ]]在上面的示例中,我们必须同时存储所有n-gram。如果文本具有m令牌,则内存的要求为O(nm),当m大时,这可能会出现问题。
与其使用列表存储所有n-gram,我们可以使用在需要时生成下一个n-gram的生成器。这被称为懒惰评估。我们可以使用关键字收益率使函数ngrams返回生成器。然后,内存要求为O(M+N)。
def ngrams ( tokens , n ):
length = len ( tokens )
for i in range ( length - n + 1 ):
yield tokens [ i : i + n ]
ngrams_generator = ngrams ( tokens , 3 )
print ( ngrams_generator )
== > < generator object ngrams at 0x1069b26d0 >
for ngram in ngrams_generator :
print ( ngram )
== > [ 'i' , 'want' , 'to' ]
[ 'want' , 'to' , 'go' ]
[ 'to' , 'go' , 'to' ]
[ 'go' , 'to' , 'school' ]生成n -grams的另一种方法是使用切片来创建列表:[0,1,...,...,-n],[1,2,...,...,-n+1],...,[n -1,n,...,...,-1],然后将它们一起sip。
def ngrams ( tokens , n ):
length = len ( tokens )
slices = ( tokens [ i : length - n + i + 1 ] for i in range ( n ))
return zip ( * slices )
ngrams_generator = ngrams ( tokens , 3 )
print ( ngrams_generator )
== > < zip object at 0x1069a7dc8 > # zip objects are generators
for ngram in ngrams_generator :
print ( ngram )
== > ( 'i' , 'want' , 'to' )
( 'want' , 'to' , 'go' )
( 'to' , 'go' , 'to' )
( 'go' , 'to' , 'school' )请注意,要创建切片,我们在范围(n)中使用(tokens [...]),而不是[tokens [...]在范围(n)中的i]。 []是返回列表的普通列表理解。 ()返回发电机。
3。课程和魔术方法
在Python中,魔术方法前缀并用双重下划线__(也称为Dunder)后缀。最著名的魔术方法可能是__init__。
class Node :
""" A struct to denote the node of a binary tree.
It contains a value and pointers to left and right children.
"""
def __init__ ( self , value , left = None , right = None ):
self . value = value
self . left = left
self . right = right但是,当我们尝试打印一个节点对象时,它不是很容易解释的。
root = Node ( 5 )
print ( root ) # <__main__.Node object at 0x1069c4518>理想情况下,当用户打印出节点时,我们希望打印出节点的值及其孩子的值。为此,我们使用魔法方法__repr__,该方法必须返回可打印对象,例如字符串。
class Node :
""" A struct to denote the node of a binary tree.
It contains a value and pointers to left and right children.
"""
def __init__ ( self , value , left = None , right = None ):
self . value = value
self . left = left
self . right = right
def __repr__ ( self ):
strings = [ f'value: { self . value } ' ]
strings . append ( f'left: { self . left . value } ' if self . left else 'left: None' )
strings . append ( f'right: { self . right . value } ' if self . right else 'right: None' )
return ', ' . join ( strings )
left = Node ( 4 )
root = Node ( 5 , left )
print ( root ) # value: 5, left: 4, right: None我们还想通过比较它们的值来比较两个节点。为此,我们用__eq__,<with __lt__和> = = __ge ____________________________________________。
class Node :
""" A struct to denote the node of a binary tree.
It contains a value and pointers to left and right children.
"""
def __init__ ( self , value , left = None , right = None ):
self . value = value
self . left = left
self . right = right
def __eq__ ( self , other ):
return self . value == other . value
def __lt__ ( self , other ):
return self . value < other . value
def __ge__ ( self , other ):
return self . value >= other . value
left = Node ( 4 )
root = Node ( 5 , left )
print ( left == root ) # False
print ( left < root ) # True
print ( left >= root ) # False有关此处支持的魔术方法的全面列表,或在此处查看官方的Python文档(更难阅读)。
我强烈建议使用的一些方法:
- __len __:要超载len()函数。
- __STR__:要超载str()函数。
- __ITER__:如果您想成为迭代器。这也使您可以在对象上调用Next()。
对于Node之类的类,我们可以确保它们可以支持的所有属性(对于节点,它们是值,左和右),我们可能想使用__slots__代表这些值的性能增强和存储器保存。要全面了解__slots__的优缺点,请参见Aron Hall在Stackoverflow上的绝对惊人答案。
class Node :
""" A struct to denote the node of a binary tree.
It contains a value and pointers to left and right children.
"""
__slots__ = ( 'value' , 'left' , 'right' )
def __init__ ( self , value , left = None , right = None ):
self . value = value
self . left = left
self . right = right 4。本地名称空间,对象的属性
Locals()函数返回包含本地名称空间中定义的变量的字典。
class Model1 :
def __init__ ( self , hidden_size = 100 , num_layers = 3 , learning_rate = 3e-4 ):
print ( locals ())
self . hidden_size = hidden_size
self . num_layers = num_layers
self . learning_rate = learning_rate
model1 = Model1 ()
== > { 'learning_rate' : 0.0003 , 'num_layers' : 3 , 'hidden_size' : 100 , 'self' : < __main__ . Model1 object at 0x1069b1470 > }对象的所有属性都存储在其__ -dict__中。
print ( model1 . __dict__ )
== > { 'hidden_size' : 100 , 'num_layers' : 3 , 'learning_rate' : 0.0003 }请注意,当参数列表很大时,将每个参数手动分配给属性可能会很累。为了避免这种情况,我们可以将参数列表直接分配给对象的__ -dict__。
class Model2 :
def __init__ ( self , hidden_size = 100 , num_layers = 3 , learning_rate = 3e-4 ):
params = locals ()
del params [ 'self' ]
self . __dict__ = params
model2 = Model2 ()
print ( model2 . __dict__ )
== > { 'learning_rate' : 0.0003 , 'num_layers' : 3 , 'hidden_size' : 100 }当使用All ** kwargs启动对象时,这可能特别方便,尽管应将** kwargs的使用降低到最小值。
class Model3 :
def __init__ ( self , ** kwargs ):
self . __dict__ = kwargs
model3 = Model3 ( hidden_size = 100 , num_layers = 3 , learning_rate = 3e-4 )
print ( model3 . __dict__ )
== > { 'hidden_size' : 100 , 'num_layers' : 3 , 'learning_rate' : 0.0003 }5。野外进口
通常,您会遇到这种看起来像这样的野外进口 *:
file.py
from parts import *这是不负责任的,因为它将导入模块中的所有内容,即使是该模块的导入。例如,如果parts.py看起来像这样:
parts.py
import numpy
import tensorflow
class Encoder :
...
class Decoder :
...
class Loss :
...
def helper ( * args , ** kwargs ):
...
def utils ( * args , ** kwargs ):
...由于parts.py没有指定__________py将导入编码器,解码器,损失,UTILS,助手,以及numpy和tensorflow。
如果我们打算只能在另一个模块中导入和使用编码器,解码器和损失,则应在parts.py中使用__________的关键字来指定。
parts.py
__all__ = [ 'Encoder' , 'Decoder' , 'Loss' ]
import numpy
import tensorflow
class Encoder :
...现在,如果某些用户不负责任地用零件导入了狂野的导入,他们只能导入编码器,解码器,损失。就个人而言,我也发现__ All __有帮助,因为它为我提供了模块的概述。
6。装饰器时间来计时您的功能
知道运行需要多长时间,例如,当您需要比较两种执行同一操作的算法的性能时,通常会很有用。一种幼稚的方法是在每个功能的开始和结束时打电话时间()并打印出差异。
例如:比较两种算法来计算第n-fibonacci编号,一个人使用回忆,一个不使用。
def fib_helper ( n ):
if n < 2 :
return n
return fib_helper ( n - 1 ) + fib_helper ( n - 2 )
def fib ( n ):
""" fib is a wrapper function so that later we can change its behavior
at the top level without affecting the behavior at every recursion step.
"""
return fib_helper ( n )
def fib_m_helper ( n , computed ):
if n in computed :
return computed [ n ]
computed [ n ] = fib_m_helper ( n - 1 , computed ) + fib_m_helper ( n - 2 , computed )
return computed [ n ]
def fib_m ( n ):
return fib_m_helper ( n , { 0 : 0 , 1 : 1 })让我们确保FIB和FIB_M在功能上是等效的。
for n in range ( 20 ):
assert fib ( n ) == fib_m ( n ) import time
start = time . time ()
fib ( 30 )
print ( f'Without memoization, it takes { time . time () - start :7f } seconds.' )
== > Without memoization , it takes 0.267569 seconds .
start = time . time ()
fib_m ( 30 )
print ( f'With memoization, it takes { time . time () - start :.7f } seconds.' )
== > With memoization , it takes 0.0000713 seconds .如果要计时多个功能,则必须一遍又一遍地编写相同的代码。很高兴有一种方法来指定如何以相同的方式更改任何功能。在这种情况下,将在每个函数的开头和结束时致电Time(Time(),并打印出时间差。
这正是装饰者所做的。它们允许程序员更改功能或类的行为。这是创建装饰时间时间的示例。
def timeit ( fn ):
# *args and **kwargs are to support positional and named arguments of fn
def get_time ( * args , ** kwargs ):
start = time . time ()
output = fn ( * args , ** kwargs )
print ( f"Time taken in { fn . __name__ } : { time . time () - start :.7f } " )
return output # make sure that the decorator returns the output of fn
return get_time 将Decorator @TimeIT添加到您的功能中。
@ timeit
def fib (
下载源码
通过命令行克隆项目:
git clone https://github.com/chiphuyen/python-is-cool.git