python中左闭右开是啥意思_Python的一些整理

2023-05-16

The Zen of Python, by Tim Peters(import this)

Beautiful is better than ugly.

Explicit is better than implicit.

Simple is better than complex.

Complex is better than complicated.

Flat is better than nested.

Sparse is better than dense.

Readability counts.

Special cases aren't special enough to break the rules.

Although practicality beats purity.

Errors should never pass silently.

Unless explicitly silenced.

In the face of ambiguity, refuse the temptation to guess.

There should be one-- and preferably only one --obvious way to do it.

Although that way may not be obvious at first unless you're Dutch.

Now is better than never.

Although never is often better than *right* now.

If the implementation is hard to explain, it's a bad idea.

If the implementation is easy to explain, it may be a good idea.

Namespaces are one honking great idea -- let's do more of those!

关于Python的一些杂七杂八的remarks。

Python是Object-oriented的,所谓对象(Object)就是某一个类(Class)的实例(Instance)。调用对象方法(即绑定到对象的函数)用instance.method(arguments),或者等价于Class.method(instance, arguments)。当然有些不应该在外面调用。类的好处在于封装(保护内部结构,只用接口去操作,不直接操作内部)和抽象。整个Python语言就是围绕着类和对象的概念构造起来的。Python程序中的每一个实体都是一个object,属于某个class。

Python是解释型语言而不是编译型的,所以在运行期,动态将代码逐句解释(interpret)为机器代码(解释器,interpreter)。这就带来一些定义的先后问题。编译型语言则是要通过编译器先编译为exe的。特别地,因为Python是不需要先编译的,所以这决定了你调用函数之前得知道有这个函数,所以执行代码前需要有要用到的函数的定义。但是程序执行中,读取到函数的定义的时候,这是记录下函数的名单,知道有这么些函数,但是不看函数里面的内容,当需要执行函数的时候才去读取这个函数里面的内容。因此函数之间互相引用的时候并没有顺序要求(实际上也不可能有顺序要求)。在调用函数的时候,会先建立局部名字空间,再执行函数体。所以如果名字空间中出现什么问题(比如违背了定义唯一性原则),并不会按顺序执行下来,而是直接报错。return之后,撤销局部名字空间(有例外:闭包)。

Python使用Off-side rule,用缩进来定义结构而不只是指示结构。规范是使用4个空格来表示每级缩进。使用Tab字符和其它数目的空格虽然都可以被解释器识别,但不符合编码规范。

Python是动态语言,变量使用之前不需要类型声明,通常变量的类型是被赋值的那个值的类型。 所以不像其他的静态语言如C、Pascal那样需要书写声明语句。

关于一些特殊的标识。类定义中_foo这种属性名或者函数名,表示是内部使用,不应该在类外面用类似game._turns = 1这种操作去修改一个对象。这并不是一个强制性的机制,只是相当于一个人为的提醒。foo_这种则单纯是为了避免和关键词重复而做的常用的修饰。__foo这种是强制内部使用的修饰,在类外面是根本不能找到的。而__foo__这种特殊方法名则是专门的,比如__init__,或者用来模拟加法的__add__等。

Python的表达式写法和别的语言没什么区别,有区别的地方随便查一下就能知道,不是什么要紧的事情。

关于函数。首先是可变参数*args或**dictargs。*和**实际上是打包和拆分的动作(packing/unpacking),def func(*args)是打包成tuple,def func(**dictargs)是打包成dict。调用的时候则是反过来,func(*tup)是拆分成多个参数输入函数。

然后是参数传递。Python的赋值操作是把(变量)名字绑定到对象上,形实结合也是这种方式(call by object reference)。所以,如果形参绑定到一个可变的对象,则通过形参对此对象内容的修改,在函数外也是可见的。如果形参绑定到一个不可变的对象,则通过形参是不能修改此对象内容,但可以把形参重新绑定到其它对象上,这并不影响函数外的对象的值。[在Python中,数值类型int 、float、 字符串str 、元组tuple、boole 都是不可变对象

列表list、集合set、字典dict都是可变对象]

如果想要拷贝一个list,直接b = a会把object拷贝过来,这时候变动b会同时变动a。为了避免这种情况,需要用b = a.copy()来建立一个不用object的副本。这是很容易犯的错误。

关于这点更深入的讨论需要名字空间的概念。A namespace is a mapping from names to objects.这么一句话已经说得很清楚了,Python的每一个实体就是一个object(属于某一个class),另外有一个name指向它。理解的时候只要画图,从name指向object即可。所以说Python没有指针,有的是namespace,每一个名字就相当于一个指针,到处都是指针。Namespaces are one honking great idea,拿它来理解参数传递、作用域的时候很方便。global声明全局变量(不一定要在全局作用域中有定义),nonlocal声明由内而外寻找(但非全局)的变量定义。这两个声明的区别还是非常大的。

跟C不一样,python当给变量赋值时,系统会为这个值分配内存空间,然后让这个变量指向这个值;当改变(不可变)变量的值时,系统会为这个新的值分配另一个内存空间,然后还是让这个变量指向这个新值。C改变变量的值时,改变的是内存空间中的值,变量的地址是不改变的。

名字空间可以用dic()查看。

关于作用域的问题,搞清楚这个例子就理解地差不多了:b94af2f8e0f7689f9afe03f9623f1814914fc405.png

要注意这个建立局部名字空间的过程必须是每个变量都有唯一定义,如果既来自局部的赋值由来自向外找到的,会报错。这里面的几个原则:1.赋值即定义(不能重复定义);2.全局和非局部声明规则(可以建立“赋值即定义”的例外);3.定义唯一性规则;4.不同定义相互无关规则;5.作用域屏蔽规则。

上面这个例子的分析为:(画一下namespace的图更清楚。想象自己就是一个解释器,一步步去解释这个程序,碰到调用函数的时候先建立局部名字空间,依照定义[赋值,参数,声明]在局部函数空间里放进新的名字,然后再去正式执行函数的内部过程)

1.在全局名字空间放入x和y。

2.看了一下有f1和f2两个函数。但是没有进去。

3.在全局名字空间放入w,然后调用f2。

4.来到f2的代码。建立局部名字空间,有x(局部),z(局部),y则没有再这个局部名字空间中定义(没有赋值,没有定义,不是参数,也没有全局或者非局部声明)。然后调用f1。来到f1的代码。

5.建立局部名字空间,放入x(局部),z(局部)。然后进入g。

6.g的局部名字空间里放入u,v,y(全局),z(f1里的)。

7.这样整个名字空间就搞清楚了(并没有出现任何错误)。然后再开始执行。

8.最终结果为78。

关于Python的异常处理。所谓防御性程序设计,就是尽可能让每个代码单元保护自己,对可能出现的异常做检查和处理。一般用try配合except、else、finally完成。用raise抛出一个异常。with相当于try...finally...的等价(不过表达式必须支持__enter__和__exit__操作,在with、快中自动打开并在最后关闭)。

Python的list slices采取左闭右开的原则。

Python 手册提倡的编程风格要点:使用 4 个空格的缩进(退格)形式,不用 tab 字符(更小的退格不够清楚;如果用更大的退格,几层嵌套后有效位置就会变得太少,不方便)。

设法把每行安排在 79 个字符以内(程序行不要太长,以方便阅读)。

在不同的函数定义、类定义之间,在函数里大块的代码段上下加入空行。

尽可能不把注释与其他内容写在同一行(注释独立成行)。

给函数写文档字符串(专门用于写程序里说明的串,用一对包含三个双引号的序列括起的文本)。

在运算符两边和逗号后面加一个空格。

采用统一的名字写法给函数和类取名。习惯上是类名采用 ClassName 的形式(分段的名字,每段用大写字母作为第一个字符),函数名采用 function_name 的形式(分段的名字,用下划线连接各段)。

程序里的标识符不要用非 ASCII 码字符(虽然 Python 默认采用 UTF-8 作为基础字符集)。

关于类,也就是新定义的数据类型。类定义中,需要定义对象的构造操作(__init__)、解析操作(各种实例方法)、变动操作(各种实例方法,可变对象/不可变对象)。调用对象方法(即绑定到对象的函数)用instance.method(arguments)或Class.method(instance, arguments),这里的方法是实例方法(针对实例的)。在类定义中还会用到静态方法,用修饰符@staticmethod标记,它不含self参数,所以调用的时候只能用Class.method(arguments)。它只不过是一个定义在类里面的普通局部函数罢了。第三种method是类方法,@classmethod,顾名思义是针对class而不是instance的,定义的时候用def method(cls, arguments),调用的时候用Class.method(arguments),它直接读取类里面的信息,而不是某一个实例对象的信息。这三个method其实看名字就非常直观,很容易理解。

关于迭代器。所谓迭代器(iterator),简单来说就是能用next()迭代的对象。也可以用iter()去生成一个迭代器。从面向对象编程的角度去理解,迭代器就是这样一个class的实例,这个class里面定义了两个方法 __iter__() 与 __next__() ,并且可能通过raise一个StopIteration异常去结束迭代。而生成器函数就是用yield去产生一个迭代器的。需要区分的是可迭代对象(iterable),它并不是一个特殊的class类型,而是一类class,包括了list、set、dict和迭代器,只要能够放在for后面做循环的,就叫做可迭代对象。

另外需要说明的是,我们经常用的range()生成的不是一个迭代器,而是一个「range类型」的可迭代对象,和list之类是类似的。所以不要试图用next()去处理一个range对象。

迭代器,可迭代对象,生成器三个概念要区分清楚。

关于Python的函数式编程:comprehension,匿名函数,闭包,生成器。匿名函数比如lambda x, y: x**2 + y**2,这种只是为了方便。生成器包括tuple comprehension和generator function,它是一种特殊的迭代器(通过两种特有的机制自动实现了“迭代器协议”(即__iter__和next方法),不需要再手动实现两方法)。描述式(comprehension),顾名思义,是通过描述的方式构建一个组合对象。比如n**2 for n in range(10),完全是和自然语言一样的描述。但是这个表达式是不完整的,要加上修饰:如果用[]修饰,就变成list comprehension,结果是一个list;如果用()修饰,就变成tuple comprehension,结果是一个generator。类似的还有dictionary conprehension,比如{n : n**3 for n in range (10)}。生成器包括tuple comprehension和generator function。至于闭包(closure)这个东西,值得仔细说。这是一个比较特殊的东西,可以理解为生成器的扩展版本。它就是在函数里定义了一个局部函数并返回它,这样一来,我们调用大函数之后,局部名字空间并没有被抛弃(这是一个反例),因为每次之后要调用里面的小函数的时候,必须在这个局部名字空间里干活。之后就可以在这个名字空间里边多次调用小函数,结果就像生成器一样可以迭代。所以说,闭包其实就相当于把一个函数局限在一个局部名字空间里反复调用。顾名思义,就是一个enclosure住的函数。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

python中左闭右开是啥意思_Python的一些整理 的相关文章

随机推荐