面向对象编程基础:继承与遮蔽(Python)

1 投票
4 回答
1679 浏览
提问于 2025-04-16 04:36

级别:初学者

我正在学习面向对象编程的基础知识。这个代码的目的是展示方法是如何在类之间传递的。所以当我调用 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 个回答

2

好的,接下来我们来简单讲讲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
3

这些回答都挺不错的,但我觉得有一点很重要需要提一下。看这个代码片段(在类 MITPerson 中):

def __init__(self, familyName, firstName):
    Person.__init__(self, familyName, firstName)

这段代码完全没用,而且是多余的。当一个子类不需要在父类的方法实现上做任何修改时,子类去“重写”这个方法就毫无意义了……然后又把所有的工作都交给父类,根本没有任何变化。

那些完全没有目的的代码,根本不会带来任何好处(除了可能稍微拖慢整个系统的速度),所以应该毫不犹豫地删除:为什么要留着它呢?!程序中存在的任何没用的代码,都会降低程序的质量:这些无用的“负担”会稀释有用的、有效的代码,让你的程序更难阅读、维护、调试等等。

大多数人似乎在大多数情况下都能直观地理解这一点(所以你不会看到很多代码在“假重写”大多数方法,但在方法体内只是简单地调用父类的实现),除了 __init__ 这个方法——很多人似乎在这方面有盲点,无法意识到这个规则同样适用于其他方法。这个盲点可能是因为他们熟悉其他完全不同的语言,在那些语言中,构造函数的规则并不适用,再加上对 __init__ 的误解,认为它是构造函数,但实际上它是一个 初始化器

所以,总结一下:子类应该定义 __init__ 只有在它需要在父类的初始化器之前、之后,或者同时在前后做一些其他事情时(很少会想要做一些 替代 的事情,也就是 调用父类的初始化器,这种情况几乎不算好做法)。如果子类的 __init__ 里只有对父类的 __init__ 的调用,并且参数完全相同、顺序也一样,那就把子类的 __init__ 从代码中删掉(就像你对待其他类似多余的方法一样)。

4

你在调用的是类,而不是类的实例。

>>> ug = UG('Dylan', 'Bob')
>>> UG.say(person, 'but i like')


UG.say(person, 'bla')

请调用实例。

>>> ug = UG('Dylan', 'Bob')
>>> ug.say(person, 'but i like')

撰写回答