本次课的内容非常有趣,第一个Special Object Methods讲的是python中的基础的Object类中的一些固定方法,比如__str__方法等,掌握这些方法有助于我们更好地构建类和在自建的类中重写这些方法;第二个讲的是一些递归对象,比如我们以前经常提到的树,但是这节课新教授了链表,非常有趣。
课程ppt如下:
Special Object Methods:Special Object Methods
Recursive Objects: Recursive Objects: Tree + LinkedList
一、lecture
1.1 Special Object Methods
首先我们熟悉一个基本情况,python中的数据结构其实本质上也是封装好的类,我们可以看到下面这张图就可以了解到他们的继承关系,都是统一继承与Object类。
图1
然后是使用的小技巧,使用dir()函数就可以获取当前类的全部方法,如下所示:
可以看到list不仅包含了许多我们经常使用的方法如pop()、append()等,还包含了许多包含双下划线的私有属性,这些正是我们这节课所要学习的内容,利用这个命令可以直观的看到一些方法,便于我们学习。
然后我们先学习第一个__str__方法,该方法的作用是返回对象的字符串表示形式。具体的使用可以看下面的例子:
from fractions import Fraction
one_third = 1/3
one_half = Fraction(1, 2)
a = [1, 2]
float.__str__(one_third) # '0.3333333333333333'
Fraction.__str__(one_half) # '1/2'
list.__str__(a) # '[1, 2]'
看到这些例子就可以发现这种方法首先将难以理解的类值返回成了容易理解的,字符串的形式。课堂上也讲了,这种方法经常使用与print语句和转化字符串形式的str()方法。那么这是不是意味着我们理解了这个方法就可以重写自建类的该方法使之输出我们想要的字符串呢?我认为这是完全可行的!下面的的代码就展示了这种重写方法,注意最后三种调用该属性的方式是一样的。
class Lamb:
species_name = "Lamb"
scientific_name = "Ovis aries"
def __init__(self, name):
self.name = name
def __str__(self):
return "🐑 : " + self.name
lil = Lamb("Lil lamb")
str(lil) # 🐑 : Lil lamb
print(lil) # 🐑 : Lil lamb
Lamb.__str__(lil) # 🐑 : Lil lamb
其次我们介绍下一种方法,__repr__方法。这种方法的意思是返回一个与对象具有相同值的字符串,这话听起来有点绕,我们还是用实例代码展示一下。
from fractions import Fraction
one_third = 1/3
one_half = Fraction(1, 2)
repr(one_third) # '0.3333333333333333'
repr(one_half) # 'Fraction(1, 2)'
有一说一看起来__str__方法和__repr__方法比较类似,但是其实__repr__方法返回的是一个对象的字符串表示,一个可以通常 eval 会将其转换回该对象的字符串;而__str__方法则可以由用户定义返回任意的字符串;更加详细的探讨见下:
python - What is the difference between __str__ and __repr__? - Stack Overflow
最后一项是检查该类中是否存在这一方法使用hasattr(object, name),具体的使用见下
hasattr(list, 'sort') #True
hasattr(list, 'hello') #False
1.2 递归类-树
之前我们在数据抽象那一节中已经了构建树的抽象了,我们这里直接将之前实现的改动一下设计成类的形式:
class Tree:
def __init__(self, label, branches=[]):
self.label = label
self.branches = list(branches)
def is_leaf(self):
return not self.branches
可以看到看到我们的树的数据结构设计非常简单,只是简单做了初始化和是否为叶子结点的判断,这都很好理解。然后我们简单看一下使用函数结构和类结构的简单对比,其实本质上都是一样的,只是设计思想的区别。
图3
1.3 递归类-链表
最后一个是链表的设计,链表是一种非常经典的数据结构,也是一种CS trade-off的体现,在插入和删除操作上时间复杂度低于线性表,代价是多花费了存储空间,接下来我们看一下链表的定义。
图4:链表定义(节点储存了本节点内容和下一节点的引用)
那么既然有了链表的定义,建议在61A Code这个里面敲打一下代码并测试一下
class Link:
empty = ()
def __init__(self, label, next = empty):
self.label = label
self.next = next
有了链表的定义我们就可以做一些复杂的操作,如增加节点等,那么增加节点并排序的代码如下:
def add(ordered_list, new_val):
"""Add NEW_VAL to ORDERED_LIST, returning modified ORDERED_LIST.
>>> s = Link(1, Link(3, Link(5)))
>>> add(s, 0)
Link(0, Link(1, Link(3, Link(5))))
>>> add(s, 3)
Link(0, Link(1, Link(3, Link(5))))
>>> add(s, 4)
Link(0, Link(1, Link(3, Link(4, Link(5)))))
>>> add(s, 6)
Link(0, Link(1, Link(3, Link(4, Link(5, Link(6))))))
"""
if new_val < ordered_list.first:
original_first = ordered_list.first
ordered_list.first = new_val
ordered_list.rest = Link(original_first, ordered_list.rest)
elif new_val > ordered_list.first and ordered_list.rest is Link.empty:
ordered_list.rest = Link(new_val)
elif new_val > ordered_list.first:
add(ordered_list.rest, new_val)
return ordered_list
链表是一种非常好用的数据结构,并且其很多操作也可以使用递归来完成,还是等到课下完成这一部分的作业再来继续深入的分析内容。