第1关:类的内建函数
任务描述
对于类、实例和其它对象而言,存在着一些内建函数,这些内建函数无需定义,可直接调用。本关的任务就是让学习者掌握并能使用这些内建函数。
相关知识
issubclass()
issubclass()是一个布尔函数,这个函数用来判断一个类是否是另外一个类的子类或者子孙类。如果给出的子类确实是给出的父类的子类,则返回True,否则返回False。它的语法如下:
issubclass(subclass, parentclass)
parentclass也可以是一个包含若干个父类的元组,只要子类属于元组中某一个父类,则返回True,否则返回False。
isinstance()
isinstance()是一个布尔函数,这个函数用来判断一个对象是否是给定类的实例。若是给定类的实例或是给定类的子类的实例,则返回True,否则返回False。它的语法如下:
isinstance(object,class)
hasattr()、getattr()、setattr()、delattr()
这几个函数可以在各种对象下工作,不限于类和实例。
(1)hasattr(); hasattr()是布尔型的,它用于判断一个对象是否有一个特定的属性,一般用于在调用某个属性前检查属性是否存在。例如:
class testClass(object):
foo = 100
def __init__(self,name):
self.name = name
test = testClass('theName')
print(hasattr(test,'name'))
print(hasattr(testClass,'foo'))
结果为:
True
True
(2)getattr()、setattr(); getattr()是用来获取对象的属性或者方法。若返回的是对象,则返回对象的值,若返回的是对象的方法,则返回方法的内存地址。setattr()是用来给对象的属性赋值。若属性不存在,就先创建属性然后再赋值。例如:
class testClass(object):
foo = 100
def __init__(self,name):
self.name = name
test = testClass('theName')
setattr(test,'fool','200')
setattr(test,'foo','50')
print(getattr(test,'fool'))
print(getattr(test,'foo'))
结果为:
200
50
(3) delattr()。 delattr()是用来从一个对象中删除属性。
dir()
作用在实例上时,显示实例变量、实例所在的类、基类中定义的方法和属性;
作用在类上时,显示类与它的基类的__dict__内容;
作用在模块上时,显示此模块的__dict__内容;
dir()不带参数时,显示调用者的局部变量。
例如:
class testClass(object):
foo = 100
def __init__(self,name):
self.name = name
test = testClass('theName')
print(dir(test))
结果为:
[‘class’, ‘delattr’, ‘dict’, ‘dir’, ‘doc’, ‘eq’, ‘format’, ‘ge’, ‘getattribute’, ‘gt’, ‘hash’, ‘init’, ‘init_subclass’, ‘le’, ‘lt’, ‘module’, ‘ne’, ‘new’, ‘reduce’, ‘reduce_ex’, ‘repr’, ‘setattr’, ‘sizeof’, ‘str’, ‘subclasshook’, ‘weakref’, ‘foo’, ‘fool’, ‘name’]
super()
super()函数的作用就是找出相应的类的父类,然后调用父类的方法与属性。例如:
class parentClass:
def __init__(self):
self.name = "parent"
def tell(self):
print("this is parentClass")
class subClass(parentClass):
def tell(self):
print("this is subClass")
parentClass.tell(self)
sc = subClass()
sc.tell()
结果为:
this is subClass
this is parentClass
在这个示例中,我们在调用父类的方法时是通过非绑定的方式调用,这样的调用方式增大了代码的耦合性。如果我们要修改subClass的父类,则在子类的调用中也要修改相应代码。如果代码量庞大的话,这个工作几乎很难完成,所以我们采用super方法。例如:
class parentClass:
def __init__(self):
self.name = "parent"
def tell(self):
print("this is parentClass")
class subClass(parentClass):
def tell(self):
print("this is subClass")
super(subClass,self).tell()
sc = subClass()
sc.tell()
结果为:
this is subClass
this is parentClass
上面的例子中,我们将寻找父类的任务交给子类,这样既可保证代码的安全,修改起来也很方便。
vars()
vars()是用来返回对象的属性及其值的一个字典。如果没有给出对象,则返回当前调用位置的属性和属性值。
如果您想了解更多类的内建函数的相关知识,请参考:[美] Wesley J.Chun 著《 Python 核心编程》附录 B.13。
编程要求
本关的测试文件中给出了两个类:parentClass与subClass。subClass为parentClass的子类,parentClass类中存在属性name与方法tell(),subClass类中存在方法tell()。
本关的编程任务是补全specialmethod.py文件中的代码,具体要求如下:
填入判断subClass是否为parentClass的子类的代码,并输出结果;
填入判断sc是否为subClass实例的代码,并输出结果;
填入判断实例sc是否包含一个属性为name的代码,并输出结果;
填入将sc的属性name的值设置为subclass的代码;
填入获取sc的属性name的值的代码,并输出结果;
填入调用subClass的父类的tell()方法的代码。
本关涉及的代码文件src/step1/specialmethod.py的代码框架如下:
import specialmethodtest
sc = specialmethodtest.subClass()
# 请在下面填入判断subClass是否为parentClass的子类的代码,并输出结果
########## Begin ##########
########## End ##########
# 请在下面填入判断sc是否为subClass实例的代码,并输出结果
########## Begin ##########
########## End ##########
# 请在下面填入判断实例sc是否包含一个属性为name的代码,并输出结果
########## Begin ##########
########## End ##########
# 请在下面填入将sc的属性name的值设置为subclass的代码
########## Begin ##########
########## End ##########
# 请在下面填入获取sc的属性name的值的代码,并输出结果
########## Begin ##########
########## End ##########
# 请在下面填入调用subClass的父类的tell()方法的代码
########## Begin ##########
########## End ##########
测试说明
本文的测试文件是specialmethod.py,具体测试过程如下:
平台自动编译生成specialmethod.exe;
平台运行specialmethod.exe,并以标准输入方式提供测试输入;
平台获取specialmethod.exe输出,并将其输出与预期输出对比。如果一致则测试通过,否则测试失败。
以下是平台对src/step1/specialmethod.py的样例测试集:
预期输入:
无
预期输出:
True
True
True
subclass
this is parentClass
开始你的任务吧,祝你成功!
参考代码:
import specialmethodtest
sc = specialmethodtest.subClass()
# 请在下面填入判断subClass是否为parentClass的子类的代码,并输出结果
########## Begin ##########
print(issubclass(specialmethodtest.subClass,specialmethodtest.parentClass))
########## End ##########
# 请在下面填入判断sc是否为subClass实例的代码,并输出结果
########## Begin ##########
print(isinstance(sc,specialmethodtest.subClass))
########## End ##########
# 请在下面填入判断实例sc是否包含一个属性为name的代码,并输出结果
########## Begin ##########
print(hasattr(sc,'name'))
########## End ##########
# 请在下面填入将sc的属性name的值设置为subclass的代码
########## Begin ##########
setattr(sc,'name','subclass')
########## End ##########
# 请在下面填入获取sc的属性name的值的代码,并输出结果
########## Begin ##########
print(getattr(sc,'name'))
########## End ##########
# 请在下面填入调用subClass的父类的tell()方法的代码
########## Begin ##########
specialmethodtest.parentClass.tell(sc)
########## End ##########
第2关:类的私有化
任务描述
在默认的情况下,Python 中的属性都是公开的(public),这就意味着此类所在的模块和导入了这个类的模块都可以访问到这个类中的属性和方法。但有时我们不希望外界直接访问某方法或属性,此时我们可以将这个方法和属性私有化。本关的任务就是让学习者掌握类的私有化。
相关知识
双下划线
在 Python 中,可以在属性或方法前添加双下划线将其变为私有。在这种方法下,要调用私有属性就在名字前加上单下划线和类名。利用这种调用方法,就可以很好地避免当子类变量名与父类变量名相同时覆盖父类的变量。例如:
class privatization(object):
def __init__(self,var):
self._var = var
self.__var = var
self.__var__ = var
self.varation = var
pr = privatization(2)
print(pr._var)
print(pr.__var__)
print(pr.varation)
print(pr._privatization__var)
结果为:
2
2
2
2
单下划线
在一个模块中以单下划线开头的变量和函数被默认当做内部函数。当我们使用from module import *来导入模块时,这些不会被导入。但如果使用import module来导入整个模块,这部分还是会被导入,那么就可以用module.var来访问。
在 Python 中,以双下划线开头和双下划线结尾的是一些特殊的方法。比如__init__()、del()等。Python 官方建议不要以这样的方式来定义自己的变量或函数。
如果您想了解更多类的私有化的相关知识,请参考:[美] Wesley J.Chun 著《 Python 核心编程》第十三章。
编程要求
在本关的Bagtest.py文件中,分别定义了两个私有变量:__price与_price。本关的编程任务是补全Bag.py文件中的调用Bagtest.py文件中私有变量的代码,具体要求如下:
填入输出Bag类中变量__price的代码;
填入输出Bag类中变量_price的代码。
本关涉及的代码文件src/step2/Bag.py的代码框架如下:
import Bagtest
price = int(input())
bag = Bagtest.Bag(price)
# 请在下面填入输出Bag类中变量__price的代码
########## Begin ##########
########## End ##########
# 请在下面填入输出Bag类中变量_price的代码
########## Begin ##########
########## End ##########
测试说明
本文的测试文件是Bag.py,具体测试过程如下:
平台自动编译生成Bag.exe;
平台运行Bag.exe,并以标准输入方式提供测试输入;
平台获取Bag.exe输出,并将其输出与预期输出对比。如果一致则测试通过,否则测试失败。
以下是平台对src/step2/Bag.py的样例测试集:
预期输入:
56
预期输出:
56
56
预期输入:
76
预期输出:
76
76
开始你的任务吧,祝你成功!
参考代码:
import Bagtest
price = int(input())
bag = Bagtest.Bag(price)
# 请在下面填入输出Bag类中变量__price的代码
########## Begin ##########
print(bag._Bag__price)
########## End ##########
# 请在下面填入输出Bag类中变量_price的代码
########## Begin ##########
print(bag._price)
########## End ##########
第3关:授权
任务描述
包装在 Python 中经常用到,包装就是把已存在的程序重新打包,使这个程序更加适合当前应用环境。而授权是包装特有的一个属性,通过授权,可以使当前类调用传入对象已存在的属性。本关的任务就是让学习者掌握 Python 中的包装与授权。
####相关知识
#####包装
包装就是对已存在对象的属性功能进行调整,删除不需要的、添加或是修改已存在的功能,以达到自己所理想的规格,并装换成另外一种更适合当前使用场合的对外接口。包装包括定义一个类,它的实例拥有标准类型的核心行为。
例如,我们需要处理一个数据,处理这个数据需要一系列的步骤,我们可以将这些步骤写到一个类里面。每当应用于不同的场景时,就将各种适合于此场景的方法包装成类,且原对象的属性和方法依然可以调用。
或许我们会觉得通过继承的方式可以达到这个目的,将父类中的成员全部继承,然后再对方法进行覆盖重写,还可以添加自己想要的属性和方法。但是在 Python2.2之前,基本数据类型不属于类,这样基本数据类型就不能被继承,也不能进行包装。以下是包装实例:
class Wrap(object):
def __init__(self,obj): # 声明了构造函数,模拟要包装对象的构造方法
self.__obj = obj # __obj就是这个被包装对象的核心
def get(self): # 获取__obj对象
return self.__obj
def __repr__(self):
return 'self.__obj'
def __str__(self): # 转换__obj对象
return str(self.__obj)
def __getattr__(self,thelist):
return getattr(self.__obj,thelist)
授权
授权是包装的一个特性,采用已存在的功能达到最大限度的代码重用。在包装中我们可以新建、修改或删除已有的功能,授权的过程就是将更新的功能交由新类来处理,已存在的功能就授权给对象的默认属性。
要实现授权,一定要覆盖__getattr__()方法。在代码里包含一个对getattr()内建函数的调用,调用getattr()得到默认对象的属性(数据属性或者方法)并返回它,以便于访问或者调用。
当引用一个属性时,解释器首先会在局部名称空间中查找那个名字,比如一个自定义的方法或局部实例属性。如果没有在局部字典中找到,则搜索类名称空间,以防一个类属性被访问。最后,如果两类搜索都失败了,搜索则对原对象开始授权请求,此时__getattr__()会被调用。
如果您想了解更多类授权的相关知识,请参考:[美] Wesley J.Chun 《 Python 核心编程》第十三章。
编程要求
在本关给出的文件中,定义了一个包装类WrapClass。本关的编程任务是补全WrapClass.py文件中实现授权和调用类中方法的代码,具体要求如下:
填入重写__getattr__()实现授权的代码;
填入实例化类,通过对象调用thelist,并输出thelist第三个元素的代码。
本关涉及的代码文件src/step3/WrapClass.py的代码框架如下:
class WrapClass(object):
def __init__(self,obj):
self.__obj = obj
def get(self):
return self.__obj
def __repr__(self):
return 'self.__obj'
def __str__(self):
return str(self.__obj)
# 请在下面填入重写__getattr__()实现授权的代码
########## Begin ##########
########## End ##########
thelist = []
inputlist = input()
for i in inputlist.split(','):
result = i
thelist.append(result)
# 请在下面填入实例化类,并通过对象调用thelist,并输出thelist第三个元素的代码
########## Begin ##########
########## End ##########
测试说明
本文的测试文件是WrapClass.py,具体测试过程如下:
平台自动编译生成WrapClass.exe;
平台运行WrapClass.exe,并以标准输入方式提供测试输入;
平台获取WrapClass.exe输出,并将其输出与预期输出对比。如果一致则测试通过,否则测试失败。
以下是平台对src/step3/WrapClass.py的样例测试集:
预期输入:
zhangsan,lisi,wangwu,zhaoliu
预期输出:
wangwu
预期输入:
23,67,98,100,120,234
预期输出:
98
开始你的任务吧,祝你成功!
参考代码:
class WrapClass(object):
def __init__(self,obj):
self.__obj = obj
def get(self):
return self.__obj
def __repr__(self):
return 'self.__obj'
def __str__(self):
return str(self.__obj)
# 请在下面填入重写__getattr__()实现授权的代码
########## Begin ##########
def __getattr__(self,thelist):
return getattr(self.__obj,thelist)
########## End ##########
thelist = []
inputlist = input()
for i in inputlist.split(','):
result = i
thelist.append(result)
# 请在下面填入实例化类,并通过对象调用thelist,并输出thelist第三个元素的代码
########## Begin ##########
temp=WrapClass(thelist)
temp_list=temp.get()
print(temp_list[2])
########## End ##########
第4关:对象的销毁
任务描述
对象销毁也称垃圾回收,很多语言都有自己的垃圾回收机制。Python 的垃圾回收机制使用了引用计数这一机制来追踪内存中的对象。本关的任务让学习者掌握 Python 的对象销毁机制。
相关知识
增加引用计数
在 Python 中,当一个对象被创建时,就自动地创建了一个引用计数器。当引用计数加1时,增加对这个对象的引用,引用计数器也依次增加。例如:
x = 12
y = x
z = y
在这个例子中,语句x = 12创建了对象12并将这个对象赋值给了x。此时12的引用计数为1,语句y = x创建了一个指向对象12的别名y,计数为2,在语句z = y中又创建了一个别名,所以此时对象12的引用计数为3。
对象的引用计数在下列情况下增加:
对象被创建时;
创建了另一个别名时;
被作为参数传递给函数时;
成为容器对象的一个元素时。
减少引用计数
当别名被重新赋值时,源对象的引用计数减1。例如:
var1 = “we”
var2 = var1
var1 = 12
在这个例子中,对象"we"赋值给了var1,引用为1,语句var2 = var1将对象的值又赋给了var2,引用为2。但在语句var1 = 12中,将对象12赋值给了var1,此时对象"we"的引用减1,12的引用加1。
对象的引用计数在下列情况下减少:
一个本地引用离开了其作用范围时,比如函数结束;
对象别名被销毁时;
对象本身被销毁时。
对象销毁
当对象的引用计数变为0时,它被垃圾回收。Python 中的垃圾回收机制可以处理两种情况,一种是引用为0,另一种是循环引用。循环引用是指两个对象互相引用,且都没有外部的对象对它们进行引用。例如:
class delObject:
def __init__(self):
self.var = 100
def __del__(self):
class_name = self.__class__.__name__
print("对象%s销毁" %class_name)
do1 = delObject()
do2 = do1
do3 = do1
print(id(do1))
print(id(do2))
print(id(do3))
del(do1)
del(do2)
del(do3)
结果为:
2176419869752
2176419869752
2176419869752
对象delObject销毁
在这个例子中,del()为一个析构函数。当删除对象时,会调用本身的函数,在对象删除完毕时也会再次调用这个函数。
如果您想了解更多对象的销毁的相关知识,请参考:[美] Wesley J.Chun 著《 Python 核心编程》第十三章。
编程要求
在本关的测试文件中,定义了一个类:delObject。在类中定义了一个析构方法__del__()方法,在对象的引用都被销毁时,调用此函数。本关的编程任务是补全delObject.py文件中的对象定义与销毁部分。具体要求如下:
声明类delObject的实例,并将其引用赋给其它别名,然后调用del方法将所有声明的对象销毁。
本关涉及的代码文件src/step4/delObject的代码框架如下:
import delObject
# 请在下面声明类delObject的实例,并将其引用赋给其它别名,然后调用del方法将其销毁
########## Begin ##########
########## End ##########
测试说明
本文的测试文件是delObject.py,具体测试过程如下:
平台自动编译生成delObject.exe;
平台运行delObject.exe,并以标准输入方式提供测试输入;
平台获取delObject.exe输出,并将其输出与预期输出对比。如果一致则测试通过,否则测试失败。
以下是平台对src/step4/delObject.py的样例测试集:
预期输入:
无
预期输出:
对象delObject销毁
开始你的任务吧,祝你成功!
人生的坚持,学习的坚持,尽管遭遇困苦,但我们却能在一次次的苦难中成长,重新爬起来。这就是奋斗的人生,就是挥洒自己青春和汗水的有意义、有价值的生活。
参考代码:
import delObjecttest
# 请在下面声明类delObject的实例,并将其引用赋给其它别名,然后调用del方法将其销毁
########## Begin ##########
temp=delObjecttest.delObject()
temp2=temp
del(temp)
########## End ##########