一、Python repr()方法:显示属性
事实上,当使用该方法输出 Item 对象时,实际上输出的是 Item 对象的 repr() 方法的返回值。也就是说,下面两行代码的效果完全一样:
print (im)
print (im.__repr__)
二、Python __del__方法:销毁对象
当程序不再需要一个 Python 对象时,系统必须把该对象所占用的内存空间释放出来,这个过程被称为垃圾回收(GC,Garbage Collector),Python 会自动回收所有对象所占用的内存空间,因此开发者无须关心对象垃圾回收的过程。
Python 采用自动引用计数(ARC)方式来回收对象所占用的空间,
当程序中有一个变量引用该 Python对象时,Python 会自动保证该对象引用计数为 1;
当程序中有两个变量引用该 Python 对象时,Python 会自动保证该对象引用计数为 2
依此类推,如果一个对象的引用计数变成了 0,
则说明程序
中不再有变量引用该对象,表明程序不再需要该对象,因此 Python 就会回收该对象。
大部分时候,Python 的 ARC 都能准确、高效地回收系统中的每个对象。但如果系统中出现循环引用的情况,比如对象 a 持有一个实例变量引用对象 b,而对象 b 又持有一个实例变量引用对象 a,此时两个对象的引用计数都是 1,而实际上程序已经不再有变量引用它们,系统应该回收它们,此时 Python 的垃圾回收器就可能没那么快,要等专门的循环垃圾回收器(Cyclic Garbage Collector)来检测并回收这种引用循环。
===================================================================
当一个对象被垃圾回收时,Python 就会自动调用该对象的 del 方法。需要说明的是,不要以为对一个变量执行 del 操作,该变量所引用的对象就会被回收,
只有当对象的引用计数变成 0 时,该对象才会被回收。因此,如果一个对象有多个变量引用它,那么 del 其中一个变量是不会回收该对象的。
class Item:
def __init__ (self, name, price):
self.name = name
self.price = price
# 定义析构函数
def __del__ (self):
print('del删除对象')
# 创建一个Item对象,将之赋给im变量
im = Item('鼠标', 29.8)
x = im # ①
# 打印im所引用的Item对象
del im
print('--------------')
程序中重写了 Item 类的 del() 方法,该方法就是 Item 类的析构函数,当系统将要回收 Item 时,系统会自动调用 Item 对象的 del() 方法。
上面程序先创建了一个 Item 对象,并将该对象赋值给 im 变量,① 号代码又将 im 赋值给变量 x,这样程序中有两个变量引用 Item 对象,接下来程序执行 del im 代码删除 im 对象,此时由于还有变量引用该 Item 对象,因此程序并不会回收 Item 对象。
运行上面程序,可以看到如下输出结果:
--------------
del删除对象
从上面程序的输出结果可以看到,del im 执行之后,程序并没有回收 Item 对象,只有等到程序执行将要结束时(系统必须回收所有对象),系统才会回收 Item 对象。
三、Python __dir__用法:列出对象的所有属性(方法)名
print(im.__dir__()) # 返回所有属性(包括方法)组成列表
print(dir(im)) # 返回所有属性(包括方法)===排序====之后的列表
四、Python __dict__属性:查看对象内部所有属性名和属性值组成的字典
五、setattr()、getattr()、hasattr()函数用法详解
在动态检查对象是否包含某些属性(包括方法〉相关的函数有如下几个:
**hasattr(obj, name):**检查 obj 对象是否包含名为 name 的属性或方法。
**getattr(object, name[, default]):**获取 object 对象中名为 name 的属性的属性值。
**setattr(obj, name, value,/):**将obj 对象的 name 属性设为 value。
class Comment:
def __init__ (self, detail, view_times):
self.detail = detail
self.view_times = view_times
def info ():
print("一条简单的评论,内容是%s" % self.detail)
c = Comment('疯狂Python讲义很不错', 20)
# 判断是否包含指定的属性或方法
print(hasattr(c, 'detail')) # True
print(hasattr(c, 'view_times')) # True
print(hasattr(c, 'info')) # True
# 获取指定属性的属性值
print(getattr(c, 'detail')) # '疯狂Python讲义很不错'
print(getattr(c, 'view_times')) # 20
# 由于info是方法,故下面代码会提示:name 'info' is not defined
#print(getattr(c, info, '默认值'))
# 为指定属性设置属性值
setattr(c, 'detail', '天气不错')
setattr(c, 'view_times', 32)
# 输出重新设置后的属性值
print(c.detail)
print(c.view_times)
实际上 setattr() 函数还可对方法进行设置,在使用 setattr() 函数重新设置对象的方法时, 新设置的方法是未绑定方法。
def bar ():
print('一个简单的bar方法')
# 将c的info方法设为bar函数
setattr(c, 'info', bar)
c.info()
六、issubclass和isinstance函数:检查类型
Python 提供了如下两个函数来检查类型:
**issubclass(cls, class_or_tuple):**检查 cls 是否为后一个类或元组包含的多个类中任意类的子类。
**isinstance(obj, class_or_tuple):**检查 obj 是否为后一个类或元组包含的多个类中任意类的对象。
# str是object类的子类,输出True
print('str是否是object类的子类: ', issubclass(str, object))
my_list = [2, 4]
# [2, 4]是list类的实例,输出True
print('[2, 4]是否是list类的实例: ', isinstance(my_list, list))
七、__call__方法
我们用 hasattr() 函数判断指定属性(或方法)是否存在,但到底是属性还是方法,则需要进一步判断它是否可调用。程序可通过判断该属性(或方法)是否包含 call 属性来确定它是否可调用。
class User:
def __init__(self, name, passwd):
self.name = name
self.passwd = passwd
def validLogin (self):
print('验证%s的登录' % self.name)
u = User('crazyit', 'leegang')
# 判断u.name是否包含__call__方法,即判断是否可调用
print(hasattr(u.name, '__call__')) # False
# 判断u.passwd是否包含__call__方法,即判断是否可调用
print(hasattr(u.passwd, '__call__')) # False
# 判断u.validLogin是否包含__call__方法,即判断是否可调用
print(hasattr(u.validLogin, '__call__')) # True
对于程序中的函数,同样既可使用函数的语法来调用它,也可把函数当成对象,调用它的 call 方法。例如如下示例代码:
def foo ():
print('--foo函数--')
# 下面示范了2种方式调用foo()函数
foo()
foo.__call__()
运行上面代码,可以看到 foo() 和 foo.call() 的效果完全相同。
八、getitem、setitem、delitem、len、__contains__用法
序列最重要的特征就是可包含多个元素, 因此和序列有关的特殊方法有如下几个:
len(self):该方法的返回值决定序列中元素的个数。
getitem(self, key):该方法获取指定索引对应的元素。该方法的 key 应该是整数值或 slice 对象,否则该方法会引发 KeyError 异常。
contains(self, item):该方法判断序列是否包含指定元素。
setitem(self, key, value):该方法设置指定索引对应的元素。该方法的 key 应该是整数值或 slice 对象,否则该方法会引发 KeyError 异常。
delitem(self, key):该方法删除指定索引对应的元素。
如果程序要实现不可变序列(程序只能获取序列中的元素,不能修改),只要实现上面前 3 个方法就行;
如果程序要实现可变序列(程序既能获取序列中的元素,也可修改),则需要实现上面
5 个方法。
九、 iter__和__reversed:实现迭代器
如果开发者需要实现迭代器,只要实现如下两个方法即可
:
iter(self):该方法返回一个迭代器(iterator),迭代器必须包含一个__next__()方法,该方法返回迭代器的下一个元素。
reversed(self):该方法主要为内建的 reversed() 反转函数提供支持,当程序调用 reversed() 函数对指定迭代器执行反转时,实际上是由该方法实现的。
定义一个代表斐波那契数列的迭代器
class Fibs:
def __init__(self, len):
self.first = 0
self.sec = 1
self.__len = len
# 定义迭代器所需的__next__方法
def __next__(self):
# 如果__len__属性为0,结束迭代
if self.__len == 0:
raise StopIteration
# 完成数列计算:
self.first, self.sec = self.sec, self.first + self.sec
# 数列长度减1
self.__len -= 1
return self.first
# 定义__iter__方法,该方法返回迭代器
def __iter__(self):
return self
# 创建Fibs对象
fibs = Fibs(10)
# 获取迭代器的下一个元素
print(next(fibs))
# 使用for循环遍历迭代器
for el in fibs:
print(el, end=' ')
上面程序定义了一个 Fibs 类,该类实现了 iter() 方法,该方法返回 self,因此它要求该类必须提供 next() 方法,该方法会返回数列的下二个值。程序使用 len 属性控制数列的剩余长度,当 __len 为 0 时,程序停止遍历。
上面程序创建了一个长度为 10 的数列,程序开始使用内置的 next() 函数来获取迭代器的下一个元素,该 next() 函数其实就是通过迭代器的 next() 方法来实现的。
程序接下来使用 for 循环来遍历应数列。运行该程序,将看到如下输出结果:
1 1 2 3 5 8 13 21 34 55
此外,程序可使用内置的 iter() 函数将列表、元组等转换成迭代器,例如如下代码:
将列表转换为迭代器
my_iter = iter([2, 'fkit', 4])
# 依次获取迭代器的下一个元素
print(my_iter.__next__()) # 2
print(my_iter.__next__()) # fkit
十、生成器详解
生成器和迭代器的功能非常相似,它也会提供 next() 方法,这意味着程序同样可调用内置的 next() 函数来获取生成器的下一个值,也可使用 for 循环来遍历生成器。
生成器与迭代器的区别在于,迭代器通常是先定义一个迭代器类,然后通过创建实例来创建迭代器;而生成器则是先定义一个包含 yield 语句的函数,然后通过调用该函数来创建生成器。
1.创建生成器
创建生成器需要两步操作:
1.定义一个包含 yield 语句的函数。
2.调用第 1 步创建的函数得到生成器。
def test(val, step):
print("--------函数开始执行------")
cur = 0
# 遍历0~val
for i in range(val):
# cur添加i*step
cur += i * step
yield cur
yield cur 语句的作用有两点:
1.每次返回一个值,有点类似于 return 语句。
2.冻结执行,程序每次执行到 yield 语句时就会被暂停。
# 执行函数,返回生成器
t = test(10, 2)
print('=================')
# 获取生成器的第一个值
print(next(t)) # 0,生成器“冻结”在yield处
print(next(t)) # 2,生成器再次“冻结”在yield处
for ele in t:
print(ele, end=' ')
6 12 20 30 42 56 72 90
此外,程序可使用 list() 函数将生成器能生成的所有值转换成列表,也可使用 tuple() 函数将生成器能生成的所有值转换成元组。例如如下代码:
#再次创建生成器
t = test(10, 1)
#将生成器转换成列表
print (list (t))
#再次创建生成器
t = test(10, 3)
#将生成器转换成元组
print(tuple(t))
运行上面代码,可以看到如下输出结果:
--------函数开始执行------
[0 , 1 , 3, 6 , 10 , 15 , 21 , 28 , 36 , 45]
--------函数开始执行------
(0 , 3 , 9 , 18 , 30 , 45 , 63 , 84 , 108, 135)
2.生成器的方法
为了实现生成器与“外部程序” 动态地交换数据,需要借助于生成器的 send() 方法
对于上面详细的描述,归纳起来就是两句话:
1.外部程序通过 send() 方法发送数据。
2.生成器函数使用 yield 语句接收收据。
另外,需要说明的是,只有等到生成器**被“冻结”**之后,外部程序才能使用 send() 方法向生成器发送数据。获取生成器第一次所生成的值,应该使用 next() 函数;如果程序非要使用 send() 方法获取生成器第一次所生成的值,也不能向生成器发送数据,只能为该方法传入 None 参数。
def square_gen(val):
i = 0
out_val = None
while True:
# 使用yield语句生成值,使用out_val接收send()方法发送的参数值
out_val = (yield out_val ** 2) if out_val is not None else (yield i ** 2)
# 如果程序使用send()方法获取下一个值,out_val会获取send()方法的参数
if out_val is not None : print("====%d" % out_val)
i += 1
sg = square_gen(5)
# 第一次调用send()方法获取值,只能传入None作为参数
print(sg.send(None)) # 0
print(next(sg)) # 1
print('--------------')
# 调用send()方法获取生成器的下一个值,参数9会被发送给生成器
print(sg.send(9)) # 81
# 再次调用next()函数获取生成器的下一个值
print(next(sg)) # 9
运行上面程序,可以看到如下输出结果:
0
1
--------------
====9
81
9
next=send(None)
send() 函数直接修改生成器返回值。即yield表达式的返回值。
此外,生成器还提供了如下两个常用方法:
1.close():该方法用于停止生成器。
程序就不能再去获取生成器的下一个值,否则就会引发异常。
2.throw():该方法用于在生成器内部(yield 语句内)引发一个异常。
在程序调用生成器的 throw() 方法引发异常之后,程序就会在 yield 语句中引发该异常。