学习目标 :掌握Python面向对象进阶知识及应用
一、类的成员 类的成员分为三大类:字段、属性和方法。
所有成员中,只有实例字段的内容保存在对象中,即:根据此类创建了多少对象,在内存中就有多少个实例字段。而其他的成员,则都是保存在类中,即:无论对象的多少,在内存中只创建一份。
1. 字段(attribute) 类中有两种类型的变量,类变量(class variable) 和实例变量(instance variable) 。由于类中声明的变量称为字段,故也称类变量也称类字段(普通字段),实例变量为实例字段或成员字段。类字段和实例字段在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同:类字段属于类 ,实例字段属于对象 。
看一下例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Province : country = '中国' def __init__ (self, name) : self.name = name obj = Province('山西省' ) print(obj.name) print(obj.country) print(Province.country) obj2 = Province('河南省' ) print(obj2.name) print(obj2.country)
由上述代码可以看出【实例字段需要通过对象来访问】【类字段通过类访问】。在看一个例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Rectangle () : recs = [] nums = 0 def __init__ (self, w, h) : self.width = w self.height = h self.recs.append((self.width, self.len)) self.nums += 1 r1 = Rectangle(10 , 24 ) r2 = Rectangle(20 , 40 ) r3 = Rectangle(100 , 200 ) print(Rectangle.recs) print(Rectangle.nums)
本例中,我们子在类Rectangle中添加了一个叫recs
的类变量,它是在__init__
方法之外定义的。因为Python
只有在创建对象时才调用__init__
方法,而我们希望能够使用类对像(不会调用__init__
方法)访问类变量。
接下来,我们创建了3个Rectangle对象。每创建一个Rectangle
对象,__init__
方法中的代码就会向recs
列表中添加一个由新对象宽度和长度组成的元组。这样,每当新创一个Rectangle
对象时,就会被自动添加到recs
列表。通过使用类变量,即可在不使用全局变量的情况下,做到了在类创建的不同实例之间共享数据 。
在使用上可以看出实例字段和类字段的归属是不同的。其在内容的存储方式类似如下图:
由上图可知:类字段在内存中只保存一份 ,实例字段在每个对象中都要保存一份 。
应用场景: 通过类创建对象时,如果每个对象都具有相同的字段,那么就使用类字段。
2. 方法 方法包括:实例方法、静态方法和类方法,三种方法在内存中都归属于类 ,区别在于调用方式不同。
实例方法:由对象 调用;至少一个self
参数;执行实例方法时,自动将调用该方法的对象 赋值给self
。
类方法:由类 调用; 至少一个cls
参数;执行类方法时,自动将调用该方法的类 复制给cls
。
静态方法:由类 调用;无默认参数。
下面来看一个示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Foo : def __init__ (self, name) : self.name = name def ord_func (self) : """ 定义实例方法,至少有一个self参数 """ print('实例方法' ) @classmethod def class_func (cls) : """ 定义类方法,至少有一个cls参数 """ print('类方法' ) @staticmethod def static_func () : """ 定义静态方法 ,无默认参数""" print('静态方法' ) f = Foo() f.ord_func() Foo.class_func() Foo.static_func()
相同点: 对于所有的方法而言,均属于类(非对象)中,所以,在内存中也只保存一份。
不同点: 方法调用者不同、调用方法时自动传入的参数不同。
定义一个三角形类,通过传入三条边的长度来构造三角形,并提供计算周长和面积的方法。计算周长和面积肯定是三角形对象的方法,这一点毫无疑问。但是在创建三角形对象时,传入的三条边长未必能构造出三角形,为此我们可以先写一个方法来验证给定的三条边长是否可以构成三角形,这种方法很显然就不是实例方法,因为在调用这个方法时三角形对象还没有创建出来。我们可以把这类方法设计为静态方法或类方法,也就是说这类方法不是发送给三角形对象的消息,而是发送给三角形类的消息,代码如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Triangle (object) : """三角形类""" def __init__ (self, a, b, c) : """初始化方法""" self.a = a self.b = b self.c = c @staticmethod def is_valid (a, b, c) : """判断三条边长能否构成三角形(静态方法)""" return a + b > c and b + c > a and a + c > b def perimeter (self) : """计算周长""" return self.a + self.b + self.c def area (self) : """计算面积""" p = self.perimeter() / 2 return (p * (p - self.a) * (p - self.b) * (p - self.c)) ** 0.5
上面的代码使用staticmethod
装饰器声明了is_valid
方法是Triangle
类的静态方法,如果要声明类方法,可以使用classmethod
装饰器。可以直接使用类名.方法名
的方式来调用静态方法和类方法,二者的区别在于,类方法的第一个参数是类对象本身,而静态方法则没有这个参数。简单的总结一下,实例方法、类方法、静态方法都可以通过类名.方法名
的方式来调用,区别在于方法的第一个参数到底是普通对象还是类对象,还是没有接受消息的对象 。静态方法通常也可以直接写成一个独立的函数,因为它并没有跟特定的对象绑定。
3. 属性(property) 如果你已经了解Python类中的方法,那么属性就非常简单了,因为Python中的属性其实是实例方法 的变种。
3.1 属性的基本使用 使用示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Foo : def func (self) : pass @property def prop (self) : pass foo_obj = Foo() foo_obj.func() foo_obj.prop
由属性的定义和调用要注意一下几点:
注意 :属性存在意义是访问属性时可以制造出和访问字段完全相同的假象 。属性由方法变种而来 ,如果Python中没有属性,方法完全可以代替其功能。
实例 :对于主机列表页面,每次请求不可能把数据库中的所有内容都显示到页面上,而是通过分页的功能局部显示,所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的所有数据(即:limit m,n),这个分页的功能包括:根据用户请求的当前页和总数据条数计算出 m 和 n,根据m 和 n 去数据库中请求数据 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Pager : def __init__ (self, current_page) : self.current_page = current_page self.per_items = 10 @property def start (self) : val = (self.current_page - 1 ) * self.per_items return val @property def end (self) : val = self.current_page * self.per_items return val p = Pager(1 ) print(p.start) 就是起始值,即:m print(p.end) 就是结束值,即:n
从上述可见,Python的属性的功能是:属性内部进行一系列的逻辑计算,最终将计算结果返回。
3.2 属性的两种定义方式 属性的定义有两种方式:
装饰器 即:在方法上应用装饰器
类字段 即:在类中定义值为property对象的类字段
装饰器方式:在类的实例方法上应用@property装饰器
Python中的类有经典类和新式类,新式类的属性比经典类的属性丰富。( 如果类继object,那么该类是新式类,Python3默认都是新式类 )。新式类 ,具有三种@property装饰器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Goods (object) : @property def price (self) : print('@property' ) @price.setter def price (self, value) : print('@price.setter' ) @price.deleter def price (self) : print('@price.deleter' ) obj = Goods() print(obj.price) obj.price = 123 del obj.price
由于新式类中具有三种访问方式,我们可以根据他们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Goods (object) : def __init__ (self) : self.original_price = 100 self.discount = 0.8 @property def price (self) : new_price = self.original_price * self.discount return new_price @price.setter def price (self, value) : self.original_price = value @price.deltter def price (self, value) : del self.original_price obj = Goods() print(obj.price) obj.price = 200 del obj.price
类字段方式,创建值为property对象的类字段
当使用类字段的方式创建属性时,经典类和新式类无区别
使用示例:
1 2 3 4 5 6 7 8 class Foo : def get_bar (self) : return 'zhangyafei' BAR = property(get_bar) obj = Foo() reuslt = obj.BAR print(reuslt)
property的构造方法中有个四个参数
第一个参数是方法名 ,调用 对象.属性
时自动触发执行方法
第二个参数是方法名 ,调用 对象.属性 = XXX
时自动触发执行方法
第三个参数是方法名 ,调用 del 对象.属性
时自动触发执行方法
第四个参数是字符串 ,调用 对象.属性.__doc__
,此参数是该属性的描述信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Foo : def get_bar (self) : return 'zhangyafei' def set_bar (self, value) : return 'set value' + value def del_bar (self) : print('del zhangyafei' ) BAR = property(get_bar, set_bar, del_bar, 'description...' ) obj = Foo() print(obj.BAR) obj.BAR = "wangxiaoer" del Foo.BAR obj.BAE.__doc__
由于类字段方式创建属性具有三种访问方式,我们可以根据他们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Goods (object) : def __init__ (self) : self.original_price = 100 self.discount = 0.8 def get_price (self) : new_price = self.original_price * self.discount return new_price def set_price (self, value) : self.original_price = value def del_price (self, value) : del self.original_price PRICE = property(get_price, set_price, del_price, '价格属性描述...' ) obj = Goods() print(obj.PRICE) obj.PRICE = 200 del obj.PRICE
注意:Python WEB框架 Django 的视图中 request.POST
就是使用的类字段的方式创建的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 class WSGIRequest (http.HttpRequest) : def __init__ (self, environ) : script_name = get_script_name(environ) path_info = get_path_info(environ) if not path_info: path_info = '/' self.environ = environ self.path_info = path_info self.path = '%s/%s' % (script_name.rstrip('/' ), path_info.lstrip('/' )) self.META = environ self.META['PATH_INFO' ] = path_info self.META['SCRIPT_NAME' ] = script_name self.method = environ['REQUEST_METHOD' ].upper() _, content_params = cgi.parse_header(environ.get('CONTENT_TYPE' , '' )) if 'charset' in content_params: try : codecs.lookup(content_params['charset' ]) except LookupError: pass else : self.encoding = content_params['charset' ] self._post_parse_error = False try : content_length = int(environ.get('CONTENT_LENGTH' )) except (ValueError, TypeError): content_length = 0 self._stream = LimitedStream(self.environ['wsgi.input' ], content_length) self._read_started = False self.resolver_match = None ... def _get_post (self) : if not hasattr(self, '_post' ): self._load_post_and_files() return self._post def _set_post (self, post) : self._post = post @cached_property def COOKIES (self) : raw_cookie = get_str_from_wsgi(self.environ, 'HTTP_COOKIE' , '' ) return http.parse_cookie(raw_cookie) def _get_files (self) : if not hasattr(self, '_files' ): self._load_post_and_files() return self._files POST = property(_get_post, _set_post) FILES = property(_get_files) REQUEST = property(_get_request)
所以,定义属性共有两种方式,分别是【装饰器】和【类字段】,而【装饰器】方式针对经典类和新式类又有所不同。
3.3 字段和属性的有什么区别? 看一个例子:
1 2 3 4 5 6 7 8 9 10 class Circle (object) : def __init__ (self, radius) : self.radius = radius @property def diameter (self) : return self.radius * 2 @diameter.setter def diameter (self, new_diameter) : self.radius = new_diameter / 2
例子中,我定义一个圆圈类,圆圈(circle)嘛,肯定有直径(diameter)有半径(radius)的,我可以简单的设置他们为attribute, 比如:
1 2 3 4 class Circle (object) : def __init__ (self, radius,diameter) : self.radius = radius self.diameter = diameter
这样写没有问题,我们实例化的时候,比如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Circle (object) : def __init__ (self, radius,diameter) : self.radius = radius self.diameter = diameter my_circle = Circle(2 ,4 ) print('radius is {}' .format(my_circle.radius)) print('diameter is {}' .format(my_circle.diameter)) my_circle.radius = 6 print('radius is {}' .format(my_circle.radius)) print('diameter is {}' .format(my_circle.diameter))
运行结果
1 2 3 4 radius is 2 diameter is 4 radius is 6 diameter is 4
发现问题了没有?一开始的时候,我们让半径等于2,直径等于4,那是因为我们知道对圆圈来说啊,这是肯定的事情。但是实际写代码过程中,我可能要去更改我的半径,比如说,让半径等于6,当然我在让半径等于6的时候,直径怎么还是4?因为代码不知道直径是半径的2倍,当然我可以暴力的把直径改为12,但我更希望,程序自己能在我把半径变成6的时候自动把直径变成12,或者当我需要改直径为10的时候,程序能把半径自动的变成5. 如果有这个需求的时候,@property就上场了,如同我一开始给出的例子这样,试试下面这个代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Circle (object) : def __init__ (self, radius) : self.radius = radius @property def diameter (self) : return self.radius * 2 @diameter.setter def diameter (self, new_diameter) : self.radius = new_diameter / 2 my_circle = Circle(2 ) print('radius is {}' .format(my_circle.radius)) print('diameter is {}' .format(my_circle.diameter)) my_circle.radius = 6 print('radius is {}' .format(my_circle.radius)) print('diameter is {}' .format(my_circle.diameter)) my_circle.diameter = 6 print('radius is {}' .format(my_circle.radius)) print('diameter is {}' .format(my_circle.diameter))
运行结果
1 2 3 4 5 6 adius is 2 diameter is 4 radius is 6 diameter is 12 radius is 3.0 diameter is 6.0
其实property是一个有点函数意思的attribute,两者虽然字面上一致,或者说翻译成中文意思上基本没差别,但使用上是完全俩个不同的东东。如果实在理解不了,可以先跳过去,等用到或者碰到的时候,跑一下代码就明白了。
二、可见性 在很多面向对象编程语言中,对象的属性通常会被设置为私有(private)或受保护(protected)的成员,简单的说就是不允许直接访问这些属性;对象的方法通常都是公开的(public),因为公开的方法是对象能够接受的消息,也是对象暴露给外界的调用接口,这就是所谓的访问可见性。在Python中,可以通过给对象属性名添加前缀下划线的方式来说明属性的访问可见性。例如,可以用__name
表示一个私有属性,_name
表示一个受保护属性,代码如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Student : def __init__ (self, name, age) : self.__name = name self.__age = age def study (self, course_name) : print(f'{self.__name} 正在学习{course_name} .' ) stu = Student('王大锤' , 20 ) stu.study('Python程序设计' ) print(stu.__name)
上面代码的最后一行会引发AttributeError
(属性错误)异常,异常消息为:'Student' object has no attribute '__name'
。由此可见,以__
开头的属性__name
是私有的,在类的外面无法直接访问,但是类里面的study
方法中可以通过self.__name
访问该属性。
特殊变量命名规则 :
1、_xx
以单下划线开头的表示的是protected
类型的变量。即保护类型只能允许其本身与子类进行访问 。若内部变量标示,如: 当使用“from M import
”时,不会将以一个下划线开头的对象引入 。
2、__xx
双下划线的表示的是私有类型的变量。只能允许这个类本身进行访问 ,连子类也不可以用于命名一个类属性(类变量),调用时名字被改变(在类FooBar
内部,__boo
变成_FooBar__boo
,如self._FooBar__boo
)
3、__xx__
定义的是特殊方法 。用户控制的命名空间内的变量或是属性,如__init__
, __import__
或是__file__
。只有当文档有说明时使用,不要自己定义这类变量。 (就是说这些是python内部定义的变量名)
需要提醒大家的是,Python并没有从语法上严格保证私有属性的私密性,它只是给私有的属性和方法换了一个名字来阻挠对它们的访问,事实上如果你知道更换名字的规则仍然可以访问到它们。当变量被标记为私有后,在变量的前端插入类名,再类名前添加一个下划线,即形成了_ClassName__变量名
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 """私有成员只能类中访问,外部不能直接访问,但若要访问,可以强制访:_类名__var, 或者在内部提供一个接口供外部访问""" class Obj (object) : def __init__ (self, name, age) : self.name = name self.__age = age def get_age (self) : return self.__age obj = Obj('张亚飞' ,23 ) print(obj.name) print(obj.get_age()) print(obj._Obj__age) """私有字段只能在当前类中访问,其子类也不能访问""" class Obj2 (Obj) : def print_age (self) : print(self.__age) obj2 = Obj2('张亚飞' ,23 ) obj2.print_age()
我们可以对上面的代码稍作修改就可以访问到私有的。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Student : def __init__ (self, name, age) : self.__name = name self.__age = age def study (self, course_name) : print(f'{self.__name} 正在学习{course_name} .' ) stu = Student('张亚飞' , 20 ) stu.study('Python程序设计' ) print(stu._Student__name, stu._Student__age)
Python中做出这样的设定是基于一句名言:“We are all consenting adults here ”(大家都是成年人)。Python语言的设计者认为程序员要为自己的行为负责,而不是由Python语言本身来严格限制访问可见性,而大多数的程序员都认为开放比封闭要好 ,把对象的属性私有化并不是必须的东西。
三、动态语言特性 Python是一门动态语言,维基百科对动态语言的解释是:“在运行时可以改变其结构的语言,例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。动态语言非常灵活,目前流行的Python和JavaScript都是动态语言,除此之外如PHP、Ruby等也都属于动态语言,而C、C++等语言则不属于动态语言”。
1. 动态添加属性
在Python中,我们可以动态为对象添加属性,这是Python作为动态类型语言的一项特权,代码如下所示。需要提醒大家的是,对象的方法其实本质上也是对象的属性,如果给对象发送一个无法接收的消息,引发的异常仍然是AttributeError
。
1 2 3 4 5 6 7 8 9 10 11 12 class Student : def __init__ (self, name, age) : self.name = name self.age = age stu = Student('张亚飞' , 20 ) stu.sex = '男' print(stu.sex) stu2 = Student('王小明' , 16 ) print(stu2.sex)
由以上代码可知,Student
类有两个属性:name
和age
。通过[对象名.属性名]给类对象stu
动态添加了对象属性sex
,而Student
的另一个类对象stu2
却不能调用这个属性。结论:通过对象名添加的对象属性,只有这个对象能使用。
1 2 3 Student.score = 100 print(stu.score) print(stu2.score)
由以上代码可知,通过[类名.属性名]给类Obj动态添加了类属性score
,Student
的类对象stu
和stu2
都能调用这个属性。注:通过类名添加的类属性,这个类的所有对象都能使用。
2.动态添加方法 类中有三种方法,实例方法,静态方法和类方法,三种方法的区别如下:
实例方法:需要绑定要一个对象上,第一个参数默认使用self
,会把对象作为第一个参数传递进来。
静态方法:使用装饰器@staticmethod
进行定义,类和对象都可以调用,不需要默认参数。
类方法:使用装饰器@classmethod
进行定义,类和对象都可以调用,第一个参数默认使用cls
,会把类作为第一个参数传递进来。
首先定义一个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from types import MethodTypeclass Obj (object) : def __init__ (self) : self.name = '张亚飞' def set_score (self, score) : self.score = score @staticmethod def static_func () : print('static_func' ) @classmethod def class_func (cls) : print('class_method' )
动态添加实例方法
1 2 3 4 5 6 7 obj = Obj() obj.set_score = MethodType(set_score, obj) obj.set_score(99 ) print(obj.score) obj2 = Obj() obj2.set_score(99 )
由以上代码可知,通过[types.MethodType(方法名, 对象名)]
给类对象obj
动态添加了实例方法set_score()
,同理,Obj
的另一个类对象obj2
不能调用这个方法。注:通过对象名添加的对象方法,只有这个对象能使用 。
动态添加静态方法
1 2 3 4 5 Obj.static_func = static_func Obj.static_func() obj.static_func() obj2.static_func()
由以上代码可知,通过[类名.静态方法名]给类Obj
动态添加了静态方法static_func()
,Obj
类的Obj
对象和obj2
对象都能调用这个方法。注:通过类名添加的静态方法,这个类及这个类的所有对象都能使用。
动态添加类方法
1 2 3 4 5 Obj.class_func = class_func Obj.class_func() obj.class_func() obj2.class_func()
由以上代码可知,通过[类名.类方法名]
给类Obj
动态添加了类方法class_func()
,Obj
类的obj
对象和obj2
对象都能调用这个方法。注:通过类名添加的类方法,这个类及这个类的所有对象都能使用。
3. 限制添加属性和方法 通过以上内容,我们知道了如何动态的添加属性和方法。但是,如果我们想要限制class的属性该怎么办?例如:只允许Obj
类的实例化对象添加name
和age
属性。为了达到这个目的,Python允许在定义class的时候,定义一个特殊变量__slots__
来限制该class能添加的属性。
1 2 3 4 5 6 7 8 9 10 11 class Obj (object) : __slots__ = ('name' , 'age' ) obj = Obj() obj.name = 'zhangyafei' obj.age = 23 obj.score = 99 Obj.score = 100 print(obj.score) obj.score = 99
通过以上代码可知,__slots__
对Obj
类的动态添加没有限制,而Obj
类对象obj
不能再动态添加对象属性和方法。
对于__slot__
有以下几个需要注意的地方:
__slots__
只对类对象进行限制,不对类进行限制
__slots__
不仅限制类对象的属性,还限制类对象的方法
__slots__
仅对当前类起作用,对继承的子类不起作用
在子类中定义__slots__
,子类允许定义的属性就是自身的__slots__
加上父类的__slots__
。
四、继承和多态 1. 继承 1.1 单继承 在程序中,继承描述的是事物之间的所属关系,例如猫和狗都属于动物,程序中便可以描述为猫和狗继承自动物;同理,波斯猫和巴厘猫都继承自猫,而沙皮狗和斑点狗都继承足够,如下图所示:
面向对象的编程语言支持在已有类的基础上创建新类,从而减少重复代码的编写。提供继承信息的类叫做父类(超类、基类),得到继承信息的类叫做子类(派生类、衍生类)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Animal : def eat (self) : print("-----吃------" ) def drink (self) : print("-----喝------" ) def sleep (self) : print("-----睡------" ) def run (self) : print("-----跑------" ) class Dog (Animal) : def bark (self) : print("---汪汪叫----" ) class Xiaotq (Dog) : def fly (self) : print("-----飞-----" ) def bark (self) : print("---狂叫-----" ) super().bark() class Cat (Animal) : def catch (self) : print("----抓老鼠---" ) xiaotq = Xiaotq() xiaotq.fly() xiaotq.bark() xiaotq.eat() wangcai = Dog() wangcai.eat() wangcai.bark() tom = Cat() tom.eat() tom.catch()
继承的语法是在定义类的时候,在类名后的圆括号中指定当前类的父类。如果定义一个类的时候没有指定它的父类是谁,那么默认的父类是object
类。object
类是Python中的顶级类,这也就意味着所有的类都是它的子类,要么直接继承它,要么间接继承它。Python语言允许多重继承,也就是说一个类可以有一个或多个父类,关于多重继承的问题我们在后面会有更为详细的讨论。在子类的初始化方法中,我们可以通过super().__init__()
来调用父类初始化方法,super
函数是Python内置函数中专门为获取当前对象的父类对象而设计的。从上面的代码可以看出,子类除了可以通过继承得到父类提供的属性和方法外,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力。在实际开发中,我们经常会用子类对象去替换掉一个父类对象,这是面向对象编程中一个常见的行为,也叫做“里氏替换原则”(Liskov Substitution Principle
)。所以,对于面向对象的继承来说,其实就是将多个类共有的方法提取到父类中,子类仅需继承父类而不必一一实现每个方法。注:除了子类和父类的称谓,你可能看到过 派生类和 基类** ,他们与子类和父类只是叫法不同而已。
私有变量和私有方法在继承中的表现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class A : def __init__ (self) : self.num1 = 100 self.__num2 = 200 def test1 (self) : print("----test1----" ) def __test2 (self) : print("-----test2----" ) def test3 (self) : self.__test2() print(self.__num2) class B (A) : def test4 (self) : self.__test2() print(self.__num2) b = B() b.test1() print(b.num1) b.test3()
方法重写
子类继承父类的方法后,还可以对方法进行重写(重新实现该方法),子类中一个方法与父类中一个方法名相同,但功能不同,调用子类该方法时会执行该子类中重写的这个方法。不同的子类可以对父类的同一个方法给出不同的实现版本,这样的方法在程序运行时就会表现出多态行为(调用相同的方法,做了不同的事情)。
1 2 3 4 5 6 7 8 9 10 11 12 class ShenXianBase (Base) : def fight (self) : print("神仙始祖们在天地边界打架。。。。" ) class ShenXian (ShenXianBase) : """神仙类""" def fight (self) : print("神仙在打架..." ) shenxian = ShenXian() shenxian.fight()
1.2 多继承 一个子类继承多个父类的情况叫做多继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Base (object) : def fight (self) : print("动物在打架...." ) class ShenXianBase (Base) : def fight (self) : print("神仙始祖们在天地边界打架。。。。" ) class ShenXian (ShenXianBase) : """神仙类""" def fly (self) : print("神仙都会飞..." ) def fight (self) : print("神仙在打架..." ) class MonkeyBase (Base) : pass class Monkey (MonkeyBase) : def eat_peach (self) : print("猴子都喜欢吃桃子..." ) class MonkeyKing (Monkey,ShenXian) : def play_goden_stick (self) : print("孙悟空玩金箍棒..." ) m = MonkeyKing() m.play_goden_stick() m.fly() m.eat_peach() m.fight()
由以上可知,当子类继承多个父类时,会继承其父类的方法。
那么多继承的属性和方法的查找顺序是怎么样呢?答:广度优先遍历 。
注:Python2中不声明object默认是经典类,深度优先,python3不管写不写object都是新式类,广度优先。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Base (object) : def test (self) : print("----Base" ) class A (Base) : pass class B (Base) : def test (self) : print("-----B" ) class C (A, B) : pass c = C() c.test() print(C.__mro__) """ (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>) """
2. 多态 多态是指一类事物有多种形态,比如动物类,可以有猫,狗,猪等等。(一个抽象类有多个子类,因而多态的概念依赖于继承)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import abcclass Animal (metaclass=abc.ABCMeta) : @abc.abstractmethod def talk (self) : pass class Cat (Animal) : def talk (self) : print('say miaomiao' ) class Dog (Animal) : def talk (self) : print('say wangwang' ) class Pig (Animal) : def talk (self) : print('say aoao' )
多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import abcclass Animal (metaclass=abc.ABCMeta) : @abc.abstractmethod def talk (self) : pass class Cat (Animal) : def talk (self) : print('say miaomiao' ) class Dog (Animal) : def talk (self) : print('say wangwang' ) class Pig (Animal) : def talk (self) : print('say aoao' ) c = Cat() d = Dog() p = Pig() def func (obj) : obj.talk() func(c) func(d) func(p)
综上可以说,多态性是 : 一个接口,多种实现
多态性的好处:
增加了程序的灵活性,以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(obj)
增加了程序的可扩展性,通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(obj)去调用
鸭子模型
调用不同的子类将会产生不同的行为,而无须明确知道这个子类实际上是什么,这是多态的重要应用场景。而在python中,因为鸭子类型(duck typing)使得其多态不是那么酷。
鸭子类型是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由”当前方法和属性的集合”决定。这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试,“鸭子测试”可以这样表述:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为”鸭子”的对象,并调用它的”走”和”叫”方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的”走”和”叫”方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的”走”和”叫”方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。
鸭子类型通常得益于不测试方法和函数中参数的类型,而是依赖文档、清晰的代码和测试来确保正确使用。
Duck typing 这个概念来源于美国印第安纳州的诗人詹姆斯·惠特科姆·莱利(James Whitcomb Riley,1849- 1916)的诗句:”When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.”
先上代码,也是来源于网上很经典的案例:
1 2 3 4 5 6 7 8 9 10 11 class Duck () : def walk (self) : print('I walk like a duck' ) def swim (self) : print('i swim like a duck' ) class Person () : def walk (self) : print('this one walk like a duck' ) def swim (self) : print('this man swim like a duck' )
可以很明显的看出,Person类拥有跟Duck类一样的方法,当有一个函数调用Duck类,并利用到了两个方法walk()和swim()。我们传入Person类也一样可以运行,函数并不会检查对象的类型是不是Duck,只要他拥有walk()和swim()方法,就可以正确的被调用。
再举例,如果一个对象实现了__getitem__
方法,那python的解释器就会把它当做一个collection,就可以在这个对象上使用切片,获取子项等方法;如果一个对象实现了__iter__
和__next__
方法,python就会认为它是一个iterator,就可以在这个对象上通过循环来获取各个子项。
最后通过一个支付的例子感受一下多态:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 from abc import ABCMeta, abstractmethod class PayMent (metaclass=ABCMeta) : @abstractmethod # 定义抽象方法的关键字 def pay (self, money) : pass class AliPay (PayMent) : def pay (self, money) : print(f'支付宝支付{money} 元' ) class WechatPay (PayMent) : def pay (self, money) : print(f'微信支付{money} 元' ) class ApplePay (PayMent) : def pay (self, money) : print(f'苹果支付{money} 元' ) def finish_pay (p, money) : p.pay(money) p1 = AliPay() p2 = WechatPay() p3 = ApplePay() finish_pay(p1, 100 ) finish_pay(p2, 200 ) finish_pay(p3, 300 )
五、创建类的三种方式
普通方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Obj (object) : x = 123 def func (self) : return 666 Obj1 = type('Obj1' ,(object,),{'x' :123 ,'func' :lambda self:666 }) obj = Obj() obj1 = Obj1() print(obj.x, obj.func()) print(obj1.x,obj1.func())
自定义type
1 2 3 4 5 6 7 8 9 10 11 12 13 class MyType (type) : pass class Obj (object, metaclass=MyType) : x = 123 def func (self) : return 666 Obj2 = MyType('Obj2' ,(object,),{'x' :123 ,'func' : lambda self:666 })
六、常用的魔法方法 Python中所有的类,均继承自一个叫Object
的父类。Object
类中定义了中众多以双下划线开头和结尾的方法,它们在面向对象的Python的处处皆是。它们是一些可以让你对类添加“魔法”的特殊方法。 它们经常是两个下划线包围来命名的(比如 __init__
, __repr__
)。下面代码中列出了常用的魔法方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 class Obj (object) : """限制对象添加属性""" __slots__ = ['storage' , 'stack_func' , 'num' , 'name' ] def __init__ (self) : """ 创建对象的时候若new返回一个对象会执行此方法 类名()""" object.__setattr__(self, 'storage' , {}) print('__init__' ) def __new__ (cls, *args, **kwargs) : """创建对象的时候会执行,返回一个对象 应用:单例/rest framework序列化 """ print('__new__' ) return super(Obj, cls).__new__(cls, *args, **kwargs) def __call__ (self) : """ 对象()会执行 应用:flask源码请求入口,django请求入口(WSGIHandler.__call__) """ print('__call__' ) def __hash__ (self) : """ hash()会执行 """ return id(self) def __str__ (self) : """ 调用对象会执行此函数 :return: string_obj 返回一个字符串对象 """ return '__str__' def __repr__ (self) : """ 转化为机器可以解释的语言 case 1: repr(object)时会执行此函数 case 2: 交互模式下打印对象会执行此函数 :return: 用于对象信息的显示 """ return '__repr__' def __getattr__ (self, item) : """当访问不存在的属性时会调用""" return '__getattr__' def __setattr__ (self, key, value) : """给对象设置属性的时候会调用""" print('__setattr__' ) if key == 'num' : object.__setattr__(self, key, value - 100 ) else : object.__setattr__(self, key, value) def __delattr__ (self, item) : """删除属性的时候会调用""" print('__delattr__' ) object.__delattr__(self, item) def __getattribute__ (self, item) : """访问任何属性的时候都会调用此方法""" print('__getattribute__' ) return super(Obj, self).__getattribute__(item) def __del__ (self) : """对象的生命周期执行结束之后执行""" print('__del__' ) def __setitem__ (self, key, value) : """obj[key] = value时会调用此方法""" print('__setitem__' ) self.storage[key] = value def __getitem__ (self, key) : """obj[key]会调用此方法""" return self.storage.get(key, None ) def __delitem__ (self, key) : """del obj[key]调用""" print('__delitem__' ) del self.storage[key] def __add__ (self, other) : return '__add__' def __sub__ (self, other) : return '__sub__' def __mul__ (self, other) : return '__mul' def __floordiv__ (self, other) : return '__floatdiv__' def __mod__ (self, other) : return '__mod__' def __divmod__ (self, other) : return '__divmod__' def __pow__ (self, power, modulo=None) : return '__pow__' obj = Obj() print(obj) obj() print(Obj.__mro__) obj.name = '__dict__' print(obj.__dict__) print(repr(obj)) print(obj.world) obj.num = 200 print(obj.num) del obj.num print(obj.storage) obj['name' ] = '张亚飞' print(obj.storage) print(obj['name' ]) del obj['name' ] print(obj['name' ]) print(obj + 7 ) print(obj - 1 ) print(obj * 1 ) print(obj // 1 ) print(obj % 3 ) print(obj.__divmod__(3 )) print(obj.__pow__(2 )) objx = Obj() print(hash(obj)) print(hash(objx)) """ 这里我们想让__setattr__执行默认行为,也就是将value赋值给name,和object对象中的同样方法,做类似的操作。 但是这里我们不调用父类__setattr__的方法来实现,做这样的尝试得到的结果就是,超过循环调用深度,报错。因为 这里在执行初始化方法self.world = world的时候,就会调用__setattr__方法,而这里的__setattr__方法里面的 self.name = value又会调用自身。所以造成了循环调用。所以使用该魔法方法的时候要特别注意。 """ class Friends (object) : def __init__ (self) : self.name = 'zhang' self.age = 23 def func (self) : print('__func__' ) class Xiaoming (Friends) : score = 99 def __init__ (self) : super(Xiaoming, self).__init__() self.run = 200 if __name__ == '__main__' : ll = [] dic = {} num = 3 f = Friends() print(f.__dict__) f.message = 'hello world' f.func = lambda x:x print(f.__dict__) print(Friends.__dict__) xiaoming = Xiaoming() print(xiaoming.__dict__) print(Xiaoming.__dict__) """ 1. 一些内置数据类型没有__dict__ 2. 实例的__dict__存有与实例相关的实例变量和函数. 类的__dict__则是和实例共享的变量,函数(方法,类属性).注意,类的__dict__并不包含其父类的属性. 3. 对象也有自己的__dict__属性, 存储self.xxx 信息,父子类对象公用__dict__ """ class BAR (object) : def __init__ (self, cls) : self.cls = cls class NEW_OBJ (object) : def __new__ (cls, *args, **kwargs) : return BAR(cls) obj = NEW_OBJ() print(obj) """new方法的返回值决定对象到底是什么""" class Obj4 () : def __enter__ (self) : print('__enter__' ) def __exit__ (self, exc_type, exc_val, exc_tb) : print('__exit__' ) with Obj4(): print('执行中' ) """with 对象默认执行__enter__方法,执行结束执行__exit__方法""" class Obj5 (object) : pass setattr(Obj5,'send' , lambda x:1 ) obj5 = Obj5() print(obj5.send()) """ 对象可以被for循环 """ class Obj (object) : def __iter__ (self) : yield 1 yield 2 yield 3 obj = Obj() for item in obj: print(item)
七、简单的总结 Python中拥有字段、方法和属性三种成员,属性看起来像字段,但实际上是方法的变种,有时我们描述的时候不会严格区分字段和属性,大多数情况下将字段、变量、属性统一称为属性。同时,Python为了保护私有变量、私有方法不被用户随意访问,制定了变量名或方法名前加下划线的规则来对用户进行约束。Python是动态语言,Python中的对象可以动态的添加属性。在面向对象的世界中,一切皆为对象 ,我们定义的类也是对象,所以类也可以接收消息 ,对应的方法是类方法或静态方法。通过继承,我们可以从已有的类创建新类 ,实现对已有类代码的复用,多态是面向对象非常重要的特性。