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 ] 4
2.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-lists
2.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 ( <span class=\"pl
