Python面向对象

来源:互联网 发布:手机相片分类软件 编辑:程序博客网 时间:2024/05/21 17:31

说实话,不知道取个什么名字好些,这篇博客的名字太大了,因为接下来我并不是讲解太多面向对象东西。还有就是我在网上看到了好几篇很基础也很全面的面向对象博客,所以我这里并不想要造轮子。只是我觉得《Python3程序开发指南》这本书上关于面向对象的讲解还是挺值得回味的,也并不是那么好理解,至少我觉得译者的翻译不是那么直白易懂,所以这里做一些笔记和要点,也是对网上关于面向对象讲解的补充吧。
首先,为什么要学习面向对象, 面向对象与面向过程或者函数式编程之间有什么区别?这里推荐一篇博客:Python面向对象(初级篇)。之前有学习过Java的人对面向对象的三大特征肯定不陌生:封装、继承、多态,其实Python里面也都是一样的。
封装:将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对对象内部信息的操作和访问;
继承:为了实现某些方法的复用而设计的;
多态:在继承的前提下,拓展自己的方法。

在Java中我们通过构造方法来进行实例化某个对象,Python中通过object的__init__()方法对不对呢?错!Python通过内置的__new__()方法创建对象,然后通过__init__()方法进行初始化。但是单部分情况下,__new__()方法都能满足需求,并不需要我们在子类中进行重写。然后就是对于重写问题,如果在子类中我们已经重写了一个__init__()方法,那么Python在执行时就不会前往父类中进行查找了,反之,则不断向上追溯,知道找到该方法为止。

Python中的“==”

我觉得这个与Java中equal方法的重写是一样的,举个例子:

   class Person(object):    def __init__(self, name):        self.name = name    if __name__ == '__main__':    person1 = Person("Adam")    person2 = Person("Adam")    print(person1 == person2) 

创建两个完全相同的对象,但是最后使用== 却发现返回的是False,这个道理其实与Java中是一样的,每一个对象的创建均对应一个独一无二的hashcode,但是这种行为在Java中可以通过重写equals和hashCode方法来完成,在Python中通过重写object的__eq__() 方法完成:

def __eq__(self, other):    return self.name == other.name

然后就会发现前面的返回结果为True,但是这样会有问题的,如下:

class Person(object):    def __init__(self, name):        self.name = name    def __eq__(self, other):        return self.name == other.nameclass Animal(object):    def __init__(self, name):        self.name = nameif __name__ == '__main__':    person1 = Person("Adam")    person2 = Person("Adam")    animal1 = Animal("Adam")    print(person1 == person2)# return True    print(person1 == animal1)# return True

那这很显然是我们不想看到的,因为Animal、Person就不是一个类型,所以这里还要做一个判断,__eq__() 方法改写如下:

    def __eq__(self, other):        assert isinstance(other, Person), "{0}需要是Person类或它的父类".format(other.__class__.__name__)        return self.name == other.name

这样就能控制输入的other参数了。

__str____repr__ 之间的区别

这里问题我之前一直都想不明白,这里在StackOverflow看到的答案,大致解释一下吧:

class Sic(object):pass>>> sic = Sic()>>> print(str(sic))<__main__.Sic object at 0x1056b0cc0>>>> print(repr(sic))<__main__.Sic object at 0x1056b0cc0>

可以看到它们的输出结果是一样的,但是这里我只重写__repr__ 后:

>>> class Sic(object):    def __repr__(self): return "hello">>> sic = Sic()>>> print(repr(sic))hello>>> print(str(sic))hello

这里__str____repr__ 输出结果还是没怎么变化,那么我如果只是重写__str__ 呢?

>>> class Sic(object):    def __str__(self): return "output">>> sic = Sic()>>> print(str(sic))output>>> print(repr(sic))<__main__.Sic object at 0x1056b0e80>

这里可以看到输出结果与前面不一样了。我觉得__repr__ 与Java中的反射很像,它返回的结果可以作为eval 方法的参数,返回一个与 __repr__ 相同的实例,这里可以看如下这个例子:

class Sic(object):    def __init__(self, name):        self.name = name    def __repr__(self):        return "Sic({0.name!r})".format(self)s = Sic("Crabime")print(repr(s)) #Sic('Crabime')q = eval(repr(s)) #返回的就是s对象print(repr(q)) #Sic('Crabime')

那么在反射的这个例子中,如果你的__repr__ 方法不这么写,输出结果肯定就会报错,你也可以自己试试,道理与Java中的反射是一样的,所以__repr__ 方法的重写一定要对类中的所有参数都很清楚,不能随便写一个字符串,这样Python在“反射“时就会出错。但是你__str__ 方法的重写就无所谓了,这个用一种友好的方式去表示当前这个类而已。

Python中的浅克隆与深克隆

这里是Python copy模块的文档,文档说:浅克隆与深克隆之间的差别主要在于是否包含符合对象,说白了就是是否包含其它的类,如果包涵复合对象,那么克隆如下:

  1. 浅克隆:克隆的对象只是指向原始对象的一个引用;
  2. 深克隆:构建一个新的复合对象,该对象的值与原是对象的值严格相同

下面是一个例子:

class Road(object):    def __init__(self, length):        self.__length = length    @property    def length(self):        return self.__length    @length.setter    def length(self, length):        self.__length = length    @length.deleter    def length(self):        del self.__length    def __str__(self):        return "I'm " + str(self.length) + "km long"if __name__ == '__main__':    road = Road(100)    print(road.length)    road.length = 10    print(road.length)    print(road)    # print(road, road.__dict__)----------import copyfrom Oop.Road import Roadclass Sic(object):    def __init__(self, name, road):        self.name = name        self.road = roadroad1 = Road(100)q = Sic("Crabime", road1)# p = copy.copy(q) #get shallow copyp = copy.deepcopy(q)print("before deep copy : ", p.road)road1.length = 10print("after deep copy : ", p.road)

这里先执行前面的浅克隆所对应的注释代码,发现下面对road1的改变并不会受原是值的影响,但是执行完深克隆后,发现对road1的改变并没有产生效果,length仍然为原始100。

Python中的attributeproperty

相信习惯了Java编程的人肯定看到python属性那一章最开始是懵的,Python中到底什么是属性?什么是方法?property与attribute到底有什么区别?这两者竟然可以混在一起!其实Python中property就是一种特殊的attribute。下面先给出一个简单的例子:

class Animal(object):    def __init__(self, habit, name):        self.habit = habit        self.name = name        self.__prey = None #定义一个私有属性prey    #这里定义私有prey的getter方法    @property    def prey(self):        print("prey的getter方法被调用")        return self.__prey    @prey.setter    def prey(self, prey):        print("prey的setter方法被调用")        self.__prey = preyif __name__ == '__main__':    animal1 = Animal("sleep", "rabit")    animal1.prey = "vegitable" #自动调用prey setter方法    print(animal1.prey) #自动调用prey getter方法

Python中property有三种方法,setter getter deleter ,delete方法前面已经有了,所以你从这里可以看出property其实就是一种attribute,有些人可能看着我在__init__ 中已经定义了__prey 这个私有属性,这个不能算,但是你去掉那一行程序不会出现任何错误,只是会出现一个警告,属性最好定义在__init__ 方法中。

attribute与property之间又有什么区别呢?
__get__ __set__ __delete__ 方法的就是property,如果没有那就是attribute。这里有是介绍Python数据模型的官方文档:Implementing Descriptors

Python中__del__() 特殊方法

书上还又一个特意提醒我们的地方,__del__() 方法重写问题,其实说实话这个方法本身也可能起不到什么作用。学过数据结构的人或者对JVM虚拟机回收对象有一点了解的人可能知道,回收使用有向图算法,在回收一个对象时,我们其实就是看所有指向该对象的引用是否都不存在,如果没有一个其他类指向该对象,那么它就要被回收。同样Python的__del__() 方法也是一样,如果调用一个类的__del__() 方法去删除某一个对象,那么只会让该对象的引用在Python内部的计数器上减少1,只要这个计数器不为0,那么这个对象就不会被回收,所以你主动调__del__() 方法去删除某一个对象其实并不一定会及时的起到作用,因此这个方法最好不要重写。然后就是:该方法不适合用于关闭文件、断开网络连接、断开数据库连接,它并不是你能控制的。如果想释放资源,可以使用try…finally语句块,在finally中做这些事情。

摒弃不想继承的方法

面向对象多态大家应该都知道,但是Python中竟然在这个环节有一点不同,它允许子类有选择的继承父类的方法,也就是说我们可以摒弃父类中不想要的部分方法。学习一门面向对象语言的人可能都觉得这个很吃惊,这个很明显就是颠覆了我们对多态的理解,但是Python确实是这样做了。下面是一个简单的例子去放弃不想继承的方法:

    #接着上面的Animal类中添加该__add__方法    def __add__(self, other):        raise NotImplementedError() #其实说白了就是抛出一个异常嘛

然后在__main__ 方法中调该方法的时候发现无非就是抛出一个异常,所以有点不能理解,这种方法感觉好自欺欺人,但是你不得不接受这样的一个事实:NotImplementedError 表示的就是摒弃的继承方法这样一个事实。

Python中的* 与 **

相信应该有不少刚学习Python的人在看到这两个符号时有点不能理解,用在方法参数中都是干什么的呢?我看的时候也是这样,这里是Python关于该identifier的官方文档,说实话感觉看完了还是没有让我理解清楚,然后这里是我在stackoverflow上看到James Polley的解释:

If the form “*identifier” is present, it is initialized to a tuple receiving any excess positional parameters, defaulting to the empty tuple. If the form “**identifier” is present, it is initialized to a new dictionary receiving any excess keyword arguments, defaulting to a new empty dictionary.
也就是说当使用*identifier 时,它表示某一个方法调用时传入的参数列表中操过该identifier定义位置的后续所有参数,是一个tuple类型,因此不包含dict类型;**identifier 也是一样的用法,只不过它是一个dictionary类型

看了上面的解释估计还是有很多不明白,这里我拿了它的一个demo:

>>> def func(a, *args, **kwargs):        print(("a {0}, args {1}, kwargs {2}").format(a, args, kwargs))    >>> func('one', 'two', 'three', four='four', five='five')    a one, args ('two', 'three'), kwargs {'four': 'four', 'five': 'five'} #输出

这里a参数的位置为1,所以func1() 被调用时第一个参数即为a的值,*args 定义为索引为2的参数,并且它又是一个tuple类型,那么我们从第二个位置开始一次向后数,直到four='four' 这个dict类型数据位置,它前面的参数均用 *args 形参表示,**kwargs 同理,只不过它表示的是传入的所有dict类型。

dict.pop(a, b) 表示什么意思?

这里是dict的pop函数官方文档,一般我们知道tuple.pop(a) 这种传入一个参数的pop 方法的含义,就是从集合中删除该数据并在结果中输出该值,通常这个a参数如果不在这个集合中就会返回一个KeyError异常,传入b参数表示删除a参数,如果a参数不存在则返回默认b参数。但是看到书上有这样一个片段:
self.__data.pop(tuple(coordinate), None) #self.__data是一个dict类型
也就是表示从data中删除这个coordinate坐标,如果没有这个坐标,程序继续进行,返回None不删除任何数据。
还有关于上面重点部分,这里给出一个例子:

a = {"one":4, "two":-1, "three":10, "four":2.1}print(a.pop("one", "three"))print(a.pop("one", "three")) #one已经不存在了,返回"three"print(a)
1 0