面向对象编程基础:继承与遮蔽(Python)
级别:初学者
我正在学习面向对象编程的基础知识。这个代码的目的是展示方法是如何在类之间传递的。所以当我调用 UG.say(person, 'but i like')
时,say
方法会被指示去调用 MITPerson
类。因为 MITPerson
类里没有 say
方法,所以它会把这个请求传递给 Person
类。我觉得这个代码没有问题,因为它是课堂讲解的一部分(见下面的来源)。我觉得是我在运行代码时漏掉了什么,但不太确定是什么。我认为错误信息中提到的 UG 实例
作为第一个参数是指 self
,但原则上这个参数是不需要提供的,对吗?有没有什么提示?
class Person(object):
def __init__(self, family_name, first_name):
self.family_name = family_name
self.first_name = first_name
def familyName(self):
return self.family_name
def firstName(self):
return self.first_name
def say(self,toWhom,something):
return self.first_name + ' ' + self.family_name + ' says to ' + toWhom.firstName() + ' ' + toWhom.familyName() + ': ' + something
class MITPerson(Person):
def __init__(self, familyName, firstName):
Person.__init__(self, familyName, firstName)
class UG(MITPerson):
def __init__(self, familyName, firstName):
MITPerson.__init__(self, familyName, firstName)
self.year = None
def say(self,toWhom,something):
return MITPerson.say(self,toWhom,'Excuse me, but ' + something)
>>> person = Person('Jon', 'Doe')
>>> person_mit = MITPerson('Quin', 'Eil')
>>> ug = UG('Dylan', 'Bob')
>>> UG.say(person, 'but i like')
UG.say(person, 'bla')
**EDIT (for completeness)**: it should say UG.say(person, 'but i like') #the 'bla' creeped in from a previous test
TypeError: unbound method say() must be called with UG instance as first argument (got Person instance instead)
来源:麻省理工学院开放课程 http://ocw.mit.edu 计算机科学与编程导论 2008年秋季
4 个回答
好的,接下来我们来简单讲讲Python中的方法。
当你在一个类里面定义一个函数时:
>>> class MITPerson:
... def say(self):
... print ("I am an MIT person.")
...
>>> class UG(MITPerson):
... def say(self):
... print ("I am an MIT undergrad.")
...
然后通过点号查找这个函数时,你会得到一个叫做“绑定方法”的特殊对象。在这个对象中,第一个参数会自动传递给函数,作为调用它的实例。看看这个:
>>> ug = UG()
>>> ug.say
<bound method UG.say of <__main__.UG object at 0x022359D0>>
不过,由于这个函数也是在类上定义的,你可以通过类来查找它,而不是通过具体的实例。如果这样做,你就不会得到一个绑定方法(显然,因为没有东西可以绑定!)。你会得到原始的函数,这时你需要手动传入你想要调用的实例:
>>> UG.say()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method say() must be called with UG instance as first argument (got nothing instead)
>>> ug.say()
I am an MIT undergrad.
>>> UG.say(ug)
I am an MIT undergrad.
第一次调用失败,因为函数say
期望第一个参数是UG
的实例,但什么都没有传入。第二次调用自动绑定了第一个参数,所以成功了;第三次则手动传入了你想用的实例。第二次和第三次的效果是一样的。
还有一点需要提到的是,似乎say
函数并不需要实际的UG
实例来执行。如果是这样的话,你可以把它注册为“静态方法”,这样就告诉Python不要绑定第一个属性:
>>> class UG:
... @staticmethod
... def say():
... print("foo")
...
>>> ug = UG()
>>> UG.say()
foo
>>> ug.say()
foo
这些回答都挺不错的,但我觉得有一点很重要需要提一下。看这个代码片段(在类 MITPerson
中):
def __init__(self, familyName, firstName):
Person.__init__(self, familyName, firstName)
这段代码完全没用,而且是多余的。当一个子类不需要在父类的方法实现上做任何修改时,子类去“重写”这个方法就毫无意义了……然后又把所有的工作都交给父类,根本没有任何变化。
那些完全没有目的的代码,根本不会带来任何好处(除了可能稍微拖慢整个系统的速度),所以应该毫不犹豫地删除:为什么要留着它呢?!程序中存在的任何没用的代码,都会降低程序的质量:这些无用的“负担”会稀释有用的、有效的代码,让你的程序更难阅读、维护、调试等等。
大多数人似乎在大多数情况下都能直观地理解这一点(所以你不会看到很多代码在“假重写”大多数方法,但在方法体内只是简单地调用父类的实现),除了 __init__
这个方法——很多人似乎在这方面有盲点,无法意识到这个规则同样适用于其他方法。这个盲点可能是因为他们熟悉其他完全不同的语言,在那些语言中,构造函数的规则并不适用,再加上对 __init__
的误解,认为它是构造函数,但实际上它是一个 初始化器。
所以,总结一下:子类应该定义 __init__
只有在它需要在父类的初始化器之前、之后,或者同时在前后做一些其他事情时(很少会想要做一些 替代 的事情,也就是 不 调用父类的初始化器,这种情况几乎不算好做法)。如果子类的 __init__
里只有对父类的 __init__
的调用,并且参数完全相同、顺序也一样,那就把子类的 __init__
从代码中删掉(就像你对待其他类似多余的方法一样)。
你在调用的是类,而不是类的实例。
>>> ug = UG('Dylan', 'Bob')
>>> UG.say(person, 'but i like')
UG.say(person, 'bla')
请调用实例。
>>> ug = UG('Dylan', 'Bob')
>>> ug.say(person, 'but i like')