Python 面向对象

面向对象编程介绍

面向对象编程其实是一种程序设计方法。将对象作为程序的基本单元,一个对象包含了数据和操作数据的方法
。Python就是一种面向对象的语言,支持面向对象编程,在 Python 中一切都被视作对象。
还有一种程序设计方法就是面向过程编程,也就是结构化程序设计。在面向过程编程中,像是在完成一个个任
务,将程序流程化,即分步操作,分步的过程中用函数来完成这些任务,解决问题的焦点集中于函数,一切围
绕函数展开。
不同的是,在面向对象编程中,将函数和变量进一步封装成类,类才是程序的基本元素,它将数据和操作绑定
在一起,并保护数据不会被外界的函数意外地改变。类和和类的实例(也称对象)是面向对象的核心概念,是
和面向过程编程、函数式编程的根本区别。

类与对象

1.:  用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。可
以理解是一个模板,通过它可以创建出无数个具体实例(对象)
2. 对象:  类并不能直接使用,通过类创建出的实例也就是对象才能使用。可以理解为建筑图纸和房屋的关系
,图纸本身(类)并不能居住,要先通过图纸盖出房子(对象)才能居住。
定义类

上面将对象比作房屋,我们要建造房屋首先就要绘制图纸,也就是创建一个类。

类定义形式如下:
		
		class ClassName:  # 类名建议使用驼峰体,且首字母大写。
		    <statement-1>  # 下面的就是类体代码
		    .
		    .
		    .
		    <statement-N>

"""类体代码无需调用就会执行,并且产生类的名称空间"""	    

图纸的结构搭好了,就下来就要完善图纸里的内容,类里有着以下内容

方法:类中定义的函数。
局部变量:定义在方法中的变量,只作用于当前实例的类
类变量:类变量定义在类中且在函数体之外,类变量是所有实例公有的变量(方法名也是类变量)
实例变量:指的是实例本身拥有的变量
'''对于类来说,类变量和类方法不是必需的,也就是说 Python 允许创建空类'''
类的示例
		class Animal:  # 类名Animal
		    name = 'a'  # 类变量name
		
		    def run(self, kind):  # 方法名run,局部变量kind,在方法中self为类的实例(必有)
		        print(f'{kind} is running')

__dict__

我们使用 __dict__ 可以查看类的名称空间

使用 __dict__ 查看名称空间

		class Animal:
		    name = 'a'
		
		    def run(self, kind):
		        print(f'{kind} is running')
		
		
		print(Animal.__dict__)

会打印出一个字典

		{'__module__': '__main__', 
		'name': 'a', 
		'run': <function Animal.run at 0x000001CEDCB2A3B0>, 
		'__dict__': <attribute '__dict__' of 'Animal' objects>, 
		'__weakref__': <attribute '__weakref__' of 'Animal' objects>,
		'__doc__': None}
		

也可以直接查找单个属性

获取类中指定属性

		class Animal:
		    name = 'a'
		
		    def run(self, kind):
		        print(f'{kind} is running')
		
		
		print(Animal.__dict__['run'])


打印结果
		<function Animal.run at 0x0000020B1971A3B0>


此时和 print(Animal.run) 的结果是一样的,用类名点类变量的方式。
也可以通过赋值来更改 Animal.school 的值...		

实例化类

图纸有了就需要去建造,也就有了实例化的过程,产物就是实例

实例化就是创建一个类的实例的过程,定义一个变量接收返回该类的一个不带参数的函数

代码示例
		class Animal:
		    name = 'a'
		
		    def run(self, kind):
		        print(f'{kind} is running')
		
		x = Animal()  # 类的实例化 
		y = Animal()  # 实例可以为多个,可以理解为按照图纸创造出多个东西
		print(x) 
		print(y)

打印结果
		<__main__.Animal object at 0x000001F483EF6B60>
		<__main__.Animal object at 0x000001F483EF6B60>
		
'''示例中创建了俩个类的新实例,并将此对象分配给变量 x 和变量 y 。'''

实例的名称空间

前面写的 __dict__ 可以看到名称属性,我们来看下实例的名称空间

查看名称空间:		
		class Animal:
		    name = 'a'
		
		    def run(self, kind):
		        print(f'{kind} is running')
		
		x = Animal()
		print(x.__dict__)

打印结果:
		{}

实例化操作会创建一个空对象,所以示例中的名称空间是空的。我们可以添加键值对进去,如下示例

		class Animal:
		    name = 'a'
		
		    def run(self, kind):
		        print(f'{kind} is running')
		
		x = Animal()
		print(x.__dict__)
		x.__dict__['color'] = 'black'
		print(x.__dict__)

此时的示例 x 的名称字典里有 {'color': 'black'}
__init__

在创建类时,我们可以手动添加一个 __init__() 方法称之为构造方法,每当创建一个类的实例对象时(也就是实例化时),Python 解释器都会自动调用它

格式
		def __init__(self,...):  # 这里的 self 代表的是实例本身
		    代码块
代码示例
		class Animal:
		    def __init__(self, kind):  # 定义 kind 参数
		        self.kind = kind  # 在实例对象名称空间中创建kind变量,值是定义的形参kind
		
		    def run(self):
		        print(f'{self.kind} is running')  # 可以在方法中使用实例对象中的kind变量
		
		x = Animal('dog')  # 传入给__init__形参kind传入实参dog。self为实例本身,不用传。
		x.run()  # 在调用方法的时候,首先传入的是实例对象,传到方法中的形参self中

'''
self代表的是实例本身,也可以叫别的名字,只不过通常都使用self来表示。
需要注意的是,类被实例化的时候就会执行__init__,所以__init__中除了self有几个参数就得传几个参数
'''
实例化对象

实例化对象也就是类被实例化产生的实例,他指向类名,所以能引用类里面的属性。
在这里插入图片描述

实例化对象名.变量名 的方式进行属性引用操作

示例
		class Animal:
		    name = 'a'
		
		    def run(self):
		        pass
		
		x = Animal()  # 实例化
		print(x.name)  # 引用了类中变量name
		x.run()  # 引用了类中的方法run

结果为 'a'
实例化对象使用实例变量

示例
		class Animal:
		    name = 'a'
		
		    def __init__(self, name):
		        self.name = name
		    def run(self):
		        pass
		
		x = Animal('b')
		print(x.name)

结果为'b',通过__init__在实例中也创建一个名为name的变量,此时会优先去实例里找,也就是优先使用
实例变量,只有在实例变量中没有的时候会在类里找

动态方法、静态方法

调用类里面的方法,可以使用实例方法、类方法和静态方法三种方式。

实例方法

实例方法就是实例调用类里面的方法。实例本身会被自动传入到类里面的方法中,所以不需要加参数
。而用类名点方法名的方式调用就相当于调用一个普通函数,需要传一个参数到 self 中。属于绑定给对象的方法

代码示例
		class C1:

		    def f1(self):
		        print('from C1.f1', self)
				
		x = C1()
		C1.f1('a')  # 用类名调用类里的方法需要加参数,不建议这样使用
		x.f1()  # 实例方法,不需要传入参数


打印结果
		from C1.f1 a
		from C1.f1 <__main__.C1 object at 0x00000286765A6B60>
		
类方法

在实例方法中用类名调用类里的方法需要加参数,使用装饰器@classmethod后可以用类名不加参数直接调用。属于绑定给类的方法。

代码示例
		class C1:
		    @classmethod  # 使用装饰器 @classmethod
		    def f1(cls):
		        print('from C1.f1', cls)
		        
		x = C1()
		C1.f1()  # 此时使用类名调用类里的方法不用加参数
		x.f1()  # 通过实例对象调用也不用加参数

打印结果
		from C1.f1 <class '__main__.C1'>
		from C1.f1 <class '__main__.C1'>

此时类里的方法参数变成了 cls 而不是 self ,cls 代表的是类名,也就是使用装饰器 @classmethod后
在执行类方法时,类名会被当作第一个参数自动传进 cls 中。此时建议使用类名点方法名的方式调用。
静态方法

实例方法和类方法也被称为动态方法,因为会有默认参数被自动传入方法中。而静态方法就是将类中的方法变成无默认参数方法。建议使用类名调用的方式(实例对象调用也行),使用@staticmethod可以将方法变成静态方法。

代码示例
		class C1:
		    @staticmethod
		    def f1():
		        print('from C1.f1')
		
		x = C1()
		C1.f1()
		x.f1()

打印结果
		from C1.f1
		from C1.f1

此时类里的方法是没有默认参数的,需要参数得自己传值,不像 self 或者 cls 一样自动传值。

三种方法结合展示

代码示例

		class C1:
		
		    def f1(self):
		        print('from C1.f1', self)
		
		    @classmethod
		    def f2(cls):
		        print('from C1.f2', cls)
		
		    @staticmethod
		    def f3():
		        print('from C1.f3')
		
		
		x = C1()  # 实例化
		
		# 调用实例方法
		x.f1()  # 自动将实例对象当作参数传入到 self 中
		C1.f1('a')  # 传入参数到 self 中。请注意这种调用方式,虽然可行,但建议不要这么做!
		
		# 调用类方法
		C1.f2()  # 自动将类名当作参数传入到 cls 中
		x.f2()  # 也不需要传参数调用。请注意这种调用方式,虽然可行,但建议不要这么做!
		
		# 调用静态方法
		C1.f3()  # 相当于调用了一个普通函数
		x.f3()  # 请注意这种调用方式,虽然可行,但建议不要这么做!

三大特性之继承

继承就如同字面意思一样,继续进行遗留下来的事业,在生活中最容易理解的是继承家产…
而在代码层面也可以进行继承,一个类可以继承另一个类的变量以及方法。一个类可以被多个类继承,也就是说被继承的类是公共的部分,创造继承的目的是可以减少代码冗余,其实类也是为了减少冗余产生的。

单继承
对象:  数据与功能的结合体
类:    多个对象相同数据和功能的结合体
父类:  多个类相同数据和功能的结合体

继承别人的类称为子类,而被继承的类称为父类、基类或超类

代码示例
		class C1:  # 定义被继承的类,也就是父类
		    def f1(self):  # 父类中的方法
		        print('from C1.f1', self)
		
		class C2(C1):  # 定义子类,子类括号里的是父类
		    pass
		
		x = C2()  # 实例化
		x.f1()  # 子类实例对象调用父类方法
		
举例
		class Person:  # 定义父类,父类的变量、方法是多个子类也需要的共同数据功能
		    def __init__(self, name, age, gender):
		        self.name = name  # 给实例添加新属性
		        self.age = age
		        self.gender = gender
		
		class Teacher(Person):
		    def teach(self):
		        print(f'{self.name}老师正在讲课')
		
		class Student(Person):  # 继承了父类Person
		    def study(self):
		        print(f'{self.name}学生正在学习')
		
		x = Student('XWenXiang', 18, 'male')  # 需要3个参数,由于父类有3个参数需要传入
		x.study()  

多继承

多继承和单继承一样,只不过多继承是一个类同时继承了多个父类。

格式
		class DerivedClassName(Base1, Base2, Base3):
		    <statement-1>
		    .
		    .
		    .
		    <statement-N>
		    
举例
		class C1:  # 父类C1
		    name = 'XWenxiang'
		
		class C2:  # 父类C2
		    id = '001'
		
		class MyClass(C1, C2):  # 继承父类的变量
		    pass
		
		x = MyClass()
		print(x.name)  # 调用父类C1的name
		print(x.id)  # 调用父类C2的id

__bases__查看所有父类

父类可以有多个,我们可以通过__bases__来看所有父类

代码示例
		class C1:
		    name = 'XWenxiang'
		
		class C2:
		    id = '001'
		
		class MyClass(C1, C2):
		    pass
		
		print(MyClass.__bases__)  # 子类名点__bases__可以查看所有父类

打印结果
		(<class '__main__.C1'>, <class '__main__.C2'>)
		

继承示例图
在这里插入图片描述

继承的查找顺序

在不同的地方都有同名的名称,会按照一下顺序查找

无继承查找顺序
没有继承关系的时候,顺序在前面写的也有体现,首先会在实例化对象中查找,再去类里面查找。

代码示例
		class C1:
		    name = 'XWenXiang'  # 定义类变量name
		    age = 18
		
		x = C1()  # 实例化
		x.name = '66'  # 在实例化对象中添加变量name
		print(x.name)  # 打印结果 66 ,由于实例添加了变量name所以先去实例名称空间里找。

单继承查找
单继承查找名称的顺序是,首先去实例化对象里面找,然后去子类中查找,再去父类中查找。

代码示例
		class C1:  # 定义父类 C1
		    def f1(self):  # 定义父类方法 f1
		        print('from C1.f1')
		
		class C2(C1):  # 定义子类 C2 继承父类 C1
		    def f1(self):  # 定义子类方法 f1
		        print('from C2.f1')
		        
		x = C2()  # 实例化
		x.f1()  # 对象调用方法 f1

打印结果
		from C1.f1

由于 x 是子类 C2 的实例化对象,所以对象中没有方法 f1 时会先去子类 C2 中找,如果将子类的 f1 方
法注销,那么会去父类中找。
经典案例
		class C1:  # 定义父类 C1
		    def f1(self):  # 定义方法
		        print('from C1.f1')
		
		    def f2(self):  # 定义方法
		        print('from C1.f2')
		        self.f1()  # 调用实例对象的方法 f1
		
		class C2(C1):  # 定义子类 C2 继承父类 C1
		    def f1(self):  # 定义方法
		        print('from C2.f1')
		        
		x = C2()  # 对子类实例化
		x.f2()  # 调用方法 f2

打印结果
		from C1.f2
		from C2.f1

实例对象调用了方法 f2 ,而子类中没有,所以去调用父类中的方法 f2 ,而父类方法 f2 中调用了实例中
的方法 f1 ,很明显实例 x 中没有但是在子类中有, 所以执行的是子类中的方法 f1 ,若是子类中没有则
执行父类的。
多继承查找

我们首先了解一下经典类与新式类。

在python2中存在经典类与新式类。在python3中只有新式类

新式类:直接或者间接继承了object或者其子类的类
经典类:不继承任何的类
object 是所有类的父类,默认所有的类都继承至Object类,里面规定了类的结构,加载方式,常用函数...
(<class 'object'>,)
我们可以使用'__bases__'来查看父类 object ,但是在python2中是没有的,需要手动添加,如下

		class 类名(Object):
			pass


1. 情况一

代码示例
		class C1:
		    name = 'C1'
		
		class C2:
		    name = 'C2'
		
		class C3:
		    name = 'C3'
		
		class MyClass(C1, C2, C3):
		    pass
		
		x = MyClass()
		print(x.name)

打印结果
		C1

'''同时继承了多个父类,父类中都有相同名字的变量,且子类中没有,此时会按照从左到右查找父类'''

情况一示意图
在这里插入图片描述

2. 情况二

代码示例
		class C4:
		    name = 'C4'
		
		class C5:
		    name = 'C5'
		
		class C6:
		    name = 'C6'
		
		class C1(C4):
		    name = 'C1'
		
		class C2(C5):
		    name = 'C2'
		
		class C3(C6):
		    name = 'C3'
		
		class MyClass(C1, C2, C3):
		    pass
		
		x = MyClass()
		print(x.name)

子类 MyClass 继承的父类又分别继承了别的父类,此时遵循'深度优先',也就是继承的父类一直走到底
如下图所示

情况二示意图
在这里插入图片描述

3. 情况三

菱形继承的情况,也就是父类还有一个共有的父类

代码示例
		class C7:
		    name = 'C7'
		
		class C4(C7):
		    name = 'C4'
		
		class C5(C7):
		    name = 'C5'
		
		class C6(C7):
		    name = 'C6'
		
		class C1(C4):
		    name = 'C1'
		
		class C2(C5):
		    name = 'C2'
		
		class C3(C6):
		    name = 'C3'
		
		class MyClass(C1, C2, C3):
		    pass
		
		x = MyClass()
		print(x.name)

此时有了一个共有的父类,相当于闭环了,此时还要遵循的是广度优先如下图顺序所示

情况三示例
在这里插入图片描述

派生类

前面写道继承别人的类称之为子类,子类继承了父类所有的变量以及方法后还进行修改、拓展的称之为派生类其实也是子类。

调用父类构造方法
代码示例(子类中需要父类的构造方法)

		class Father:
		    def __init__(self, name):
		    	self.name = name
		        print('from Father.__init__')
		
		    def f1(self):
		        print('from father.f1')
		
		class Son(Father):
		    def f1(self):
		        print('from son.f1')
		
		x = Son('x')
		x.f1()


打印结果
		from Father.__init__
		from son.f1


在示例中,子类Son继承了父类Father,而在父类中有方法__init__,其中还有着形参name。
子类不重写 __init__,实例化子类时,会自动调用父类定义的 __init__。
重写构造方法

如果父类方法的功能不能满足需求,可以在子类重写父类的方法

代码示例(重写__init__)

		class Father:
		    def __init__(self, name):
		    	self.name = name
		        print('from Father.__init__')
		
		    def f1(self):
		        print('from father.f1')
		
		class Son(Father):
		    def __init__(self, age):
		    	self.age = age
		        print('from Son.__init__')
		
		    def f1(self):
		        print('from son.f1')
		
		x = Son('x')
		x.f1()

打印结果
		from Son.__init__
		from son.f1		

此时重写了__init__ ,实例化子类,就不会调用父类已经定义的 __init__,而是调用子类的构造方法。
super()函数

但是在子类重写父类的方法不能获取父类的构造方法,如果我们要获取父类方法又额外增加自己的方法可以使用 super 关键字。

语法格式
		super(子类,self).父类方法(参数1,参数2....)
		
还有一种写法
		父类名称.父类方法(self,参数1,参数2...)
		
代码示例
		class Father:
		    def __init__(self, name):
		        self.name = name
		        print('from Father.__init__')
		
		    def f1(self):
		        print('from father.f1')
		
		class Son(Father):
		    def __init__(self, name, age):  # 定义子类构造方法,定义参数
		        super().__init__(name)  # 相当于继承了父类构造方法的参数
		        #  Father.__init__(self, name)  这样写也可以
		        self.age = age  # 添加属于自己的参数
		        print('from Son.__init__')
		
		    def f1(self):
		        print('from son.f1')
		
		x = Son('x', 12)
		x.f1()

打印结果
		from Father.__init__
		from Son.__init__
		from son.f1

使用了super()方法后可以在继承父类构造方法的基础上进行修改扩展。
其实也就相当于少写 self.name = name ,要是参数多了会体现方便之处。
派生继承列表
代码示例
		class C1(list):  # 继承列表
		    def append(self, i):  # 定义一个append方法,定义形参i
		        if i > 5:  # 对形参i进行判断
		            print('不能添加')
		            return  # 结束函数
		        super().append(i)  # 使用super后,调用的方法其实是列表里面的方法。
		        
		x = C1()  # 实例化,此时的x相当于一个列表
		x.append(6)  # 调用类C1中的方法,但是在类方法中又调用了列表实际的append方法。 
		print(x) 

可以理解为继承了列表,但又进行了自己的功能
派生继承 json

由于datetime类型的对象不能被JSON序列化,我们可以继承JSON然后进行添加功能。

  1. 首先我们可以先试一下会报什么错,它的报错信息是: TypeError: Object of type datetime is not JSON serializable

  2. 根据报错提示可以跳转到源码位置
    在这里插入图片描述
    在这里插入图片描述

  3. 根据上图一我们可以看到JSON中的 dumps方法中 cls 参数默认为 None,方法中还有一个判断,当 cls 为None的时候将 JSONEncoder 赋值给 cls,我们查看 JSONEncoder 的源码可以看到它是一个位于 encoder.py 文件中的类,这个类中的方法 default 有主动报错的功能,我们可以进行继承重写此方法。

重写 default 方法:

		import datetime
		import json
				
		class MyJsonEncoder(json.JSONEncoder):  # 继承类 JSONEncoder
		    def default(self, o):  # 重写 default 方法
		        # 形参o就是即将要被序列化的数据对象
		        '''将o处理成json能够序列化的类型即可,例如格式化字符串'''
		        if isinstance(o, datetime.datetime):  # 判断是否为 datetime 格式
		            return o.strftime('%Y-%m-%d %X')  # 返回格式化时间字符串
		        elif isinstance(o, datetime.date):  # 判断是否为 date 格式
		            return o.strftime('%Y-%m-%d')
		# 调用父类的default,让父类的default方法继续执行防止有其他额外操作(其实没有)       
		        return super().default(o)  
		
		
		d1 = {'t1': datetime.datetime.today(), 't2': datetime.date.today()}
		res = json.dumps(d1, cls=MyJsonEncoder)  # 修改json的dumps方法中cls的默认值
		print(res)

三大特性之封装

封装是指将数据与具体操作的实现代码放在某个对象内部,使这些代码的实现细节不被外界发现,外界只能通过接口使用该对象。目的是为了隔离复杂度

对一个对象进行隐藏可以在命名时在前面加入俩个下划线代表该变量或数据是私有的

代码示例
		class C1(object):
		    name = 'XWenXiang'
		    __age = 18
		
		    def __f1(self):
		        print('from C1.f1')

		x = C1()
		print(C1.__dict__)
		# 错误语句
		# print(x.age)  # 'C1' object has no attribute 'age'
		# print(x.__age)  # 'C1' object has no attribute '__age'
		# x.f1()  
		# x.__f1()

使用__dict__打印类C1里的名称:
		{'__module__': '__main__', 'name': 'XWenXiang', '_C1__age': 18, '_C1__f1': <function C1.__f1 at 0x00000138C187A3B0>, '__dict__': <attribute '__dict__' of 'C1' objects>, '__weakref__': <attribute '__weakref__' of 'C1' objects>, '__doc__': None}

发现在名称空间里面,'__age' 其实是以'_C1__age'也就是' _类名__变量名'的形式存在的,所以说并没
有严格意义上地限制外部访问,仅仅只是一种语法意义上的变形
所以是可以用'x._C1__age''x._C1__f1'的形式强行调用,但是这样没有意义。

代码示例

		class C1(object):
		    name = 'XWenXiang'
		    __age = 18
		
		    def __f1(self):
		        print('from C1.f1')
		
		x = C1()
		print(x._C1__age)
		x.__age = 19  # 相当于加了一个普通变量,也没意义
		print(x.__age)


我们把属性隐藏起来然后开个公共接口,可以让外面进行访问

私有的属性在类里是可以正常调用的
代码示例
		class UserInfo(object):
		    def __init__(self, name, age):  # 实例化类的时候自动执行
		        self.set_info(name, age)  # 调用方法 set_info
		
		    def check_info(self):  # 查看信息的接口
		        print(f'姓名是 {self.__name}, 年龄 {self.__age} 岁')
		
		    def set_info(self, name, age):  # 设置信息的接口
		        if not isinstance(name, str):
		            raise TypeError('姓名必须是字符串类型')
		        if not isinstance(age, int):
		            raise TypeError('年龄必须是整型')
		        self.__name = name
		        self.__age = age

		x = UserInfo('x', 18)
		x.check_info()


也就是说如果想要调用接口,必须的满足接口的要求才行。
类的成员与下划线总结:
		_name、_name_、_name__:建议性的私有成员,不要在外部访问。
		__name、 __name_ :强制的私有成员,但是你依然可以蛮横地在外部危险访问。
		__name__:特殊成员,与私有性质无关,例如__doc__。
		name_、name__:没有任何特殊性,普通的标识符,但最好不要这么起名。
@property装饰器

Python内置的@property装饰器可以把类的方法伪装成属性调用的方式,本来是 Foo.func() 的调用方法,变成 Foo.func 的方式

代码示例
		class C1(object):
		    def f1(self):
		        print('from C1.f1')
		
		    @property
		    def f2(self):
		        print('from C1.f2')
		
		x = C1()
		x.f1()
		x.f2

打印结果
		from C1.f1
		from C1.f2

加了装饰器后我们调用就不需要加括号了,伪装成了一个变量。

三大特征之多态

多态按照字面意思就是一个事物有着多种状态,现实生活中最典型的是水,它有液态气态固态三种形态。在代码中可以解释成一个抽象类有多个子类

示例中描述的是动物类和其子类

代码示例
		class Animal: #同一类事物:动物
		    def talk(self):
		        pass
		class Cat(Animal): #动物的形态之一:猫
		    def talk(self):
		        print('喵喵喵')
		class Dog(Animal): #动物的形态之二:狗
		    def talk(self):
		        print('汪汪汪')
		class Pig(Animal): #动物的形态之三:猪
		    def talk(self):
		        print('哼哼哼')
		
		#实例化得到三个对象
		cat=Cat()
		dog=Dog()
		pig=Pig()
		cat.talk()
		dog.talk()
		pig.talk()
	
打印结果
		喵喵喵
		汪汪汪
		哼哼哼


示例中,不同的子类重写父类的方法,使得不同的子类实现同种方法时调用方法名是一样的,这样的好处是增
强了程序的灵活性和可扩展性。

或者在这个基础上在加一个统一的接口:

		def talk(animal):
			animal.talk()
			

其实这种编程技巧在之前也有遇到过

代码示例
		len('XWenXiang')
		len([1, 2, 3])
		len((1, 2, 3))
		len({1: 1, 2: 2, 3: 3})

例如 len() 他对不同的数据类型使用的时候调用的都是 len() 而不是每一个数据类型都有不一样的方法。

面向对象的多态性也需要自己去遵守,虽然也可以强制的去要求

强制多态
		import abc
		
		# 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化
		class Animal(metaclass=abc.ABCMeta):
		    @abc.abstractmethod  # 该装饰器限制子类必须定义有一个名为talk的方法
		    def talk(self):  # 抽象方法中无需实现具体的功能
		        pass
		class Person(Animal):  # 但凡继承Animal的子类都必须遵循Animal规定的标准
		    def talk(self):
		        pass
		        
		x = Person()  # 若子类中没有一个名为talk的方法则会抛出异常TypeError,无法实例化

在多态中还引出了 duck typing 一词。只要你看着像鸭子,行为像鸭子 那么你就是鸭子!!!

		class Txt: # Txt类有两个与文件类型同名的方法,即read和write
		    def read(self):
		        pass
		    def write(self):
		        pass
		
		class Disk: # Disk类也有两个与文件类型同名的方法:read和write
		    def read(self):
		        pass
		    def write(self):
		        pass

反射

反射指的是程序可以访问、检测和修改本身状态或者行为的一种能力,其实就是通过字符串来操作对象的数据和功能

我们要知道'name' 和 name 两者字面上看起来一样,却完全不一样。
反射涉及到四个内置函数,分别为:

		hasattr(object,'name')
		getattr(object, 'name', default=None)
		setattr(x, 'y', v)
		delattr(x, 'y')

hasattr(object,‘name’)

这个内置方法是判断对象是否有这个名字(查的是 name,不是 ‘name’ )的属性,name必须为字符串

代码示例
		class C1():
		    def f1(self):
		        pass
		
		print(hasattr(C1, 'f1'))

判断C1类中是否有f1属性,有则返回True,否则返回False
getattr(object, ‘name’, default=None)

根据字符串获取类中对应的变量名或者方法名,若没有返回None

代码示例
		class C1():
		    def f1(self):
		        pass
		x = C1()
		print(getattr(x, 'f1', None))  # 没有设置None且属性不存在程序会报错。

输出结果
		<bound method C1.f1 of <__main__.C1 object at 0x0000029CFC586590>>

获取到类中的方法名,等同于x.f1
setattr(x, ‘y’, v)

根据字符串给对象设置键值对(名称空间中的名字)

代码示例
		class C1():  # 定义类C1
		    def f1(self):
		        pass
		
		def f2():  # 定义函数f2
		    pass
		
		x = C1()  # 实例化类
		setattr(x, 'name', 'X')  # 给x的名称空间传入变量,变量名为name,值为X
		setattr(x, 'f2', f2)  # 给x的名称空间传入方法
		print(x.__dict__)  # 打印x的名称空间

打印结果
		{'name': 'X', 'f2': <function f2 at 0x00000216DA303E20>}

此方法相当于 x.name = 'X'
delattr(x, ‘y’)

根据字符串删除对象名称空间中对应的属性

代码示例
		class C1():
		    def f1(self):
		        pass
		def f2():
		    pass
		
		x = C1()
		setattr(x, 'name', 'X')
		setattr(x, 'f2', f2)
		print('删除前: ', x.__dict__)
		delattr(x, 'f2')  # 删除 f2 属性
		print('删除后: ', x.__dict__)

打印结果
		删除前:  {'name': 'X', 'f2': <function f2 at 0x000001ABE05C3E20>}
		删除后:  {'name': 'X'}

相当于执行了 del x.f2
结合使用
代码示例
		class C1:
		    def add_obj(self):  # 添加属性名
		        print('from C1.f1')
		        obj = input('请输入您添加的属性名:  ').strip()
		        obj_a = input('请输入您添加的属性值:  ').strip()
		        setattr(self, obj, obj_a)  # 相当于 self.obj = obj_a
		
		    def del_obj(self):
		        print('from C1.f2')
		        obj = input('请输入您要删除的属性:  ').strip()
		        delattr(self, obj)  # 相当于 del self.obj
		
		x = C1()
		while True:
		    func = input('请输入您要执行的方法名:  ').strip()  # 动态执行方法
		    if hasattr(x, func):  # 判断该方法存不存在
		        getattr(x, func)()  # 获取该方法名并加括号调用
		        print(x.__dict__)
		    else:
		        print('没有该方法名')


反射案例
反射提供了一种不需要考虑代码的前提下,操作数据和功能,我们可以模拟一下cmd命令。

		class WinCmd(object):
		    def cd(self):
		        print('cd')
		
		    def ls(self):
		        print('ls')
		
		x = WinCmd()
		def run(x):
		    while True:
		        cmd = input('输入cmd命令: ').strip()
		        if hasattr(x, cmd):
		            y = getattr(x, cmd)
		            y()
		        else:
		            print('没有这个命令')
		            
		run(x)
		
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值