按要求编写程序 1)编写Animal类,类中定义sleep0方法,方法中输出"睡觉"即可. 2)?

截至笔者撰写本回答时,Python的最新稳定版是3.10。一些比较基本的像是字符串切片之类的,一切Python入门书都会提的特性就不提了,这里只介绍一些容易被忽略的Python特性。

Pythonic用来形容具有特定风格的代码,即善用Python提供的各种特性使自己的代码更加简洁直观。Pythonic不是一套严格的规范,这只是大家在使用Python语言过程中形成的习惯。如果你认为你对Python提供的某些特性的使用使得你的代码更加简洁与直观,那么你就在践行Pythonic的路上了。

开头先放一下目前比较推荐的,适合了解Python进阶特性的书籍。

  • Effective Python(第2版):基于Python 3.8,以简洁的语言介绍了如何写出Pythonic的代码。版本比较新,因此像是asyncio这些较新的特性也会介绍。应该是目前能买到的中文版Python书籍中最推荐的进阶书。
  • 流畅的Python(第2版):目前第2版英文版刚出版,自然是没有中文版的,你可以在上读到这本书的英文版(可以考虑注册个新账号白嫖一周试用期读一读,Github上也有下载工具,这里就不多提了)。第二版基于Python 3.10,是撰写本回答时Python的最新版本,较为深入地介绍了一切常用的Python特性,事无巨细。顺带一提,目前国内能买到第一版的中文版,但由于第一版基于Python 3.4,很多后续更新的内容都没有,已经略有些过时了。

至于Python Cookbook,个人不太推荐。这本书的最新版(英文原版)也是2013年出版的,现在看起来实在有些太老了。

代码规范是个老生常谈的话题。虽说大家应该都清楚在运算符两边加空格这种基本和通用的规则,但Python有一些特别的规范,可能会出乎你的预料。

一些常见的、代码格式化工具就能纠正的代码规范问题这里不过多赘述,只说明那些代码格式化工具也不容易纠正的问题。

如果你平时用惯了Java、JS等语言,可能习惯于使用小驼峰命名。然而在Python中,大致上除了类名使用大驼峰,函数/方法命名和变量命名都建议使用下划线分隔。因此如果你编写的类库中大量出现了以小驼峰命名的方法,那么你的代码实际上并不"Pythonic"。

如果你写惯了Java,可能会认为将尽可能多的成员变量/方法用双下划线开头命名(以使其为私有的),如__a,是个好主意。然而在Python中,推荐你尽量以单下划线开头命名私有变量/方法,即使这会使得它们实际上不是私有的。用双下划线开头命名变量/方法仅当会出现命名冲突的时候才推荐使用。

Python建议多行语句优先使用括号而非续行符(反斜杠),如果你发现在你的Python代码中大量使用了续行符,而且是在本可以使用括号的地方使用了续行符,那么你的代码也不Pythonic。当然,在某些无法使用括号的特定情况下,续行符仍是有意义的。

下面是使用括号写多行语句的示例。对于下面所示的多行逻辑判断,PEP 8给出了三种可以接受的方案:

# 加入一条用于说明的注释 # 加入额外缩进,使代码更清晰

另外,关于运算符的多行使用,Python有一点和多数语言不同:

# 在多数语言中,多行语句将运算符放在行尾
# 然而在Python中,推荐将多行语句的运算符放在行首
 

上面这点可能会让你感到有些反直觉。如果你常常编写Go这种强制要求你把运算符放在行尾的代码,这个习惯可能很难纠正过来。

另外,在很多语言中,你可能已经习惯于在块内只有一条语句的情况不换行。然而Python建议你不要在一行中写多条语句

Python建议你在类定义、顶级函数定义之间插入两个空行,类内方法使用一个空行分隔,并推荐多使用空行在函数/方法内部分隔逻辑。

Python并不强求你使用单引号或双引号,但建议你选择一种并加以遵守。你可能会因各种代码格式化工具的使用而以为PEP要求优先使用单引号,但事实是PEP 8并未在这上面做出规范。但是对于docstring,PEP 8建议使用三个双引号,而非三个单引号。

然后是行内花括号的使用。如果你习惯了Java、JS等语言,你可能习惯在行内的花括号两边加上空格了:

然而在Python中,不推荐你这么做:

对于元组定义,推荐你不在单元素元组的逗号后加上空格

值得注意的是,Python虽然建议在运算符两边加上空格,但在切片场景下,考虑到冒号应该在代码中更醒目,建议你优先在冒号两边加上空格(在这种情况下,你可以选择不在常规运算符两边加上空格):

# 或者你也可以对冒号和运算符一视同仁,都加上空格

有些人可能习惯于使用空格来对齐赋值语句,然而Python不建议你这么做:

另外即使在常规情况下,也不建议总是在运算符两边加上空格,有时候适当减少空格可以使代码更清晰,例如乘法运算的优先级比加减法运算更高,就只在加减法运算符两边加上空格。但是对于"=""+=""==""<"">"这类运算符,建议永远在两边加上空格:

如果你常常写JS,可能习惯在命名参数等号的两边加上空格了,然而Python建议你在命名参数的两边不加空格

例外是,如果你使用type hints做类型提示,则建议你加上空格:

你可能非常关心如何将docstring写得符合规范。在Python中,docstring以三个双引号表示,放在函数/方法/类定义语句头的下一行

上面演示的是一个最普通的单行docstring。如果你认为docstring以后需要扩展,可以把单行docstring写成多行的,Python并没有规定当docstring可以写在一行里时,就一定要写在一行。例如写成这样,也没有任何问题:

对于多行docstring,需要在第一行写上简要描述,然后跟上一个空行,再跟上详细描述

Python并没有规定docstring的第一个三双引号是否要单独成行,但建议你将docstring的右三双引号永远单独成行

由于Python目前的type hints已经比较完善,因此建议当你需要为函数/方法注解参数/返回值类型时,不要使用docstring,而是直接使用type hints,这样更为清晰。

Python推荐优先使用is not而非not ... is,因为这更符合人类的阅读习惯:

这里仅摘取了一些代码规范上容易犯的错误。但PEP 8中对代码规范的描述远多于这里描述的这些,尤其是命名规范,因此建议花些时间将PEP 8从头到尾看一遍:

使用赋值表达式(:=)

Python在条件判断中使用赋值语句。如果你经常在写其他语言的代码时使用这种操作,可能会觉得非常难受。在Python 3.8中,官方意识到在有些情况下,于条件判断内使用赋值语句反而能使代码更清晰,因此加入了“海象运算符(:=)”,即赋值表达式。

在赋值表达式未出现前,你可能经常需要写下面这种代码:

加入赋值表达式后,你可以使上面的代码更清晰:

显然,由于Python的赋值语句不存在返回值,因此像是a = b = c这种代码确实是语法糖,而不像很多其它语言中是由于赋值语句存在返回值而存在。在Python中,a := b:= c才不算是语法糖。

赋值表达式不止可以用来解决上面提到的问题。在推导式中,赋值表达式也有很好的效果。

上面代码的作用是检查每种产品的库存是否达到最低发货限制(8个为一批,至少有一批才能发货)

你可能会感到可以使用字典推导式简化代码:

上面代码的问题是,get_batches(stock.get(name, 0), 8)被重复了两次,程序没必要把这条语句执行两遍,而且这么写也使得代码看起来比较混乱。在实际生产中,我们在上面的情况中会倾向于使用基本的for循环而非字典推导式。

然而如果使用赋值表达式简化字典推导式,字典推导式的写法看起来就变得清晰到能够接受了:

优先使用f-string进行字符串格式化

如果你写惯了C/C++,看到Python中也支持类似的字符串格式化语法,可能会毫不犹豫地这么写:

Python也提供了format函数和str.format方法,用来更友好地进行字符串格式化。你可能也会经常这么写:

format看起来稍微清楚一点。然而自从Python 3.8加入了f-string,上面的两个方法都不再推荐使用。现在推荐优先使用f-string进行字符串格式化:

# 你甚至可以在f-string中插入表达式:

可以看到,f-string在三种方法里最清晰也最短。但需要注意的是,f-string在某些特殊情况下存在限制,例如f-string中无法使用表达式生成多行(带'\n')的字符串,例如f'{"\n".join(...)}'就会出错,而format就不会。因此format仍有一定的存在价值,但类C语法的字符串格式化就不建议使用了。

另外在f-string中如果要表示实际的花括号,不要使用反斜杠转义,诸如f'\{\}'会出错,请使用两层花括号表示转义,如f'{{}}'表示'{}'。

仅限位置参数与仅限关键字参数

这是Python 3.8加入的一个新特性,因此可能不那么为人所知。

在Python中,你会经常需要写位置参数和可变参数,如:

但是当某个函数接收的参数过多时,你可能会希望用户使用关键字参数而非位置参数,以防止用户写出下面这样不直观的代码,也为了方便维护代码库。

例如在机器学习库scikit-learn中,你经常可以见到下面这样的类或函数:

你会希望用户这么调用你写的类:

然而有些用户可能会这样调用该类:

作为库的设计者,你肯定不希望用户用上面的方式调用该类。这一方面是由于这么写会使代码的可读性变得非常低,更重要的是如果你打算在后面的更新中调整一些参数的顺序,通过上面这种方式写的代码就会因为类库更新而变得无法使用,你显然不希望这种问题发生。

在Python 3.8中,加入了“仅限关键字参数”。你可以注意到,在Python 3.8及以上版本,上面演示的第二种调用方式会出错,这是因为scikit-learn库使用了这一新特性。现在回头重新观察LinearSVC类的定义:

同理,Python 3.8也同时加入了“仅限位置参数”,用"/"表示,在"/"前面的参数必须通过位置指定,而不能通过关键字指定。例如将上面的类定义稍作修改:

相比起仅限关键字参数,仅限位置参数的应用范围不广。你可以在很多类库中看到“仅限关键字参数”的身影。

说实话我不太想把这一点放在这里,因为学过Python的人应该都很擅长使用序列解包(unpacking)。但为了以防万一有人真不知道,还是在这里额外加一条。

例如最经典的交换变量值,在其它语言中经常这么写:

在Python中可以这么写:

这不是魔法,也不是什么语法糖,这是因为Python内置了元组。上面的代码等价于:

右边用逗号隔开的b, a实际上是创建了一个元组,相当于(b, a),然后左侧存在两个变量,因此将元组内的两个依次赋值给a和b。

如果你像下面这样写,逻辑就更清楚了:

很多人说Python函数支持多返回值,例如return a, b,严格来说这是不严谨的。return a, b本质上也是返回了一个元组(a, b),相当于return (a, b),实际上还是单返回值:

# 这么写看起来确实像是多返回值 # 如果像下面这么写,就能意识到其本质是元组与解包

正如上面代码演示的那样,使用解包比使用下标依次给变量赋值要清楚得多。应当尽量通过解包避免使用下标访问元组内的元素

有时,你可以使用带星号的解包使得代码更加简洁:

如果你不使用解包,可能会这么写:

显然使用解包的代码清晰简单许多。

序列解包和多返回值很好用,但有时你可能会过度依赖这一特性,写出丑陋且难以维护的代码:

上面代码中的最后一行就是一个错误示范。上面的get_stats函数有五个返回值,只要你一不留神将返回值的顺序搞错就会产生混乱,而且每次使用该函数都需要将五个返回值都写出来,费时费力,可维护性也很差。

上面的代码很容易出现在需求更改的时候。一开始函数可能只需要返回两三个值,这时使用多返回值很合适。然而随着需求的增加,你可能随手就将返回值越添越多,最后导致每次调用这个函数都变得非常痛苦,最后自己都不想用自己写的函数。

当出现这种情况时,正确的选择是定义一个单独的用于存放数据class,并返回该class的实例(后面会讲到使用dataclass能简化这个类定义),或者使用namedtuple(后面也会讲到)。

如果你是Python新手,可能很容易写出下面这种代码:

Python的逻辑是让使用者尽可能少关心下标,上面的写法是常规的C写法,但并非常规的Python写法。正确的方式是使用enumerate函数:

同样的,enumerate函数返回的对象也支持解包。上面这种写法显然更加赏心悦目。enumerate函数能将任何一种可迭代对象封装成惰性生成器,你可以不用去理解这里的术语,但你应当明白enumerate至少可以对元组、列表、集合等常见的序列使用,在很多情况下善用enumerate可以更好地简化代码。

很多时候需要处理键不存在于字典中的情况,你可能经常会编写下面这样的代码:

你可能受够了反复编写类似的重复代码,然后发觉dict有一个setdefault方法可以解决你的问题:

# 你也可以使用链式调用

setdefault方法的确很短,但可能并不能十分清晰地表示你代码的意图。特别是对于不了解这个方法的阅读者,可能需要花点时间才能理解你的代码做了什么。

使用defaultdict在很多情况下是更好的选择:

defaultdict函数接收一个工厂函数作为参数,它可以是set、list、int、str等,当然也可以是一个你自己定义的工厂函数:

可以看到,defaultdict函数从函数名上就十分直观,即使阅读者不了解其用法,也很容易推断出其在代码中起到的作用。优先考虑使用defaultdict是比使用dict.get和setdefault更好的方案。

你可能会困惑于为什么defaultdict接收工厂函数而非一个具体的值。事实上,Python许多内置的API都接收函数而非类的实例,例如sort方法的key参数。这种设计是有原因的,例如在上面最后演示的那个稍显复杂的代码逻辑中,如果defaultdict接收的不是函数,就需要对defaultdict进行二次包装,创建一个新的类用来处理上面的逻辑。Python中函数是一等对象,可以直接作为参数传入,因此你不必拘泥于Java中的各种设计模式,而是大胆地模仿这些接收工厂函数的内置API,将自己设计的函数也改为接收工厂函数,这有时能有更好的可扩展性。

然而defaultdict不适合应对更复杂的场景。例如有时你需要对传入的键值做判断,为不同的缺失键值设定不同的默认值。由于defaultdict只能传入不接收参数的工厂函数,因此它无法胜任这项任务。这时继承dict创建一个新的类,并重写__missing__方法是一个可以考虑的选择。考虑到这不是一个特别常用的操作,这里就不演示了,只稍作提及。

这是一个很深的话题,这里只简单介绍。“闭包(Closure)”在函数式编程是一个非常重要的概念,可以简单理解为“定义在大函数中的小函数能够引用大函数中的变量”,例如:

上面的代码中,helper函数引用了外层的group变量,这就是闭包的一个应用。你可能已经自然而然地在代码中使用了很多闭包,但没有意识到自己在使用闭包。

如果你写惯了JS,可能对这个概念并不陌生,同时你可能会对“词法作用域”这一概念感到熟悉(这里不介绍)。然而Python并非使用严格的词法作用域,因此你可能会遇到下面这些出乎预料的问题:

如果你调用上面的foo()函数,你会得到一个“counter赋值前引用”的错误。你可能会感到很奇怪,毕竟在其他很多语言中,上面的操作没有任何问题。

这是由于Python的闭包只允许“引用”外层变量,不允许直接改变外层变量的值。然而你可以通过一些方式绕过这一限制,例如使用可变数据类型:

现在上面的foo()函数就可以正常运行了:

值得注意的是,在foo()函数中你不需要写global counter,作用域机制使得你能够自然而然地引用外层变量。

装饰器是一个有些争议的特性,有人认为装饰器带来的坏处多于好处。然而适当使用装饰器确实能够是你的代码更清晰。Python对装饰器的支持非常完善,甚至可以说是主流语言中最完善的。

你可能经常看到介绍装饰器的文章突然拍出一段下面这样的代码:

或是突然给出下面这样的类装饰器:

这可能会让你产生一种错觉,好像装饰器只能按照上面提供的方式依葫芦画瓢去定义。然而装饰器的本质逻辑其实非常简单:

# 第一段代码等价于:
 

是的,装饰器其实就是额外进行了一个步骤:将函数本身作为参数传入装饰器函数,调用该函数,然后将函数重新赋值为装饰器函数处理后的函数。

这样一来,“函数装饰器”“类装饰器”这样的概念就非常容易理解了,它们本质上没有任何区别。

Python也提供一些内置装饰器,例如lru_cache可以实现函数缓存:

实际上上面的lru_cache装饰器原理也非常简单,你可以自己写一个简化的缓存装饰器:

不过,多数人对装饰器的使用就到此为止了。事实上通过上面方法定义的装饰器存在一些问题,例如上面定义的trace装饰器:

可以看到新的fibonacci函数不叫fibonacci,同样这也会使得fibonacci函数丢失其他元数据,例如help(fibonacci)也无法正常显示docstring。如果你的代码依赖元编程或使用了某些依赖反射机制的代码检查工具,例如调试器,这样做就会出问题。

好在functools模块中提供了wraps辅助函数,用于解决这一问题,它会将重要的元数据全部从内部函数复制到外部函数

现在打印fibonacci的名称与使用help函数就都正常了。

建议对于一切装饰器定义,都使用wraps函数,以免因某些代码依赖元数据而导致问题。

关于map/filter/reduce函数的使用和列表推导式/字典推导时这里就不介绍了,因为这些东西已经广为人知了,而且任何Python入门教程也应当会涉及这些东西。如果你还不清楚,建议你暂停一下,查点资料补补课。

尽管可能是一句废话,但这里要提醒一下,Python中的lambda函数、map/filter/reduce函数和列表推导式/字典推导式/生成器推导式这些用于简化代码的特性都应当仅在确保能使你的代码更清晰的情况下使用。如果你发现代码中过度使用了这些特性使得代码变得难读,那并不意味着你充分利用了Python提供的特性使得你的代码更加Pythonic,这反而是违背了Pythonic的准则。

生成器是一个很容易被人忽略的Python特性。在主流语言中,你几乎只能在JavaScript中看到生成器的影子,而且在JS中生成器的存在感比Python还低得多(事实上JS的生成器也是从Python里抄来的)。你也许在教程中了解到Python存在生成器,也了解了它的使用,但可能仍对自己在什么情况下需要用到生成器感到困惑。

你也许经常编写返回一个列表或长元组的函数:

上面定义的函数作用是返回每个单词首字母对应的下标:

你可能注意到result变量的存在使代码逻辑不太容易一眼看清楚,这时候使用生成器就是更好的选择:

现在代码逻辑就变得非常清楚了。使用生成器的另一个好处是避免了result变量造成的内存占用。可以想到,如果传入了一段非常长的字符串,result变量也会占用大量空间,而使用生成器就不会。

上面的生成器可以这样使用:

如果你确实需要一个列表,也可以使用list函数做到:

你可能会想更好地利用生成器惰性求值的特性,例如你只想得到上面定义的生成器的前n项内容,而不是通过list()函数全部求值然后切片。这时你可以考虑使用itertools模块中的islice函数:

# 这里定义了一个稍微复杂一些的生成器 # 用于依次输出文件中每个单词首字母的索引 # islice与切片类似,都是左闭右开

itertools模块中提供了许多非常有用的函数,它们不仅适用于生成器,也适用于一切迭代器。比较常用的有islice、takewhile、dropwhile、product等函数。在下面也会对其中比较常用的函数进行介绍。

值得一提的是,生成器函数返回的迭代器无法重复使用如果你不注意这点,可能会使用生成器编写更多BUG而非更优雅的代码

为了阐明问题的严重性,还是拿上面定义的生成器举例:

你可能会很震惊,在已经迭代完的迭代器上继续迭代竟然不会报错。这不仅适用于生成器返回的迭代器,也适用于所有迭代器,你需要尤其小心这一点。一种方式是永远不直接使用迭代器,而是通过直接在返回迭代器函数的返回值上迭代,例如我们常用的range()、enumerate()等函数通常就是这么使用的。

对于一些更简单的逻辑,例如统计文件中每行文本的长度,就可以使用不用定义生成器,直接使用生成器推导式:

如果需要统计总长度,就直接调用sum()函数:

你可能会多写一层括号,然而事实上你可以省略它:

你可能经常写类似下面这样的代码:

然而实际上这没有必要,你可以直接将在列表推导式上调用函数改写成在生成器推导式上调用函数:

这不仅简化了代码,而且使得Python不会浪费内存额外创建一个列表。几乎在任何类似的情况下你都可以通过使用生成器推导式改写列表推导式

生成器推导式也可以像列表推导式一样嵌套。这乍一听很不起眼,然而考虑一下,就会发现生成器的惰性求值特性使得事情变得非常有趣:

上面的roots迭代器在推进时会引发连锁反应:它也推进内部迭代器it以判断当前是否能在it上继续迭代,如果可以,就把it返回的值带入(x, x**0.5)中求出结果。这种写法可以显著减少内存占用。

你可能已经写了很多生成器函数:

然后你编写了一个新函数调用这些生成器:

然后你感到一直重复使用for循环来调用生成器并不聪明,你重复了很多次几乎一模一样的代码,而且很难简化它们。

在这种情况下,你可以使用yield from语句:

关于生成器的其他高阶特性

如果你尝试深入了解生成器,会发现这是一个很深的话题。例如,生成器还支持send方法,可以手动为生成器注入数据。然而本回答只是浅尝辄止地介绍一部分最用的Python特性,不打算成为一份详细指南,因此不会过多介绍这些内容。

另外,生成器许多高阶特性的应用都会导致代码变得难以看懂,建议谨慎使用。一般来说,使用这里提到的几个生成器特性用于简化代码就已足够。

itertools模块中内置了很多实用的函数,在迭代器上使用可以有效减少代码量。这里介绍几个常用的函数。上面在生成器部分中已经提过的islice函数就不再介绍了。

这两个函数的作用都非常直观,takewhile一直从迭代器中获取元素,直到测试函数为False;dropwhile一直从迭代器中跳过元素,直到测试函数为True,再从这个地方开始取值。

如果你对函数式编程有所了解,可能会对这两个十分常用的函数感到亲切。

2)),相信你就算不理解其中的数学概念也能很快看懂。

有时你可以通过使用product减少循环嵌套:

然而这不是个常见的用法。而且在多数情况下,滥用product减少循环嵌套会使得代码更加难读,建议仅当你确认product能够使代码更清晰时将product用于减少循环嵌套。

之前提到过如果某个函数有过多的返回值,可以考虑使用dataclass或namedtuple来返回数据。这里演示的就是namedtuple的使用。

namedtuple顾名思义,就是创建一个每个元素不仅有索引编号,还有名字的元组。例如:

需要注意的是,namedtuple仍是一个元组,因此用户可以通过下标来访问元素,尽管你可能并不希望用户这么做。如果你不希望能够通过下标访问元素,建议你使用dataclass而非namedtuple。

在Python 3.7中,加入了dataclass用于处理那些只需要定义一个用于存放数据的简单类的情况。在此之前,你可能经常需要写这种令人厌恶的代码:

可以看到在__init__方法中,name、unit_price、quantity_on_hand重复了两遍。问题可能还不止如此,例如当你需要打印这个类的实例时,它很不直观:

于是你打算加上个__repr__方法:

然后为了支持相等比较,还得加上个__eq__方法:

然后为了支持大于小于比较,你还得加上__lt__、__gt__方法……这下没完没了了

可以看到使用了@dataclass装饰器后,一切都被大大简化了。该装饰器会创建必要的__repr__、__eq__等方法。你还可以自定义dataclass:

# 这里演示的是dataclass函数的默认参数
 

这里的几个参数都很好懂。init、repr、eq分别表示是否自动创建__init__;__repr__和__eq__方法,order表示是否自动创建__lt__、__le__、__gt__、__ge__方法;frozen表示该数据类是否是可变的,如果设置为True,且eq也设置为True,则还会自动创建__hash__方法,此时该数据类就会变得可哈希,也就意味着它可以作为字典的键使用;如果unsafe_hash设置为True,即使当frozen不为True也会强制创建__hash__方法,当然正如其参数名表示的那样,这样做是不安全的,仅在特殊情况下才建议将其设置为True。

你也可以使用field函数更改某个特定字段的设置:

# 例如你希望某个字段不允许比较

关于field函数各参数的作用这里就不详细介绍了,你可以在PEP 557中看到更详细的说明:

dataclass其实并不是个新鲜的概念。像是scala中早有case class用于实现相似的功能,而最新的C#和Java 17中也提供了record关键字用于实现类似的“数据类”。这是一个正在逐渐被各语言广泛采用的特性。

多重继承与混入(mix-in)

Python支持多重继承,因此你可以使用混入(mix-in)模式来设计你的代码。如果你来自Java这种仅支持单继承的语言,可能会感到这很新颖。

例如你希望使某个定义的类支持to_dict方法。你当然可以直接在这个类中编写to_dict方法。但如果你有很多个这样的类,你可能会觉得使用继承更好一些:

然后定义一个二叉树,它通过继承ToDictMixin来实现to_dict方法:

min-in类就是指上面这样不带实例属性和__init__方法的类,这主要是为了避免多重继承引发的一些问题(如钻石继承)。使用mix-in类有时可以让代码设计变得简洁直观,而这在许多仅支持单继承的语言中是无法做到的。

如果你有过使用其他面向对象语言的经验,应该对抽象类与抽象方法并不陌生。简单来说,抽象类就是无法实例化的类,仅用于继承。抽象方法则是只属于抽象类的方法,它不在抽象类中实现(抽象类中仅声明该方法),必须由子类实现。

就像上面的定义的ToDictMixin和JsonMixin,它们仅用作混入,直接将它们实例化显然是没有意义的,因此可以令它们继承abc.ABC(Abstract Base Class):

不过需要注意的是,Python在定义class时不会执行abastractmethod检查,因此即使你写出了如class BinaryTree(JsonMixin)这样的代码也不会报错,只有当你实际创建了BinaryTree的实例时才会报错。但是一般来说,静态类型检查器会提示这类错误,例如PyCharm、VSCode等都内置类似的检查。

如果你使用过Java,一定会对Java中诸如Iterable、CharSequence这样的诸多接口记忆犹新。Python中没有接口,只有抽象类,但也有类似的抽象基类表示哪些类型的容器应当实现哪些方法。例如当你需要创建某个序列类型,那么就应当从collections.abc中继承对应的抽象基类Sequence:

如果你正确实现了相应的抽象方法,就不会报错:

collections.abc中内置了一些常见的抽象基类,但你可能还打算定义自己的抽象基类,这当然也很简单,只需要创建相应的抽象类与抽象方法即可。

如果你曾经用过Java,肯定对以get和set开头的访问器/修改器并不陌生:

考虑到PEP 8推荐所有方法使用下划线命名而非小驼峰命名,因此将上面的Java命名风格代码改写一下会更好:

然而这并不是Python推荐的方案。如果像上面这么写,你就需要这样使用Register类:

# 如果需要更改属性值的话

这是Java中常用的方式,然而在Python中,你不必写得这么死板,直接将属性定义成public的就可以了:

你可能会疑虑如果要在访问/修改某些属性时实现一些额外的操作(副作用)该怎么办,而这正是Java中总是定义访问器/修改器的原因。然而在Python中,你可以使用相应的装饰器来实现副作用,因此你完全不必担心类似的问题:

上面定义了voltage的getter(访问器)和setter(修改器)方法,并在setter中实现了副作用(修改电压voltage时也修改电流current)。你可以像访问和修改一个普通的实例属性一样访问和修改voltage属性:

可以看到,副作用被成功执行了。

你当然也可以在setter上进行一些检查,例如这里设置当电阻ohms小于等于0时报错:

getter和setter还有很多作用。例如仅设置getter就可以得到一个只读属性,甚至可以通过getter定义一个计算得到的属性(考虑一个Line2D类,保存一条平面直线两点的四个坐标,它有一个length属性,通过getter定义,可以计算得到两点之间的距离)。你也可以通过修改setter阻止用户修改超类中的属性。

有时为了简单起见,你会直接将某个属性设置为public的。后来你意识到该属性需要进行一些额外的计算才能得到,这时你可能会考虑重构,编写一个新的方法,然后将原来引用该属性的地方都改成调用这个新方法。然而你也可以换一种思路,使用@property装饰器编写一个同名的getter方法,这样你就不需要修改原来引用该属性的地方了。@property装饰器可以逐步修改现有的代码,而不过度影响代码结构,这是它的一大优点

当然,getter/setter也不是万能的。如果你意识到自己编写了太多getter/setter,而它们处理相似的逻辑,例如一个Exam类,包含了chinese_grade、math_grade、english_grade等属性,而它们都需要检查取值范围是否在0~100之间,这时全部使用setter就会编写大量重复代码。在这种情况下,建议考虑重写__get__和__set__魔术方法,而非使用getter/setter。由于介绍这部分内容需要深入Python的属性访问机制,因此这里只做提及,不详细介绍。如果你打算深入了解这方面的内容,可以去了解一下Python的描述器(descriptor)

如果你用过JS,可以发现Python中的getter与setter和JS中的get、set关键字用法一致,应当会感到十分亲切。

Python 3.5有几个重量级更新,其中一个就是asyncio库的加入。这极大地简化了编写并发代码需要的思维量。通过使用asyncio库,你不再需要考虑复杂且难以维护的多线程操作(更何况Python由于GIL的存在也没有实际上的多线程加速),而是可以通过编写看起来很“同步”的代码来执行异步操作。

考虑到篇幅原因,这里只对asyncio库进行简单介绍。

asyncio引入了协程(coroutine)的概念,它需要配合async和await关键字使用。要在很短的篇幅内说清楚asyncio很不容易,这里只简单给出几个例子,附以简单的说明,让读者自行感受。关于asyncio的详细资料很容易找到,笔者就不班门弄斧了。

典型的使用asyncio定义的异步函数如下所示:

await只能在通过使用async定义的函数内部使用。await正如字面意思一样,会“等待”右边的函数执行完毕(await右边的函数也必须是一个async函数),然后执行下面的代码。

你可以通过asyncio.run运行上面定义的函数:

你可能会希望直接通过调用main()就能运行这个异步函数。在JS中确实可以这样做,然而在Python中你必须使用asyncio.run或是其他相关的函数。这主要是由于JS因为是为了web前端设计,因而天生是异步的,回调函数本就十分常用,后来Promise和async/await关键字的引入只是对其异步本质的扩展。而Python天生是同步的,因此asyncio需要使用一些别扭的方式来实现异步,这是不可避免的。asyncio已经尽可能设计得简单易用,但还是无法设计得像是JS一样简单。

await语句也有返回值,例如:

不过,你可能会感到困惑,这不还是同步的吗,asyncio能实现异步的机制在哪里呢?别着急,让我们再来看一个“同步”的asyncio示例:

可以看到,上面的代码中hello在等待一秒后输出,而world在接着等待了两秒后输出。输出结果显示一共经过了三秒,这符合我们的预期。

然而这与简单地编写同步代码time.sleep有什么区别呢?为什么要多此一举加上async/await关键字,还要使用asyncio.run调用?

现在我们稍微改写上面的main()函数,使其变成异步的:

现在再运行asyncio.run(main()),你就能看到不一样的输出结果:

可以看到,现在时间仅过去了两秒。上面代码的逻辑不用解释也应该很容易看懂,asyncio.gather在这里就是将两个say_after异步函数同步运行,当然你也可以运行不止两个这样的异步函数。

这其中使用了什么样的机制呢?这要涉及coroutine(协程)的概念。使用async定义的函数,其调用时不会直接返回结果,而是返回“协程(coroutine)”,你可以尝试打印上面的main和main():

协程顾名思义,就是一个“协调的例程”。对于一般的函数,一旦开始就无法中断,自然也无法从中间某一段跳出去再进入。然而协程是不同的,协程可以在执行过程中于许多个不同的点上进入、退出和恢复。而这些“点”就通过await关键字定义。显然,协程的这一特性有助于我们将多个协程糅合到一起,以实现异步操作。

如果你感到这一概念很难理解,你可以换一种(很不准确)的方式尝试理解协程。在这种理解方式下,你可以将协程理解为一个“将来求值”的概念。例如直接运行say_after(1, 'hello'),你立即得到一个coroutine对象,表示将来某一刻将会等待一秒,并且打印'hello'字符串,而不会实际执行函数体中的等待等异步操作。这个“将来某一刻”具体是哪一刻,取决于你什么时候执行这个协程。(这实际上更近似于future而非coroutine的概念,不过暂时为了方便你可以这么理解)

asyncio.run就是一个最基本的,用于执行协程的函数。在async函数体内部,你还可以使用await关键字右跟一个协程(即async函数的返回值)表示你将在这里执行这个协程。而asyncio.gather()可以将多个协程组合成一个协程(实际上这个返回值不是协程,而是future,这里暂时简单理解为coroutine),让它们在将来某一刻同时运行。

此外,还有一个常用的asyncio.wait_for函数,用于进行超时等待。在web后端和物联网开发中,你可能会经常需要使用它:

此外,asyncio中还有wait、shild等有用的函数,也支持和多线程一起使用。你可以在Python官方文档中学习相关的知识。本回答已经写得够长了,不打算写得更长了。

其实,await并不只能用来等待“协程”,还存在其他的“可等待(awaitable)”对象,它们分别是协程(coroutine)、任务(task)和future(这个我暂时想不到适合的中文翻译,就不翻译了)。

task对象与coroutine对象不同的是,对task对象使用await关键字时,不会等待上一个task执行结束再执行下一条await语句。例如使用task改写上面的main()函数:

# 等待两个task都执行完毕

可以看到时间也过去了两秒,这说明上面的写法也是异步的。

asyncio.create_task的作用是将一个协程包装成一个task对象,将其绑定到事件循环(Event Loop)上,然后智能调度目前已经绑定的几个task。这就是为什么在上面的示例中await两个task并不会等待上一个task结束再运行下一个task。

然后是future对象。future也是对协程的封装,但一般来说你不需要显式创建一个future对象,而是调用函数返回的future对象,例如asyncio.gather。

上面提到了一种不太准确的理解coroutine的方式,即将其理解为“未来求值”对象。这个描述用来形容future会更准确,future表示表示一个异步操作的最终结果,当一个future对象被等待,意味着协程将保持等待直到该future对象在其他地方操作完毕。操作结束后,这个结果就会被绑定到future对象上。

可以看到,future对象记录了协程的最终结果。这里的两个say_after协程都返回None,因此这里的result就是[None, None]。然而多数情况下返回值不是None,在函数运行结束前Python并不知道返回值是什么。下面稍微改写一下say_after函数:

可以看到对于尚未得到的结果,显示"pending"。

future还有一些其他方法,例如done()方法可以判断它是否完成:

result()方法可以查看结果:

这段代码会报错,因为在future尚未完成时调用了result()方法,改写成下面这样就没有问题了:

future对象还支持许多其他操作,例如取消(cancel)。这里不详细介绍future的使用,而且多数情况下不会直接操作future对象,而是使用await关键字自动处理。

asyncio中提供的异步能力只是为了“非阻塞”,不要指望通过asyncio实现并行计算。例如你编写了一个桌面应用,点击某个按钮后会执行耗时的爬虫任务,这时为了防止界面卡死,你就可以使用asyncio库。

然而,GIL存在使得Python无法使用线程实现真正的并行计算。如果确实有计算密集型任务,asyncio也无能为力。你可以考虑使用C语言编写扩展模块,或是干脆使用multiprocessing模块启动多进程(这可以通过concurrent.futures模块使用)。

type hints顾名思义,就是“类型提示”。它在不破坏Python动态类型系统的前提下为Python带来了静态类型检查。自从type hints出现之后,你再也不需要在函数的docstring描述参数类型了,而是可以直接使用type hints标注函数的参数类型和返回值。

# 因为编辑器可以通过类型推断自动推导出dct的类型为dict

上面就是一个很简单的type hints例子,相信人人都能看得懂,这里就不过多描述了。

你也可以使用泛型来表示容器中包含哪些类型的元素:

你甚至可以编写自己的泛型:

type hints是个很大的话题,这里只浅尝辄止,就不过多叙述了。如果你感兴趣,可以看一下我写过的一篇介绍type hints的文章:

上面只说明了一些不特别广泛地为人所知的Pythonic写法,此外也有一些广泛为人所知的Pythonic写法我并未在上面列出。没列出这些不是因为它们不重要,而是因为几乎人人都在用,而且基本上每一本Python入门教材和每一套Python入门视频都会讲解这些内容,因此就不写出来浪费大家时间了。其中也有少部分是由于本身一两句话就能说清楚,因此就不单独列出了,只集中列在下面。还有一些是可能只有少部分人需要用到的特性,因此也只在这里列出,不详细展开。

下面列出这些Pythonic写法,你可以看一下这个列表,了解一下哪些Pythonic特性你还不甚了解:

  • 使用in判断元素是否存在于序列中
  • 不要使用可变值作为默认参数(这会产生安全性问题)
  • 尽量不使用for-else语句,因为这一语句违反直觉
  • sort方法与使用key参数表示复杂排序逻辑
  • 使用dict.get处理键不在字典中的情况,而非使用in和KeyError
  • 尽量抛出异常,而非返回None或忽略异常。既然Python存在异常,就尽量利用它,而非返回None表示操作失败,或像Go一样使用一个额外的返回值表示操作是否成功。
  • 位置参数(*)与关键字参数(**)
  • 元类(metaclass)的使用。与列表中的其他条目不同,这项是由于其属于比较高阶的特性且难以在较短的篇幅内说清,因而并未包含在上面的内容中。metaclass并不常用,然而在某些特定情况下也很有价值。
  • 列表推导式与字典推导式的使用
  • 使用datetime处理本地时间,而非使用time
  • 在需要准确计算的场合使用decimal,因为IEEE 754的浮点数存在舍入误差
  • 使用内置的unittest模块进行单元测试
  • 使用deque来实现先进先出队列。deque类的append和popleft方法都是线性时间复杂度的,而list.pop(0)是平方复杂度的,在有些情况下使用deque能显著提高性能
  • 使用bisect二分搜索已排序的序列,以提高性能
  • 使用heapq制作优先级队列
  • 使用pip安装模块、创建虚拟环境、创建并导入他人创建的requirements.txt文件

事实上写到这里,目前所写的内容已经超出了我最初的预期。我最初只是想简单地将我脑中的一些常见Python用法如setter/getter、dataclass、type hints、赋值表达式、enumerate等写出来,当我翻出书架中几本相关的书并耐心整理时,才发现原来不知不觉中使用了这么多"Pythonic"的用法,而我甚至浑然未知。于是这篇回答就越总结越多,后来几乎变成了一个小百科。我的本意是希望任何人都能在这篇回答里找到有价值的东西,但不知不觉写了很长,但愿本回答的长度不会让你感到头晕。

正如高赞回答表示的,其实写出Pythonic代码最重要的不是学会这些应用性的东西,而是熟悉Python标准库以及特定场景下的常规做法。本回答只是个简易“速成”指南,具体如何应用这些特性,还是要在具体使用中慢慢熟悉起来。

甚至有时候,这些特性的使用也是其次的,更重要的是写出逻辑清晰的代码。我本有打算在回答中加入一些描述如何安排类之间关系的部分,然而后来想想这似乎不是Python独有的,而是编写任何语言代码都应该做到的。无论是阅读《设计模式》、《重构》还是《Clean Code》,这类方法论的书对于如何编写简洁优雅的代码都是有帮助的。而简洁与优雅,正是Pythonic的核心。

stat无权限控制,lsof有权限控制,只能看到本用户
2.losf能看到pid和用户,可以找到哪个进程占用了这个端口

i++; //找出左边比中间值大的数

j--; //找出右边比中间值小的数

if(i<=j){ //将左边大的数和右边小的数进行替换

另外两种方式都由依赖,第一个直接依赖于目标类,第二个把依赖转移到工厂上,第三个彻底与目标和工厂解耦了。在spring的配置文件中配置片段如下:

C/S 程序可以不可避免的整体性考虑,构件的重用性不如在B/S要求下的构件的重用性好.

B/S 对的多重结构,要求构件相对独立的功能.能够相对较好的重用.就入买来的餐桌可以再利用,而不是做在墙上的石头桌子

C/S 程序由于整体性,必须整体考察,处理出现的问题以及系统升级.升级难.可能是再做一个全新的系统

B/S 构件组成,方面构件个别的更换,实现系统的无缝升级.系统维护开销减到最小.用户从网上自己下载安装就可以实现升级.

C/S 程序可以处理用户面固定,并且在相同区域,安全要求高需求,与操作系统相关.应该都是相同的系统

B/S 建立在广域网上,面向不同的用户群,分散地域,这是C/S无法作到的.与操作系统平台关系最小.

C/S 多是建立的Window平台上,表现方法有限,对程序员普遍要求较高

B/S 建立在浏览器上,有更加丰富和生动的表现方式与用户交流.并且大部分难度减低,减低开发成本.

C/S 程序一般是典型的中央集权的机械式处理,交互性相对低

B/S 信息流向可变化, B-B B-C B-G等信息、流向的变化,更像交易中心。

3、应用服务器有那些?

一个另类的回答:j2ee就是增删改查。

5、J2EE是技术还是平台还是框架?什么是J2EE

J2EE本身是一个标准,一个为企业分布式应用的开发提供的标准平台。

6、请对以下在J2EE中常用的名词进行解释(或简单描述)

web容器:给处于其中的应用程序组件(JSP,SERVLET)提供一个环境,使JSP,SERVLET直接更容器中的环境变量接口交互,不必关注其它系统问题。主要有WEB服务器来实现。例如:TOMCAT,WEBLOGIC,WEBSPHERE等。该容器提供的接口严格遵守J2EE规范中的WEB APPLICATION 标准。我们把遵守以上标准的WEB服务器就叫做J2EE中的WEB容器。

EJB容器:Enterprise java bean容器。更具有行业领域特色。他提供给运行在其中的组件EJB各种管理功能。只要满足J2EE规范的EJB放入该容器,马上就会被容器进行高效率的管理。并且可以通过现成的接口来获得系统级别的服务。例如邮件服务、事务管理。

JNDI:(Java Naming & Directory Interface)JAVA命名目录服务。主要提供的功能是:提供一个目录系统,让其它各地的应用程序在其上面留下自己的索引,从而满足快速查找和定位分布式应用程序的功能。

JMS:(Java Message Service)JAVA消息服务。主要实现各个应用程序之间的通讯。包括点对点和广播。

JTA:(Java Transaction API)JAVA事务服务。提供各种分布式事务服务。应用程序只需调用其提供的接口即可。

JAF:(Java Action FrameWork)JAVA安全认证框架。提供一些安全控制方面的框架。让开发者通过各种部署和自定义实现自己的个性安全控制策略。

RMI/IIOP:(Remote Method Invocation /internet对象请求中介协议)他们主要用于通过远程调用服务。例如,远程有一台计算机上运行一个程序,它提供股票分析服务,我们可以在本地计算机上实现对其直接调用。当然这是要通过一定的规范才能在异构的系统之间进行通信。RMI是JAVA特有的。

7、如何给weblogic指定大小的内存?

(这个问题不作具体回答,列出来只是告诉读者可能会遇到什么问题,你不需要面面俱到,什么都精通。)

8、如何设定的weblogic的热启动模式(开发模式)与产品发布模式?

可以在管理控制台中修改对应服务器的启动模式为开发或产品模式之一。或者修改服务的启动文件或者commenv文件,增加setPRODUCTION_MODE=true。

9、如何启动时不需输入用户名与密码?

修改服务启动文件,增加 WLS_USER和WLS_PW项。也可以在boot.properties文件中增加加密过的用户名和密码.

10、在weblogic管理制台中对一个应用域(或者说是一个网站,Domain)进行jms及ejb或连接池等相关信息进行配置后,实际保存在什么文件中?

保存在此Domain的config.xml文件中,它是服务器的核心配置文件。

11、说说weblogic中一个Domain的缺省目录结构?比如要将一个简单的helloWorld.jsp放入何目录下,然的在浏览器上就可打入http://主机:端口号//helloword.jsp就可以看到运行结果了?又比如这其中用到了一个自己写的javaBean该如何办?

Domain目录服务器目录applications,将应用目录放在此目录下将可以作为应用访问,如果是Web应用,应用目录需要满足Web应用目录要求,jsp文件可以直接放在应用目录中,Javabean需要放在应用目录的WEB-INF目录的classes目录中,设置服务器的缺省应用将可以实现在浏览器上无需输入应用名。

12、在weblogic中发布ejb需涉及到哪些配置文件

13、如何在weblogic中进行ssl配置与客户端的认证配置或说说j2ee(标准)进行ssl的配置?

缺省安装中使用DemoIdentity.jks和DemoTrust.jksKeyStore实现SSL,需要配置服务器使用Enable SSL,配置其端口,在产品模式下需要从CA获取私有密钥和数字证书,创建identity和trust keystore,装载获得的密钥和数字证书。可以配置此SSL连接是单向还是双向的。

可以使用管理控制台,在它的Deployment中可以查看所有已发布的EJB

SessionBean在J2EE应用程序中被用来完成一些服务器端的业务操作,例如访问数据库、调用其他EJB组件。EntityBean被用来代表应用系统中用到的数据。

对于客户机,SessionBean是一种非持久性对象,它实现某些在服务器上运行的业务逻辑。

对于客户机,EntityBean是一种持久性对象,它代表一个存储在持久性存储器中的实体的对象视图,或是一个由现有企业应用程序实现的实体。

Java Bean 是可复用的组件,对Java Bean并没有严格的规范,理论上讲,任何一个Java类都可以是一个Bean。但通常情况下,由于Java Bean是被容器所创建(如Tomcat)的,所以Java Bean应具有一个无参的构造器,另外,通常Java Bean还要实现Serializable接口用于实现Bean的持久性。Java Bean实际上相当于微软COM模型中的本地进程内COM组件,它是不能被跨进程访问的。Enterprise Java Bean相当于DCOM,即分布式组件。它是基于Java的远程方法调用(RMI)技术的,所以EJB可以被远程访问(跨进程、跨计算机)。但EJB必须被布署在诸如Webspere、WebLogic这样的容器中,EJB客户从不直接访问真正的EJB组件,而是通过其容器访问。EJB容器是EJB组件的代理,EJB组件由容器所创建和管理。客户通过容器来访问真正的EJB组件。

SessionBean:Stateless Session Bean的生命周期是由容器决定的,当客户机发出请求要建立一个Bean的实例时,EJB容器不一定要创建一个新的Bean的实例供客户机调用,而是随便找一个现有的实例提供给客户机。当客户机第一次调用一个Stateful SessionBean 时,容器必须立即在服务器中创建一个新的Bean实例,并关联到客户机上,以后此客户机调用Stateful SessionBean的方法时容器会把调用分派到与此客户机相关联的Bean实例。

EntityBean:Entity Beans能存活相对较长的时间,并且状态是持续的。只要数据库中的数据存在,Entity beans就一直存活。而不是按照应用程序或者服务进程来说的。即使EJB容器崩溃了,Entity beans也是存活的。Entity Beans生命周期能够被容器或者 Beans自己管理。

5、EJB容器提供的服务

主要提供声明周期管理、代码产生、持续性管理、安全、事务管理、锁和并发行管理等服务。

以Stateful Session Bean为例:其Cache大小决定了内存中可以同时存在的Bean实例的数量,根据MRU或NRU算法,实例在激活和去激活状态之间迁移,激活机制是当客户端调用某个EJB实例业务方法时,如果对应EJB Object发现自己没有绑定对应的Bean实例则从其去激活Bean存储中(通过序列化机制存储实例)回复(激活)此实例。状态变迁前会调用对应的ejbActive和ejbPassivate方法。

实体Bean可分为Bean管理的持续性(BMP)和容器管理的持续性(CMP)两种

8、客服端调用EJB对象的几个基本步骤

设置JNDI服务工厂以及JNDI服务地址系统属性,查找Home接口,从Home接口调用Create方法创建Remote接口,通过Remote接口调用其业务方法。

Web ServiceWeb Service是基于网络的、分布式的模块化组件,它执行特定的任务,遵守具体的技术规范,这些规范使得Web Service能与其他兼容的组件进行互操作。

JAXP(Java API for XML Parsing) 定义了在Java中使用DOM, SAX, XSLT的通用的接口。这样在你的程序中你只要使用这些通用的接口,当你需要改变具体的实现时候也不需要修改代码。

WSDL是一种 XML格式,用于将网络服务描述为一组端点,这些端点对包含面向文档信息或面向过程信息的消息进行操作。这种格式首先对操作和消息进行抽象描述,然后将其绑定到具体的网络协议和消息格式上以定义端点。相关的具体端点即组合成为抽象端点(服务)。

UDDI 的目的是为电子商务建立标准;UDDI是一套基于Web的、分布式的、为Web Service提供的、信息注册中心的实现标准规范,同时也包含一组使企业能将自身提供的Web Service注册,以使别的企业能够发现的访问协议的实现标准。

2、CORBA是什么?用途是什么?

OMG)标准化。它的组成是接口定义语言(IDL),语言绑定(binding:也译为联编)和允许应用程序间互操作的协议。其目的为:用不同的程序设计语言书写在不同的进程中运行,为不同的操作系统开发。

4、LINUX下线程,GDI类的解释。

LINUX实现的就是基于核心轻量级进程的"一对一"线程模型,一个线程实体对应一个核心轻量级进程,而线程之间的管理在核外函数库中实现。

GDI类为图像设备编程接口类库。

5. 问得稀里糊涂的题

page否是代表与一个页面相关的对象和属性。一个页面由一个编译好的 Java servlet类(可以带有任何的include指令,但是没有 include动作)表示。这既包括 servlet又包括被编译成 servlet的 JSP页面

request是是代表与 Web客户机发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个Web组件(由于 forward指令和 include动作的关系)

session是是代表与用于某个 Web客户机的一个用户体验相关的对象和属性。一个 Web会话可以也经常会跨越多个客户机请求

application是是代表与整个 Web应用程序相关的对象和属性。这实质上是跨越整个 Web应用程序,包括多个页面、请求和会话的一个全局作用域

区别主要答两点:a.条件操作只能操作布尔型的,而逻辑操作不仅可以操作布尔型,而且可以操作数值型

b.逻辑操作不会产生短路

1、请用英文简单介绍一下自己.

2、请把首页的这一段话用中文翻译一下?

1,堆和栈的区别,有一个64k的字符串,是放到堆上,还是放到栈上,为什么?

2,什么时候用到接口,什么时候用到抽象类,二者区别

3,有一个100万的数组,里边有两个市重复的,如何设计算法找到。

4,设计数据库时,n维,如何设计。

例如[省份][城市][网吧],这是三维关系,它的表也应该有三个,网吧有外键引用城市,城市有外键应用省份,这个规律就是下层的要有一外键去引用上层。

我要回帖

更多关于 python函数式编程例子 的文章

 

随机推荐