Python3学习笔记——创建自定义对象

本文介绍了Python3中的面向对象编程概念,包括类、对象、多态、封装和继承。文章强调了抽象和封装在编程中的重要性,解释了如何创建和使用类,以及如何通过继承来扩展类的功能。还讨论了抽象基类和接口内省的概念,提醒读者谨慎使用继承,特别是多重继承,以避免潜在的bug。
摘要由CSDN通过智能技术生成

Python3学习笔记——创建自定义对象

1.前言——面向对象编程

这些照搬来的概念我个人有点难理解,就尝试先接受再理解。

面向对象是以“对象”为中心思想,将要解决的问题分解为各个对象,对象拥有自己的属性和行为。Python设计之初就是一门面向对象的语言,Python的所有东西都是对象。创建对象类型是Python的核心概念,创建对象类型要学会:如何创建对象,多态,封装,方法,属性,超类和继承。

由此创造的东西是抽象的,而抽象是程序能被人理解的关键所在。计算机喜欢具体的命令,而人通常不是这样。好比有人问路,你会回答”向前走到路口后右转,然后直走从天桥过马路“,但是你不会回答”向前走100步,然后逆时针转身90°,接着直走150步,走上25层阶梯后顺时针转身90°,直走50步后顺时针转身90°,接着走下25层阶梯……“。而抽象就能做到将具体的命令变为抽象的命令,你无需知道中间是如何运作的,也不需要关心。而这些具体的细节,将在其他部分给出。

2.对象魔法

面向对象中,对象大致意味着一系列属性和方法,之所以用对象而不用全局变量或函数,有如下原因:

  • 多态:对不同类型的对象执行相同操作,这些操作“就像被施了魔法”一样可以正常运行。

  • 封装:对外部隐藏有关对象工作原理的细节。

  • 继承:可以从通用类创建出专用类。

2.1.多态(Polymorphism)

简单理解:无需知道变量指向哪种类型,也能够对其执行操作,且操作行为随对象所属类型而异。

如果我收到一个对象,我不知道对象是如何实现的——它可能是众多“形态“中的任意一种,那我只要知道我可以利用这个对象获得我想要的结果即可。

与对象属性相关联的函数成为方法

多态形式多种多样,比如"+"号可以连接字符串,也可以为数字做加法等,其参数可以是任何支持加法的对象。

很多函数和运算符都是多态的,由此编写的程序也可能如此,即使我不是有意为之。

2.2.封装

封装指的是向外部隐藏不必要的细节。这有点多态,即无需知道内部的细节即可使用。它们都是抽象的原则,让你无需关心不必要的细节。

简单来说,我希望每个对象拥有自己的状态,而不是每个人公用一颗心脏。

2.3.继承

如果我已经创建了一个类,而我需要写一个与之类似的类,这个新类可能只是新增了几个方法,那我只需要继承以前写的类,并附上新增的方法即可。而不需要把代码CTRL+C/V上去。

3.类

3.1.类是什么

每一种都对象属于特定类,是类的实例。

注意:英语交流中用复数形式表示类,Python中约定用单数首字母大写表示。

3.2.创建类

>>> class Person:
        def set_name(self,name):
            self.name = name
        def get_name(self):
            return self.name
        def greet(self):
            print("Hello,I am {}.".format(self.name))

class 关键字用于创建一个类,class之后为类的名称并以冒号结尾

>>> class ClassName:
       '''类的帮助信息''''   # 类文档字符串
       class_suite         # 类体

帮助信息可以通过ClassName.__doc__查看

3.3.参数self

参数self代表类的示例,而非类,它指向对象本身。

类的方法与一般的函数只有一个特别的区别:它们必须有一个额外的第一个参数名称。

3.4.隐藏对象

要让方法或属性成为私有的(不能从外部访问),只需要在其名称前面以两个下划线打头即可。

>>> class ClassName:
        def __Secret(self):
            print("Bet you can't see me")

现在不能从外部访问__Secret,但是从内部(ClassName类)可以。

在类定义中,对所有以两个下划线打头的名称都进行转换,即在开头加上一个下划线和一个类名。知道这个以后还是可以从外部访问:

>>> s=ClassName()
>>> s._ClassName__Secret(self)
Bet you can't see me

所以理解为双下划线意味着通过隐形修改名字,虽然无法阻止别人访问私有方法和属性,但是这种方法强烈警告别人不要这么做。如果你既不希望别人访问又不希望修改名字,那么可以用一个下划线打头。虽然这只是约定,但是也有一些作用。比如,from module import *就不会导入以单下划线打头的名称。

3.5.类的命名空间

首先了解类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。比如:

>>> class ClassName:
    number=0
    def init(self)
        ClassName.number+=1
>>> m1=ClassName()
>>> m1.init()
>>> ClassName.number
1
>>> m2=ClassName()
>>> m2.init()
>>> ClassName.number
2

可见,number的值不受实例化是m1或m2.

但是如果给一个实例中的number赋值,那新值就会写入实例中,这个属性遮住了类级的变量。

>>> m1.number='Two'
>>> m1.number
'Two'
>>> m2.nember
2

3.6.指定超类与继承

继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。

子类扩展了超类的定义。指定超类,可在class语句中的类名后加上超类名,并用圆括号括起。

>>> class Person:
    def set_name(self,name):
        self.name = name
    def get_name(self):
        return self.name
    def greet(self):
        print("How are you?")

>>> class Human(Person):
    def greet(self):
        print("Hello,I am a human")

Human类直接继承了Person类的set_name和get_name方法,因此无需重新定义。同时Human类以提供新定义的方式重写了方法greet的定义。

确定一个类是否属于另一个类,可以用内置方法issubclass

>>> issubclass(Human,Person)
True
>>> issubclass(Person,Human)
False

同样也可以适用于判断对象是否是特定类的实例,并且可以判断直接实例和简介实例。

>>> s = Human()
>>> issubclass(s,Human)
True
>>> issubclass(s,Person)
True

利用属性__class__可以得到对象属于哪个类。

>>> s.__class__
<class '__main__.Human'>

如果有多个超类,称为多重继承,但是这会带来很多”并发症“,所以除非万不得已不要使用。使用时务必要注意,如果多个超类以不同的方式实现了同一个方法(即不同超类有同名方法),需要在class语句中谨慎排列这些超类。位于前面的类的方法会覆盖位于后面的类的方法。当出现多个超类的超类相同时,查找特定方法或属性时访问超类的顺序称为方法解析顺序(MRO),它的算法非常复杂,但是效果非常好,知道即可。

>>> class Mankind:
        def greet(self):
            print("Hello,I am a Mankind")
>>> class Man(Person,Mankind):
        pass
>>> sk=Man()
>>> sk.greet()
How are you?
>>> class Lady(Mankind,person):
>>> sl=Lady()
>>> sl.greet()
Hello,I am a Mankind

可见,超类的排序会影响其中的方法调用。

3.7.接口与内省

处理多态对象时,只需要关心接口(协议)——对外暴露的方法和属性。Python不显式地指定对象必须包含哪些方法才能用作参数,而是假定对象能够完成你要求它完成的任务。如果完不成,程序通常失败。通过检查所需方法是否存在,可以实现要求对象遵循特定的接口。

>>> hasattr(s,'get_name')
True

同时还可以检查方法是否可以调用

>>> callable(getattr(s,'get_name',None))
True

方法getattr用于检查属性是否存在,并且可以指定属性不存在时使用默认值,这里指定为’None’。

同时还要介绍函数setattr,用于设置对象属性:

>>> setattr(s,'name','Emore')
>>> s.name
'Emore'

查看对象中储存的所有值,可以检查其__dict__属性。

>>> s.__dict__
{'name': 'Emore'}

3.8.抽象基类

Python通过引入模块abc为所谓的抽象基类提供支持。一般而言,抽象类是不能(至少不应该)实例化的类,其职责是定义子类应实现的一组抽象方法。

from ab import ABC,adstractmethod

class Talker(ABC):
    @adstractmethod
    def talk(self):
        pass

形如@this的是一种修饰器,@adstractmethod用来将方法标记为抽象的,必须在子类实现的方法。

抽象类不可以实例化,这是抽象类(包含抽象方法的类)最显著的特征。

>>> Talker()
Traceback (most recent call last):
  File "<pyshell#71>", line 1, in <module>
    Talker()
TypeError: Can't instantiate abstract class Talker with abstract method talk

如果派生出一个超类,但是不重写方法Talk,那这个子类也是抽象的,不能实例化。

这是用isinstance检查给定的实例确实是Talker对象,就知道一定要重写方法Talk。

另外isinstance的妙用。

>>> class Herry:
        def talk(self):
            print('ding')
>>> h=Herry()
>>> isinstance(h,Talker)
False

这时如果把Herry定义为Talker的子类,可能万事大吉,但是如果做不到,就需要将Herry注册为Talker,所有的Herry对象都被视为Talker对象,这样区别于派生子类,而理解为”挂名“。但是这么挂名,有一个缺点直接从抽象派生提供的保障没有了。

>>> class Clam:
        pass
>>> Talker.register(Clam)
<class '__main__.Clam'>
>>> issubclass(Clam,Talker)
True
>>> c=Clam()
>>> isinstance(c,Talker)
True
>>> c.talk()
Traceback (most recent call last):
  File "<pyshell#80>", line 1, in <module>
    c.talk()
AttributeError: 'Clam' object has no attribute 'talk'

报错提示是没有指定的方法talk。

所以这是一种挂名的方式,书本称之为”意图“。

4.小结

要慎用继承,尤其是多重继承,会造成难以排除的bug。

不要让对象过于亲密,方法只关心其所属的实例的属性,别的实例的状态让他们自己管理。

方法要简短可读,篇幅尽量在一屏以内。


水平有限,仅供参考。如有错误,劳请指出。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值