您的位置:首页 > 编程语言 > Python开发

python 基础复习笔记

2016-03-18 18:16 501 查看
本文是作者结合廖雪峰网站python教程和深入学习python3教程的笔记,多为个人截取比较关键的知识点。是基础的基础,不涉及IO、正则表达式、线程、XML等结合知识点。具体学习还请点击下面地址:

http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000

http://old.sebug.net/paper/books/dive-into-python3/index.html

>>> from humansize importapproximate_size

>>> approximate_size(4000,a_kilobyte_is_1024_bytes=False) ①

'4.0 KB'

>>> approximate_size(size=4000,a_kilobyte_is_1024_bytes=False) ②

'4.0 KB'

>>>approximate_size(a_kilobyte_is_1024_bytes=False, size=4000) ③

'4.0 KB'

>>>approximate_size(a_kilobyte_is_1024_bytes=False, 4000) ④

File "<stdin>", line 1

SyntaxError: non-keyword arg after keywordarg

>>> approximate_size(size=4000,False) ⑤

File "<stdin>", line 1

SyntaxError: non-keyword arg after keywordarg

① 这个对 approximate_size() 函数的调用给第一个参数((size)指定了值 4000,并且给名为 a_kilobyte_is_1024_bytes 的参数指定了值 False。(那碰巧是第二个参数,但这没有关系,马上你就会了解到。)

② 这个对 approximate_size() 函数的调用给名为 size 参数指定了值 4000,并为名为 a_kilobyte_is_1024_bytes 的参数指定了值 False。(这些命名参数碰巧和函数声明时列出的参数顺序一样,但同样不要紧。)

③ 这个对 approximate_size() 函数的调用给名为a_kilobyte_is_1024_bytes 的参数指定了值 False,然后给名为 size 的参数指定了值 4000。(看到了没?我告诉过你顺序没有关系。)

④ 这个调用会失败,因为你在命名参数后面紧跟了一个非命名(位置的)的参数,这个一定不会工作。从左到右的读取参数列表,一旦你有一个命名的参数,剩下的参数也必须是命名的。

⑤ 这个调用也会失败,和前面一个调用同样的原因。是不是很惊讶?别忘了,你给名为 size 的参数传入了值 4000,那么“显然的” False 这个值意味着对应了 a_kilobyte_is_1024_bytes 参数。但是 Python 不按照这种方式工作。只要你有一个命名参数,它右边的所有参数也都需要是命名参数。

很多 Python 的集成开发环境(ide)使用 docstring (文档字符串)来提供上下文敏感的文档,以便于当你输入一个函数名称的时候,它的 docstring 会以一个提示文本的方式显式出来。这可能会极其有用,但它只有在你写出好的 docstring (文档字符串)的时候才有用。

通过使用 sys.path.insert(0, new_path),你可以插入一个新的目录到sys.path 列表的第一项,从而使其出现在 Python 搜索路径的开头。这几乎总是你想要的。万一出现名字冲突(例如,Python 自带了版本 2 的一个特定的库,但是你想使用版本 3),这个方法就能确保你的模块能够被发现和使用,替代 Python 自带的版本。

你可能从其他程序语言环境中听说过 “first-class object” 的说法。在 Python 中,函数是 first-class objects,你可以将一个函数作为一个参数传递给另外一个函数;模块是 first-class objects,你可以把整个模块作为一个参数传递给一个函数;类是 first-class objects,而且类的单独的实例也是 first-classobjects。

这个很重要,因此刚开始我会重复几次以防你忘记了:在 Python 里面所有东西都是对象。字符串是对象,列表是对象,函数是对象,类是对象,类的实例是对象,甚至模块也是对象。

if size < 0:

raise ValueError('number must be non-negative')

抛出一个异常的语法足够简单。使用 raise 语句,紧跟着异常的名称,和一个人们可以读取的字符串用来调试。这个语法让人想起调用的函数。(实际上,异常是用类来实现的,这个 raise 语句事实上正在创建一个 ValueError 类的实例并传递一个字符串 'number must be non-negative' 到它的初始化方法里面。

try:

from lxml import etree

except ImportError:

import xml.etree.ElementTree as etree

在这个 try..except 块的结尾,你导入了某个模块并取名为 etree。由于两个模块实现了一个公共的 api,你剩下的代码不需要一直去检查哪个模块被导入了。而且由于这个一定会被导入的模块总是叫做 etree,你余下的代码就不会被调用不同名称模块的 if 语句所打乱。

if __name__ == '__main__':

print(approximate_size(1000000000000, False))

print(approximate_size(1000000000000))

那么是什么使得这个 if 语句特别的呢? 好吧,模块是对象,并且所有模块都有一个内置的属性 __name__。一个模块的 __name__ 属性取决于你怎么来使用这个模块。如果你 import 这个模块,那么__name__ 就是这个模块的文件名,不包含目录的路径或者文件的扩展名。

>>> import humansize

>>> humansize.__name__

'humansize'

但是你也可以当作一个独立的程序直接运行这个模块,那样的话 __name__ 将是一个特殊的默认值 __main__。 Python 将会评估这个 if 语句,寻找一个值为 true 的表达式,然后执行这个 if 代码块。在这个例子中,打印两个值。

c:\home\diveintopython3>c:\python31\python.exe humansize.py

1.0 TB

931.3 GiB

>>> float(2) ①

2.0

>>> int(2.0) ②

2

>>> int(2.5) ③

2

>>> int(-2.5) ④

-2

>>> 1.12345678901234567890 ⑤

1.1234567890123457

>>> type(1000000000000000) ⑥

<class 'int'>



通过调用float() 函数,可以显示地将 int 强制转换为 float。



毫不出奇,也可以通过调用 int() 将 float 强制转换为 int 。



int() 将进行取整,而不是四舍五入。



对于负数,int() 函数朝着 0 的方法进行取整。它是个真正的取整(截断)函数,而不是 floor[地板]

函数。



浮点数精确到小数点后 15 位。



整数可以任意大。

>>> −11
// 2 ③
−6
>>> 11.0
// 2 ④
5.0
③ 当整数除以负数, // 运算符将结果朝着最近的整数“向上”四舍五入。从数学角度来说,由于−6 比 −5 要小,它是“向下”四舍五入,如果期望将结果取整为 −5,它将会误导你。

④ //运算符并非总是返回整数结果。如果分子或者分母是 float,它仍将朝着最近的整数进行四舍五入,但实际返回的值将会是 float 类型。

>>> a_list = ['a', 'b', 'c']

>>> a_list.extend(['d', 'e','f']) ①

>>> a_list

['a', 'b', 'c', 'd', 'e', 'f']

>>> len(a_list) ②

6

>>> a_list[-1]

'f'

>>> a_list.append(['g', 'h','i']) ③

>>> a_list

['a', 'b', 'c', 'd', 'e', 'f', ['g', 'h','i']]

>>> len(a_list) ④

7

>>> a_list[-1]

['g', 'h', 'i']

① extend()方法只接受一个参数,而该参数总是一个列表,并将列表 a_list 中所有的元素都添加到该列表中。

② 如果开始有个 3 元素列表,然后将它与另一个 3 元素列表进行 extend 操作,结果是将获得一个 6 元素列表。

③ 另一方面, append() 方法只接受一个参数,但可以是任何数据类型。在此,对一个 3 元素列表调用 append() 方法。

④ 如果开始的时候有个 6 元素列表,然后将一个列表 append[添加]上去,结果就会……得到一个 7 元素列表。为什么是 7个?因为最后一个元素(刚刚 append[添加] 的元素) 本身是个列表 。列表可包含任何类型的数据,包括其它列表。这可能是你所需要的结果,也许不是。但如果这就是你想要的,那这就是你所得到的。

① 如果不带参数调用, pop() 列表方法将删除列表中最后的元素,并返回所删除的值。

② 可以从列表中 pop[弹出]任何元素。只需传给 pop() 方法一个位置索引值。它将删除该元素,将其后所有元素移位以“填补缝隙”,然后返回它删除的值。

③ 对空列表调用 pop() 将会引发一个例外。

① 在布尔类型上下文环境中,空列表为假值。

② 任何至少包含一个上元素的列表为真值。

③ 任何至少包含一个上元素的列表为真值。元素的值无关紧要。

那么元组有什么好处呢?

元组的速度比列表更快。如果定义了一系列常量值,而所需做的仅是对它进行遍历,那么请使用元组替代列表。

对不需要改变的数据进行“写保护”将使得代码更加安全。使用元组替代列表就像是有一条隐含的assert 语句显示该数据是常量,特别的想法(及特别的功能)必须重写。(??)

一些元组可用作字典键(特别是包含字符串、数值和其它元组这样的不可变数据的元组)。列表永远不能当做字典键使用,因为列表不是不可变的。

为创建单元素元组,需要在值之后加上一个逗号。没有逗号,Python 会假定这只是一对额外的圆括号,虽然没有害处,但并不创建元组。

由于从 Python 2 沿袭而来历史的古怪规定,不能使用两个花括号来创建空集合。该操作实际创建一个空字典,而不是一个空集合。

>>> a_set = {1, 2, 3}

>>> a_set

{1, 2, 3}

>>> a_set.update({2, 4, 6}) ①

>>> a_set ②

{1, 2, 3, 4, 6}

>>> a_set.update({3, 6, 9}, {1, 2,3, 5, 8, 13}) ③

>>> a_set

{1, 2, 3, 4, 5, 6, 8, 9, 13}

>>> a_set.update([10, 20,30]) ④

>>> a_set

{1, 2, 3, 4, 5, 6, 8, 9, 10, 13, 20, 30}

① update()方法仅接受一个集合作为参数,并将其所有成员添加到初始列表中。其行为方式就像是对参数集合中的每个成员调用add() 方法。

② 由于集合不能包含重复的值,因此重复的值将会被忽略。

③ 实际上,可以带任何数量的参数调用 update() 方法。如果调用时传递了两个集合, update() 将会被每个集合中的每个成员添加到初始的集合当中(丢弃重复值)。

④ update()方法还可接受一些其它数据类型的对象作为参数,包括列表。如果调用时传入列表,update() 将会把列表中所有的元素添加到初始集合中。

① discard()接受一个单值作为参数,并从集合中删除该值。

② 如果针对一个集合中不存在的值调用 discard() 方法,它不进行任何操作。不产生错误;只是一条空指令。

③ remove()方法也接受一个单值作为参数,也从集合中将其删除。

④ 区别在这里:如果该值不在集合中,remove() 方法引发一个 KeyError 例外。

① pop()方法从集合中删除某个值,并返回该值。然而,由于集合是无序的,并没有“最后一个”值的概念,因此无法控制删除的是哪一个值。它基本上是随机的。

② clear()方法删除集合中 所有 的值,留下一个空集合。它等价于 a_set = set(),该语句创建一个新的空集合,并用之覆盖 a_set 变量的之前的值。

>>> a_set = {2, 4, 5, 9, 12, 21,30, 51, 76, 127, 195}

>>> 30 in a_set ①

True

>>> 31 in a_set

False

>>> b_set = {1, 2, 3, 5, 6, 8, 9,12, 15, 17, 18, 21}

>>> a_set.union(b_set) ②

{1, 2, 195, 4, 5, 6, 8, 12, 76, 15, 17, 18,3, 21, 30, 51, 9, 127}

>>> a_set.intersection(b_set) ③

{9, 2, 12, 5, 21}

>>> a_set.difference(b_set) ④

{195, 4, 76, 51, 30, 127}

>>>a_set.symmetric_difference(b_set) ⑤

{1, 3, 4, 6, 8, 76, 15, 17, 18, 195, 127,30, 51}

① 要检测某值是否是集合的成员,可使用 in 运算符。其工作原理和列表的一样。

② union()方法返回一个新集合,其中装着 在两个 集合中出现的元素。

③ intersection()方法返回一个新集合,其中装着 同时 在两个集合中出现的所有元素。

④ difference()方法返回的新集合中,装着所有在 a_set 出现但未在b_set 中的元素。

⑤ symmetric_difference()方法返回一个新集合,其中装着所有 只在其中一个 集合中出现的元素。

至少包含一个键值对的字典为真值。

None 是 Python 的一个特殊常量。它是一个空 值。None 与 False 不同。None 不是 0 。None 不是空字符串。将 None 与任何非 None 的东西进行比较将总是返回 False 。

None 是唯一的空值。它有着自己的数据类型(NoneType)。可将 None 赋值给任何变量,但不能创建其它 NoneType 对象。所有值为 None 变量是相等的。

你可以在列表解析中使用任何的Python表达式, 包括os 模块中用于操作文件和目录的函数。

>>> a_dict = {'a': 1, 'b': 2, 'c':3}

>>> {value:key for key, value ina_dict.items()}

{1: 'a', 2: 'b', 3: 'c'}

字节即字节,并非字符。字符在计算机内只是一种抽象。字符串则是一种抽象的序列。

>>> import humansize

>>> si_suffixes =humansize.SUFFIXES[1000] ①

>>> si_suffixes

['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB','YB']

>>> '1000{0[0]} =1{0[1]}'.format(si_suffixes) ②

'1000KB = 1MB'

一个不可变(immutable)的Unicode编码的字符序列叫做string。一串由0到255之间的数字组成的序列叫做bytes对象。

bytes对象是不可变的;我们不可以给单个字节赋上新值。如果需要改变某个字节,可以组合使用字符串的切片和连接操作(效果跟字符串是一样的),或者我们也可以将bytes对象转换为bytearray对象。

>>> by = b'abcd\x65'

>>> barr = bytearray(by) ①

>>> barr

bytearray(b'abcde')

>>> len(barr) ②

5

>>> barr[0] = 102 ③

>>> barr

bytearray(b'fbcde')

① 使用内置函数bytearray()来完成从bytes对象到可变的bytearray对象的转换。

② 所有对bytes对象的操作也可以用在bytearray对象上。

③ 有一点不同的就是,我们可以使用下标标记给bytearray对象的某个字节赋值。并且,这个值必须是0–255之间的一个整数。

s.count(by.decode('ascii'))

bytes对象有一个decode()方法,它使用某种字符编码作为参数,然后依照这种编码方式将bytes对象转换为字符串,对应地,字符串有一个encode()方法,它也使用某种字符编码作为参数,然后依照它将串转换为bytes对象。

def fib(max):

a, b = 0, 1 ①

while a < max:

yield a ②

a, b = b, a + b ③

>>> assert 1 + 1 == 2 ①

>>> assert 1 + 1 == 3 ②

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

AssertionError

>>> assert 2 + 2 == 5, "Onlyfor very large values of 2" ③

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

AssertionError: Only for very large valuesof 2

① assert语句后面跟任何合法的Python 表达式。在这个例子里, 表达式 1 + 1 == 2 的求值结果为 True, 所以 assert 语句没有做任何事情。

② 然而, 如果Python 表达式求值结果为False, assert 语句会抛出一个 AssertionError.

③ 你可以提供一个人类可读的消息,AssertionError异常被抛出的时候它可以被用于打印输出。

因此, 这行代码:

assert len(unique_characters) <= 10,'Too many letters'

…等价于:

if len(unique_characters) > 10:

raiseAssertionError('Too many letters')

>>>unique_characters = {'E', 'D', 'M', 'O', 'N', 'S', 'R', 'Y'}

>>> gen= (ord(c) for c in unique_characters) ①

>>>gen ②

<generatorobject <genexpr> at 0x00BADC10>

>>>next(gen) ③

69

>>>next(gen)

68

>>>tuple(ord(c) for c in unique_characters) ④

(69, 68, 77, 79,78, 83, 82, 89)

① 生成器表达式类似一个yield值的匿名函数。表达式本身看起来像列表解析, 但不是用方括号而是用圆括号包围起来。

② 生成器表达式返回迭代器。

③ 调用 next(gen) 返回迭代器的下一个值。

④ 如果你愿意,你可以将生成器表达式传给tuple(),list(), 或者 set()来迭代所有的值并且返回元组,列表或者集合。在这种情况下,你不需要一对额外的括号 — 将生成器表达式ord(c) for c in unique_characters 传给 tuple() 函数就可以了, Python 会推断出它是一个生成器表达式。

(x+1 for x inlst) #生成器表达式,返回迭代器。外部的括号可在用于参数时省略。

[x+1 for x inlst] #列表解析,返回list

itertools.product()函数返回包含两个序列的笛卡尔乘积的迭代器。

eval() 并不限于布尔表达式。它能处理任何 Python 表达式并且返回任何数据类型。

字母谜题:

import re

import itertools

def solve(puzzle):

words = re.findall('[A-Z]+', puzzle.upper())

unique_characters =
set(''.join(words))

assert len(unique_characters)
<= 10,
'Too many letters'

first_letters =
{word[0]
for word
in words}

n = len(first_letters)

sorted_characters =
''.join(first_letters)
+ \

''.join(unique_characters
- first_letters)

characters = tuple(ord(c)
for c in sorted_characters)

digits = tuple(ord(c)
for c in
'0123456789')

zero = digits[0]

for guess
in itertools.permutations(digits, len(characters)):

if zero
not in guess[:n]:

equation = puzzle.translate(dict(zip(characters,
guess)))

if
eval(equation):

return equation

if __name__
== '__main__':

import sys

for puzzle
in sys.argv[1:]:

print(puzzle)

solution = solve(puzzle)

if solution:

print(solution)

通过re.findall()函数找到谜题中的所有字母

使用集合和set()函数找到谜题出现的所有不同的字母

通过assert语句检查是否有超过10个的不同的字母 (意味着谜题无解)

通过一个生成器对象将字符转换成对应的ASCII码值

使用itertools.permutations()函数计算所有可能的解法

使用translate()字符串方法将所有可能的解转换成Python表达式

使用eval()函数通过求值Python 表达式来检验解法

返回第一个求值结果为True的解法

单元测试事实上有 三种 返回值:通过、失败以及错误。“通过”,但当然就是说测试成功了──被测代码符合你的预期。“失败”就是就如之前的测试用例一样(直到你编写代码令它通过)──执行了被测试的代码但返回值并不是所期望的。“错误”就是被测试的代码甚至没有正确执行。

再次读取文件不会产生一个异常。Python不认为到达了文件末尾(end-of-file)还继续执行读取操作是一个错误;这种情况下,它只是简单地返回一个空字符串。

seek()和tell()方法总是以字节的方式计数,但是,由于你是以文本文件的方式打开的,read()方法以字符的个数计数。中文字符的UTF-8编码需要多个字节。而文件里的英文字符每一个只需要一个字节来存储,所以你可能会产生这样的误解:seek()和read()方法对相同的目标计数。而实际上,只有对部分字符的情况是这样的。

withopen('examples/chinese.txt', encoding='utf-8') as a_file:

a_file.seek(17)

a_character = a_file.read(1)

print(a_character)

这段代码调用了open()函数,但是它却一直没有调用a_file.close()。with语句引出一个代码块,就像if语句或者for循环一样。在这个代码块里,你可以使用变量a_file作为open()函数返回的流对象的引用。所以流对象的常规方法都是可用的 — seek(),read(),无论你想要调用什么。当with块结束时,Python自动调用a_file.close()。

这就是它与众不同的地方:无论你以何种方式跳出with块,Python会自动关闭那个文件…即使是因为未处理的异常而“exit”。是的,即使代码中引发了一个异常,整个程序突然中止了,Python也能够保证那个文件能被关闭掉。

with语句不只是针对文件而言的;它是一个用来创建运行时环境的通用框架(generic framework),告诉对象它们正在进入和离开一个运行时环境。如果该对象是流对象,那么它就会做一些类似文件对象一样有用的动作(就像自动关闭文件!)。但是那个行为是被流对象自身定义的,而不是在with语句中。

只要对象包含read()方法,这个方法使用一个可选参数size并且返回值为一个串,它就是是流对象。不使用size参数调用read()的时候,这个方法应该从输入源读取所有可读的信息然后以单独的一个值返回所有数据。当使用size参数调用read()时,它从输入源读取并返回指定量的数据。当再一次被调用时,它从上一次离开的地方开始读取并返回下一个数据块。

定义函数时,需要确定函数名和参数个数;

如果有必要,可以先对参数的数据类型做检查;

函数体内部可以用return随时返回函数结果;

函数执行完毕也没有return语句时,自动return None。

函数可以同时返回多个值,但其实就是一个tuple。

默认参数必须指向不变对象(L[])

默认参数必须指向不变对象. 命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错

在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用,除了可变参数无法和命名关键字参数混合。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数/命名关键字参数和关键字参数。

对于任意函数,都可以通过类似func(*args,**kw)的形式调用它,无论它的参数是如何定义的

Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。

默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!

要注意定义可变参数和关键字参数的语法:

*args是可变参数,args接收的是一个tuple;

**kw是关键字参数,kw接收的是一个dict。

以及调用函数时如何传入可变参数和关键字参数的语法:

可变参数既可以直接传入:func(1,2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3));

关键字参数既可以直接传入:func(a=1,b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})。

使用*args和**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。

命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。

定义命名的关键字参数不要忘了写分隔符*,否则定义的将是位置参数。

函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

def triangles():

N=[1]

while True:

yield N

N.append(0)

N=[N[i-1]+N[i] for i in range(len(N))]

既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

def count():

fs = []

for i in range(1, 4):

def f():

return i*i

fs.append(f)

return fs

f1, f2, f3 =count()

返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变.

def count():

def f(j):

def g():

return j*j

return g

fs = []

for i in range(1, 4):

fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()

return fs

关键字lambda表示匿名函数,冒号前面的x表示函数参数。

匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。

用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数

def log(func):

def wrapper(*args, **kw):

print('call %s():' % func.__name__)

return func(*args, **kw)

return wrapper

def log(text):

def decorator(func):

def wrapper(*args, **kw):

print('%s %s():' % (text, func.__name__))

return func(*args, **kw)

return wrapper

return decorator

import functools

def log(func):

@functools.wraps(func)

def wrapper(*args, **kw):

print('call %s():' % func.__name__)

return func(*args, **kw)

return wrapper

简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

注意到上面的新的int2函数,仅仅是把base参数重新设定默认值为2,但也可以在函数调用时传入其他值

创建偏函数时,实际上可以接收函数对象、*args和**kw这3个参数

,每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是mycompany

任何模块代码的第一个字符串都被视为模块的文档注释

private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。

注意到__init__方法(别的类的方法也是)的第一个参数永远是self(这是强制习惯),表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。总的来说就是,Python本身没有任何机制阻止你干坏事,一切全靠自觉。

静态语言 vs 动态语言

对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。

对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了。这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

class A(object):

def run(self):

print(A.__name__)

class B(A):

def run(self):

print(B.__name__)

class C(A):

def run(self):

print(C.__name__)

class D(object):

def run(self):

pass

def runs(A):

A.run()

x = D()

runs(x)

使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。

>>> classStudent(object):

... def __init__(self, name):

... self.name = name

... def __str__(self):

... return 'Student object (name:%s)' % self.name

...

>>> print(Student('Michael'))

Student object(name: Michael)

这样打印出来的实例,不但好看,而且容易看出实例内部重要的数据。

但是细心的朋友会发现直接敲变量不用print,打印出来的实例还是不好看:

>>> s =Student('Michael')

>>> s

<__main__.Studentobject at 0x109afb310>

这是因为直接显示变量调用的不是__str__(),而是__repr__(),两者的区别是__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,__repr__()是为调试服务的。

只有在没有找到属性的情况下,才调用__getattr__,已有的属性,比如name,不会在__getattr__中查找

如果你把对象看成函数,那么函数本身其实也可以在运行期动态创建出来,因为类的实例都是运行期创建出来的,这么一来,我们就模糊了对象和函数的界限

动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。

def fn(self,name='world'): # 先定义函数

... print('Hello, %s.' % name)

...

>>>Hello = type('Hello', (object,), dict(hello=fn)) # 创建Helloclass

要创建一个class对象,type()函数依次传入3个参数:

class的名称;
继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。

通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。

metaclass,直译为元类,简单的解释就是:

当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。

但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。

连接起来就是:先定义metaclass,就可以创建类,最后创建实例。

所以,metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。

由于没有错误发生,所以except语句块不会被执行,但是finally如果有,则一定会被执行(可以没有finally语句)Python的错误其实也是class,所有的错误类型都继承自BaseException,所以在使用except时需要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”

try:

foo()

except ValueError as e:

print('ValueError')

except UnicodeError as e:

print('UnicodeError')

第二个except永远也捕获不到UnicodeError,因为UnicodeError是ValueError的子类,如果有,也被第一个except给捕获了。

Python内置的logging模块可以非常容易地记录错误信息

如果要抛出错误,首先根据需要,可以定义一个错误的class,选择好继承关系,然后,用raise语句抛出一个错误的实例:

#err_raise.py

class FooError(ValueError):

pass

def foo(s):

n = int(s)

if n==0:

raise FooError('invalid value:%s' % s)

return 10 / n

foo('0')

断言

凡是用print()来辅助查看的地方,都可以用断言(assert)来替代:

def foo(s):

n = int(s)

assert n != 0, 'n is zero!'

return 10 / n

def main():

foo('0')

assert的意思是,表达式n != 0应该是True,否则,根据程序运行的逻辑,后面的代码肯定会出错。

如果断言失败,assert语句本身就会抛出AssertionError:

$ python3 err.py

Traceback (mostrecent call last):

...

AssertionError:n is zero!

logging

把print()替换为logging是第3种方式,和assert比,logging不会抛出错误,而且可以输出到文件:

import logging

s = '0'

n = int(s)

logging.info('n= %d' % n)

print(10 / n)

logging.info()就可以输出一段文本。

编写单元测试时,我们需要编写一个测试类,从unittest.TestCase继承。

以test开头的方法就是测试方法,不以test开头的方法不被认为是测试方法,测试的时候不会被执行。

对每一类测试都需要编写一个test_xxx()方法。由于unittest.TestCase提供了很多内置的条件判断,我们只需要调用这些方法就可以断言输出是否是我们所期望的。最常用的断言就是assertEqual()。可以在单元测试中编写两个特殊的setUp()和tearDown()方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。

import math

def abbs(n):

'''

ABS

Example:

>>> abbs(1)

1

>>> abbs(-1)

1

>>> abs(0)

0

'''

return
abs(n)

if
__name__ == '__main__':

import
doctest

doctest.testmod()

注:作者初学,水平有限,若有不妥之处,请多多指教。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: