当前位置: 首页 > 文档资料 > 跟老齐学 Python >

类(4)

优质
小牛编辑
118浏览
2023-12-01

爱人不可虚假,恶要厌恶,善要亲近。爱弟兄,要彼此亲热;恭敬人,要彼此推让。殷勤不可懒惰。要心里火热,常常服侍主。在指望中要有喜乐,在患难中要有忍耐,祷告要恒切。圣徒缺乏要帮补,客要一味地款待。逼迫你们的,要给他们祝福,只要祝福,不可诅咒。与喜乐的人要同乐,与哀哭的人要同哭。要彼此同心,不要志气高大,倒要俯就卑微的人。不要自以为聪明。不要以恶报恶。众人以为美的事,要留心去作。(ROMANS 12:9-17)

类(4)

方法

在类里面,除了属性,就是方法,当然还有注释和文档,但计算机不看它们的,只是人看的。

关于方法,在通常情况下用实例调用。但是,跟方法有关的一些深入的话题,还需要辨析。

绑定方法和非绑定方法

除了特殊方法,类中的其它的普通方法,是经常要用到的,所以,要对这些普通方法进行研究。

>>> class Foo:         #Python 2: class Foo(object):               def bar(self):            print("This is a normal method of class.")        #Python 2 使用print 语句>>> f = Foo()>>> f.bar()This is a normal method of class.

在类Foo中,方法bar()本质上是一个函数,只不过这个函数的第一个参数必须是self——在类中给它另外一个名字,叫“方法”——跟函数相比,没有本质的不同。

当建立了实例之后,用实例开调用这个方法的时候,因为Python解释器把实例已经作为第一参数隐式地传给了该方法,所以就不需要显示地写出self参数了——这个观点反复强调,就是让读者理解self就是实例。

如果要把实例显示地传给方法,可以用下面的方式进行,

>>> Foo.bar(f)This is a normal method of class.

用这种方式,跟证实了前述观点,即实例化之后,self和实例f是相同的。通常,我们在类里面使用self,类外面使用f这个实例,两者有分工。

如果在用类调用方法的时候,不传实例,会怎样?

>>> Foo.bar()

这样执行,不同的Python版本,报错信息有所不同。

Python 2的报错信息是:

Traceback (most recent call last):  File "<pyshell#7>", line 1, in <module>    Foo.bar()TypeError: unbound method bar() must be called with Foo instance as first argument (got nothing instead)

而Python 3下报错信息变成了:

Traceback (most recent call last):  File "<pyshell#7>", line 1, in <module>    Foo.bar()TypeError: bar() missing 1 required positional argument: 'self'

不管你是用什么版本,最好都阅读上述两个报错信息。在Python 2的报错信息中,告诉我们bar()是非绑定方法,它必须以Foo的实例作为第一个参数;Python 3的报错信息也是告诉我们bar()缺少一个必须的参数self,它也是一个实例。所以,不管哪个版本,都要传一个实例。

Python中一切皆对象——又是老生常谈,都是因为此观念之重要。类Foo的方法bar()也是对象——函数对象,那么,我们就可以像这样来获得该对象了。

>>> Foo.bar<unbound method Foo.bar>     #Python 3的显示结果:<function Foo.bar at 0x00000000006AAC80>

在Python 2的结果中,可以很清晰看出,通过类调用的方法对象,是一个非绑定方法——unbound method,又遇到这个词语了。

此外,还可以通过实例来得到该对象。

>>> f.bar<bound method Foo.bar of <__main__.Foo object at 0x02A9BFB0>>

用实例来得到这个方法对象,不管是Python 2还是Python 3,结果是一样的。在这里我们看到的是bound method——绑定方法。

下面就要逼近unbound method和bound method的概念本质了。

在类Foo的属性中,有一个__dict__的特殊属性,前文已经介绍过了。我们使用它,来窥探内部信息。

>>> Foo.__dict__['bar']<function bar at 0x02AA98F0>    #Python 3显示结果:<function Foo.bar at 0x00000000006AAC80>

从这个层面进一步说明bar是一个函数对象。

这个似乎是已经熟悉的了。

下面我们再看一个新的东西——描述器。

什么是描述器?

Python中有几个特殊方法比较特殊,它们分别是__get__()__set__()__delete__(),简单地说,有这些方法的对象叫做描述器。

描述器是属性、实例方法、静态方法、类方法和继承中使用的super的背后实现机制,它在Python中使用广泛。这句话中的那些生疏的名词,以后都会用到,稍安勿躁。

如何使用?

上述三个特殊方法,可以用下面的方式来使用——所谓的描述器协议。

descr.__get__(self, obj, type=None) --> valuedescr.__set__(self, obj, value) --> Nonedescr.__delete__(self, obj) --> None

关于描述器的内容,本节不重点阐述,这里提及它,目的是要解决“绑定方法”和“非绑定方法”的问题,所以,读者如果有兴趣深入了解描述器,可以去google。

弱水三千,只取一瓢。我们在这里也只看__get__()

>>> Foo.__dict__['bar'].__get__(None, Foo)<unbound method Foo.bar>        #Python 3显示结果:<function Foo.bar at 0x00000000006AAC80>

对照描述器协议,我将self赋以了None,其返回结果和Foo.bar的返回结果是一样的。让selfNone的意思就是没有给定的实例,因此该方法被认为非绑定方法(unbound method)。

如果给定一个实例呢?

>>> Foo.__dict__['bar'].__get__(f, Foo)<bound method Foo.bar of <__main__.Foo object at 0x02A9BFB0>>

这时候的显示结果和f.bar是相同的。

综上所述,可以认为:

  • 当通过类来获取方法的时候,得到的是非绑定方法对象。
  • 当通过实例获取方法的时候,得到的是绑定方法对象。

所以,通常用实例调用的方法,都是绑定方法。那么非绑定方法在哪里会用到呢?当学习“继承”相关内容的时候,它会再次登场。

静态方法和类方法

先看下面的代码

#!/usr/bin/env python#coding:utf-8class Foo(object):        #Python 3: class Foo:    one = 0    def __init__(self):        Foo.one = Foo.one + 1def get_class_attr(cls):    return cls.oneif __name__ == "__main__":    f1 = Foo()    print "f1:",Foo.one        #Python 3: print("f1:"+str(Foo.one)),下同,从略    f2 = Foo()    print "f2:",Foo.one    print get_class_attr(Foo)

在上述代码中,有一个函数get_class_attr(),这个函数的参数我用cls,从函数体的代码中看,要求它引用的对象应该具有属性one,这就说明,不是随便一个对象就可以的。恰好,就是这么巧,我在前面定义的类Foo中,就有one这个属性。于是乎,我在调用这个函数的时候,就直接将该类对象传给了它get_class_attr(Foo)

其运行结果如下:

f1: 1f2: 22

在这个程序中,函数get_class_attr()写在了类的外面,但事实上,函数只能调用前面写的那个类对象,因为不是所有对象都有那个特别的属性的。所以,这种写法,使得类和函数的耦合性太强了,不便于以后维护。这种写法是应该避免的。避免的方法就是把函数与类融为一体。于是就有了下面的写法。

#!/usr/bin/env python#coding:utf-8class Foo(object):    #Python 3: class Foo:    one = 0    def __init__(self):        Foo.one = Foo.one + 1    @classmethod    def get_class_attr(cls):        return cls.oneif __name__ == "__main__":    f1 = Foo()    print "f1:",Foo.one    f2 = Foo()    print "f2:",Foo.one    print f1.get_class_attr()    print "f1.one",f1.one    print Foo.get_class_attr()    print "*"* 10    f1.one = 8    Foo.one = 9    print f1.one    print f1.get_class_attr()    print Foo.get_class_attr()

在这个程序中,出现了@classmethod——装饰器——在函数那部分遇到过了。需要注意的是@classmethod所装饰的方法的参数中,第一个参数不是self,这是和我们以前看到的类中的方法是有区别的。这里我使用了参数cls,你用别的也可以,只不过习惯用cls

再看对类的使用过程。先贴出上述程序的执行结果:

f1: 1f2: 22f1.one 22**********899

分别建立两个实例,此后类属性Foo.one的值是2,然后分别通过实例和类来调用get_class_attr()方法(没有显示写cls 参数),结果都相同。

当修改类属性和实例属性,再次通过实例和类调用get_class_attr()方法,得到的依然是类属性的结果。这说明,装饰器@classmethod所装饰的方法,其参数cls引用的对象是类对象Foo

至此,可以下一个定义了。

所谓类方法,就是在类里面定义的方法,该方法由装饰器@classmethod所装饰,其第一个参数cls所引用的是这个类对象,即将类本身作为引用对象传入到此方法中。

理解了类方法之后,用同样的套路理解另外一个方法——静态方法。还是先看代码——一个有待优化的代码。

#!/usr/bin/env python#coding:utf-8T = 1def check_t():    T = 3    return T class Foo(object):        #Python 3: class Foo:    def __init__(self,name):        self.name = name    def get_name(self):        if check_t():            return self.name        else:            return "no person"if __name__ == "__main__":    f = Foo("canglaoshi")    name = f.get_name()    print name        #Python 3: print(name)

先观察上面的程序,发现在类Foo里面使用了外面定义的函数check_t()。这种类和函数的关系,也是由于有密切关系,从而导致程序维护有困难,于是在和前面同样的理由之下,就出现了下面比较便于维护的程序。

#!/usr/bin/env python#coding:utf-8T = 1class Foo(object):        #Python 3: class Foo:    def __init__(self,name):        self.name = name    @staticmethod    def check_t():        T = 1        return T    def get_name(self):        if self.check_t():                   return self.name        else:            return "no person"if __name__ == "__main__":    f = Foo("canglaoshi")    name = f.get_name()    print name        #Python 3: print(name)

经过优化,将原来放在类外面的函数,移动到了类里面,也就是函数check_t()现在位于类Foo的命名空间之内了。但是,不是简单的移动,还要在这个函数的前面加上@staticmethod装饰器,并且要注意的是,虽然这个函数位于类的里面,跟其它的方法不同,它不以self为第一个参数。当使用它的时候,可以通过实例调用,比如self.check_t();也可以通过类调用这个方法,比如Foo.check_t()

从上面的程序可以看出,尽管check_t()位于类的命名空间之内,它却是一个独立的方法,跟类没有什么关系,仅仅是为了免除前面所说的维护上的困难,写在类的作用域内的普通函数罢了。但,它的存在也是有道理的,以上的例子就是典型说明。当然,在类的作用域里面的时候,前面必须要加上一个装饰器@staticmethod。我们将这种方法也给予命名,称之为静态方法。

方法,是类的重要组成部分。本节专门讲述了方法中的几种特殊方法,它们为我们使用类的方法提供了更多便利的工具。但是,类的重要特征之一——继承,还没有亮相。