第 6 章 抽象
1.给函数编写文档
给函数编写文档,以确保其他人能够理解,可添加注释(以#打头的内容)。还有另一种编写注释的方式,就是添加独立的字符串。放在函数开头的字符串称为文档字符串(docstring),将作为函数的一部分存储起来。
例子:
def square(x):
'计算给定x的平方值'
return x * x
print(square(10))
print(square.__doc__)
#out
100
计算给定x的平方值
数学意义上的函数总是返回根据参数计算得到的结果。在Python中,有些函数什么都不返回。但在Python中,函数就是函数,即使它严格来说并非函数。什么都不返回的函数不包含return语句,或者包含return语句,但没 有在return后面指定值。
def test():
print('This is printed')
return #return语句只是为了结束函数
print('This is not')
x = test()
print(x)
#输出 直接跳过第二个print
This is printed
None
#python中所有的函数都返回值,如果没有指定返回什么,就将返回None
2.参数魔法
这里只介绍收集参数和分配参数
2.1 收集参数
python允许向函数提供任意数量的参数,只需遵循下面的定义:
def print_params(*params):
print(params)
print_params('bozhou')
print_params('bozhou', 'anyi', 'zzh')
#输出
('bozhou',) #括号里面有个逗号,说明返回的结果为一个元组类型结果
('bozhou', 'anyi', 'zzh') #这个结果就更明显了
进一步示例:
def print_params_2(want, *params):
print(want)
print(params)
print_params_2('hello', 5, 2, 1)
#输出
hello
(5, 2, 1) #参数前带星号,意味着将余下的值收集为一个元组
进一步
def in_the_middle(x, *y, z):
print(x, y, z)
in_the_middle(1, 2, 3, 4, z=10) #也可以放在其他位置,就像本例一样要指定后续参数名称以便区分,否则报错
#输出
1 (2, 3, 4) 10
收集关键字参数,可使用两个星号
def print_params_3(**params):
print(params)
print_params_3(x=1, y=2, z=3)
#输出一个字典而不是元组
{'z': 3, 'x': 1, 'y': 2}
进一步可结合使用,如:
def print_params_4(x, y, z=3, *pospar, **keypar):
print(x, y, z)
print(pospar)
print(keypar)
print_params_4(1, 2, 3, 4, 5, a=10, b=20)
#输出
1 2 3
(4, 5)
{'a': 10, 'b': 20}
2.2 分配参数
执行与收集参数相反的操作,用例子来说明什么是相反的操作:
def add(x, y):
return x + y
params = (1, 2)
print(add(*params))
#输出
3
进一步
#本例是只在调用的时候使用了星号
def fun(name, age):
print('name:', name)
print('age:', age)
params = {'name': 'zzh', 'age': 25}
fun(**params)
#输出
name: zzh
age: 25
#函数定义和调用都使用星号
def fun1(**params):
print('name:', params['name'])
print('age:', params['age'])
fun1(**params)
#输出
name: zzh
age: 25
#普通传参
def fun1(params):
print('name:', params['name'])
print('age:', params['age'])
fun1(params)
#输出
name: zzh
age: 25
本小节中,星号被称为拆分运算符。
3 作用域
作用域:变量存储在作用域(也叫命名空间)中。在Python中,作用域分两大类:全局作用域和局部作用域。作用域可以嵌套。
变量到底是什么呢?可将其视为指向值的名称。因此,执行赋值语句x = 1后,名称x指向值1。这几乎与使用字典时一样(字典中的键指向值),只是你使用的是“看不见”的字典。实际上,这种解释已经离真相不远。有一个名为vars的内置函数,它返回这个不可见的字典:
x = 1
scope = vars()
print(type(scope))
print(scope['x'])
#输出
1
#这种“看不见的字典”称为命名空间或作用域
读取全局变量的值通常不会有问题,但还是存在出现问题的可能性。如果有一个局部
变量或参数与你要访问的全局变量同名,就无法直接访问全局变量,因为它被局部变量遮
住了。
如果需要,可使用函数globals来访问全局变量。这个函数类似于vars,返回一个包含全局变量的字典。(locals返回一个包含局部变量的字典)。当函数内外有两个变量名称相同时,必要时可以使用以下方式在函数内部访问函数外的那个全局变量:
globals()['parameter']
待续```````````````````
第 7 章 再谈抽象
开篇定义
多态:可对不同类型的对象执行相同的操作
封装:对外部隐藏有关对象工作原理的细节
继承:可基于通用类创建出专用类
1 对象魔法
1.1 多态
多态,即多种形态,即便你不知道变量(参数变量)指向的是哪种对象,也能够对其执行操作,且操作的行为将随对象所属的类型(类)而异
示例:
#可假设本例为获取狗狗的体重
def get_something(object):
if isinstance(object, tuple): #当以元组的形式传入时,如('哈士奇', 50)
return object[1]
#元组的形式过于固定,因为小狗的体重是变化的
#可以字典的形式传入,将体重单独存在名称为“体重”的key下,这样更加灵活
elif isinstance(object, dict):
return int(object['height'])
#可以看到一个功能函数,对应两个甚至多个不同类型的对象都可以进行操作
进一步,还有更多的例子:
>>> 'abc'.count('a')
1
>>> [1, 2, 'a'].count('a')
1
#无需知道它是字符串还是列表就能调用方法count:只要你向这个方法
提供一个字符作为参数,它就能正常运行。
不仅仅是函数,python中的运算符也体现出多态的特点,如:
>>> 1 + 2
3
>>> 'zzh' + 'bozhou'
'zzhbozhou'
这里讨论的多态形式是Python编程方式的核心,有时称为鸭子类型。这个术语源自如下说法:“如果走起来像鸭子,叫起来像鸭子,那么它就是鸭子。
1.2 封装
在程序设计中,封装(Encapsulation)是对具体对象的一种抽象,即将某些部分隐藏起来,在程序外部看不到,其含义是其他程序无法调用。
封装数据的主要原因是:保护隐私(把不想别人知道的东西封装起来)
封装方法的主要原因是:隔离复杂度
封装其实分为两个层面,但无论哪种层面的封装,都要对外界提供好访问你内部隐藏内容的接口
第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装
第二层次的封装:是指把类中的属性和方法私有化,只供内部使用,也可称为隐藏,但不是真正的隐藏
以上两点下面会提到
1.3 继承
对一个通用类进行拓展产生子类,但程序员不想把代码再写一遍,所以对类继承财产并发扬光大的功能就产生了
2 类
2.1 隐藏
要让方法或属性成为私有的(不能从外部访问),只需让其名称以两个下划线打头即可:
class Secretive:
def __inaccessible(self):
print("Bet you can't see me ...")
def accessible(self):
print("The secret message is:")
self.__inaccessible()
o = Secretive()
o.accessible()
#输出
The secret message is:
Bet you can't see me ...
#可以看到仍然是可以用其他方式访问的,并不是真正意义上的隐藏
2.2 继承
至于什么是继承,怎么继承这里就不多叙述了..............
我们来看看如何查看一个类是否是另一个类的子类,python已经提供了内置方法,例子如下:
#类定义就不写了
>>> issubclass(son, father)
True
#再者如果想查看一个类的基类,可访问其特殊属性__bases__
son.__bases__
要确定一个对象是否是特定类的实例,可使用isinstance,用法同上
进一步
x = son()
#所有son类的对象实例都是类father的对象实例,因为son是father的子类,
这时候使用isinstance可能不是精准的,下面小节中会讲到抽象基类。
2.3 多个超类
一个类的父类可以有多个,如何继承语法形式如下:
#多重继承
class son(father1, father2):
pass
需要注意的是:
使用多重继承时,有一点务必注意:如果多个超类以不同的方式实现了同一个方法(即有多个同名方法),必须class语句中小心排列这些超类,因为位于前面的类的方法将覆盖位于后面在的类的方法
2.4 接口和内省
类中的方法属性都是类的协议接口,多态和封装的思想使我们只关心怎样使用接口,并不关系其中的细节,但python也提供了了解接口的方法,如查看、获取、设置:
class son:
temp = 10
def fun(self, a):
self.name = a
print('my name is: ', self.name)
o = son()
print(hasattr(o, 'fun'))
print(getattr(o, 'fun'))
print(getattr(o, 'fun1', None))
setattr(o, 'temp', 7)
print(o.temp)
#输出
True
>
None
7
2.5 抽象基类
一般而言,抽象类是不能(至少是不应该)实例化的类,其职责是定义子类应实现或者说必要的一组抽象方法,示例如下:
Python通过引入模块abc提供了官方解决方案,这个模块为所谓的抽象基类提供了支持。
from abc import ABC, abstractmethod
class Talker(ABC):
@abstractmethod #将方法标记为抽象的——在子类中必须实现的方法
def talk(self):
pass
抽象类(即包含抽象方法的类)最重要的特征是不能实例化
如果继承的子类没有重写talk方法,则该子类也是抽象类,不能进行实例化否则报错!
#正确做法
class Knigget(Talker):
def talk(self):
print("Ni!")
现在可以进行实例化,而且只有在这种情形下使用isinstance才是比较精确的,
这种情况下,我们可以相信这个实例有对应的方法(如本例talk方法),相对
于手工检测方法抽象基类是个更好的选择
因为在处理编程和对象时,强调构成问题而不是身份问题,强调hasattr函数而不是isinstance函数(而前者是更精确的)
第 9 章 魔法方法、特性和迭代器
1 元素访问
在Python中,协议通常指的是规范行为的规则。协议指定应实现哪些方法以及这些方法应做什么,类似于接口。序列和映射基本上是元素(item)的集合。
- __len__(self):这个方法应返回集合包含的项数,对序列来说为元素个数,对映射来说为键-值对数
- __getitem__(self, key):这个方法应返回与指定键相关联的值。对序列来说,键应该是0~n-1的整数,其中n为序列的长度
- __setitem__(self, key, value):这个方法应以与键相关联的方式存储值,以便以后能够使用__getitem__来获取
- __delitem__(self, key):这个方法在对对象的组成部分使用__del__语句时被调用,应删除与key相关联的值
class temp:
def __init__(self, value):
self.value = value
self.seq = {}
def __len__(self):
return len(self.seq)
def __getitem__(self, key):
print('i am getitem!')
return self.seq[key]
def __setitem__(self, key, value):
print('i am setitem!')
self.seq[key] = self.value
b = temp(100)
b[1] = 10 #对self.[key]进行赋值操作时调用__getitem__
print(b[1]) #以self[key]的方式获取值时调用__setitem__
print(len(b))
#输出、由输出可看到调用了哪个方法
i am setitem!
i am getitem!
100
1
函数 property
示例:
class Rectangle:
def __init__ (self):
self.width = 0
self.height = 0
def set_size(self, size):
self.width, self.height = size
def get_size(self):
return self.width, self.height
r = Rectangle()
r.width = 10
r.height = 5
print(r.get_size())
print(r.set_size((10, 20)))
print(r.width)
#输出
(10, 5)
None
10
使用property函数
通过调用函数property并将存取方法作为参数(获取方法在前,设置方法在后)
创建了一个新特性(如果我们需要这个特性的话,property给我提供了一个方便
的实现,否则我们还要重新写类方法),然后将名称size关联到这个特性;
class Rectangle:
def __init__ (self):
self.width = 0
self.height = 0
def set_size(self, size):
self.width, self.height = size
def get_size(self):
return self.width, self.height
size = property(get_size, set_size)
r = Rectangle()
r.width = 10
r.height = 5
print(r.size) #r.size会触发get_size方法
r.size = 150, 100 #赋值会触发set_size方法
print(r.width)
#输出
(10, 5)
150
property形式
class property([fget[, fset[, fdel[, doc]]]])
fdel参数 -- 删除属性值函数
doc参数 -- 属性描述信息
访问控制
__getattr__(self, name)
当用户试图访问一个根本不存在(或者暂时不存在)的属性时,你可以通过这个魔
法方法来定义类的行为。这个可以用于捕捉错误的拼写并且给出指引,使用废弃属
性时给出警告(如果你愿意,仍然可以计算并且返回该属性),以及灵活地处理属
性错误。只有当试图访问不存在的属性时它才会被调用,所以这不能算是一个真正
的封装的办法
------------------------------------------------------------------------
__setattr__(self, name, value)
和 __getattr__ 不同, __setattr__ 可以用于真正意义上的封装。它允许你自定义某
个属性的赋值行为,不管这个属性存在与否,也就是说你可以对任意属性的任何变
化都定义自己的规则。然后,一定要小心使用 __setattr__ ,这个列表最后的例子
中会有所展示
-------------------------------------------------------------------------
__getattribute__(self, name):在属性被访问时自动调用
-------------------------------------------------------------------------
__delattr__(self, name):试图删除属性时自动调用
2 迭代器
迭代(iterate)意味着重复多次,就像循环那样。已知的for循环迭代序列和字典,但实际上也可迭代其他对象:实现了方法__ iter__的对象;
方法__ iter__返回一个迭代器(可迭代对象),前提是该对象包含方法__ next__ ;当你调用方法__ next__时,迭代器应返回其下一个值。如果迭代器没有可供返回的值,应引发StopIteration异常;
使用 iter() 函数调用,以及在类似 for x in container: 的循环时被调用
class Fibs:
def __init__(self):
self.a = 0
self.b = 1
def __next__(self):
print("b")
self.a, self.b = self.b, self.a + self.b
return self.a
def __iter__(self):
print("a")
return self
b = Fibs()
print("start!")
for f in b:
print("d")
if f > 2:
print("mark")
print(f)
break
#输出,通过输出字母的顺便来看是方法触发的顺序
start!
a
b
d
b
d
b
d
b
d
mark
3
迭代与使用列表相比,在某些情况下,更简洁;列表可能会占有过多的内存,而有
时候我们想逐个的取值,再者如上例所示斐波那契数列是无穷大的,使用列表不合
适;
3 生成器
生成器是一种使用普通函数语法定义的迭代,当Python 函数不用return 返回值,用yield关键字的时候,函数的返回值为生成器对象;生成器不是使用return返回一个值,而是可以生成多个值,每次一个。
创建生成器
def fun():
yield 10
print("z~~~~~~~")
yield 20
a = fun()
print(a)
print(a.__next__())
print(next(a))
# print(next(a)) #报错,没有可供返回的值了
#输出
10
z~~~~~~~
20
另一种创建形式,生成器表达式
g = ((i + 2) ** 2 for i in range(2, 27))
print(next(g))
print(next(g))
#输出
16
25