python 3.x 深入类和对象
深入类和对象
鸭子类型和多态
- 维基百科摘要:
- 当看到一只鸟走起来像鸭子,游泳起来像鸭子,叫起来也像鸭子,那么这只鸟就可以被称为鸭子
- 个人理解鸭子类型:
- 多个类或者说多个对象都实现至少一个共同的方法,这个方法名一定要一样,这样的话这些类可以归为一种类型
- python中的鸭子类型类似于java中的多态
-
在python中不需要子类继承任何父类,只需要多个类实现至少一个共同名字的方法,即可实现python中所谓的多态.
-
在python中没有多态的概念
-
python不像java语言需要先定义一个抽象类并在其中定义一些抽象的方法,让子类继承抽象类,并覆写父类的方法,从而实现java的多态.
-
python中的内置函数(魔法函数)的实现方式就是鸭子类型(所谓的多态)
-
示例1:
1 """ 2 鸭子类型和多态 3 当看到一只鸟走起来像鸭子,游泳起来像鸭子,叫起来也像鸭子,那么这只鸟就可以被称为鸭子(维基百科摘要) 4 5 python中的鸭子类型类似于java中的多态 6 在python中不需要子类继承任何父类,只需要多个类实现至少一个共同名字的方法,即可实现python中所谓的多态. 7 在python中没有多态的概念 8 python不像java语言需要先定义一个抽象类并在其中定义一些抽象的方法,让子类继承抽象类,并覆写父类的方法, 9 从而实现java的多态. 10 11 python中的内置函数(魔法函数)的实现方式就是鸭子类型(所谓的多态) 12 """ 13 14 15 class Cat(object): 16 def say(self): 17 print("i am a cat") 18 19 20 class Dog(object): 21 def say(self): 22 print("i am a dog") 23 24 25 class Duck(object): 26 def say(self): 27 print("i am a duck") 28 29 30 animal_list = [Cat, Dog, Duck] 31 32 # 结果: 33 # i am a cat 34 # i am a dog 35 # i am a duck 36 for animal in animal_list: 37 animal().say() 38 39 """ 40 类Cat,Dog,Duck实现同一个方法say(),从而实现鸭子类型 41 """
示例2:
1 a = ["python", "java"] 2 b = ["hello", "world"] 3 name_tuple = ("python1", "java1") 4 name_set = set() 5 name_set.add("jayden") 6 name_set.add("june") 7 8 9 class Company(object): 10 def __init__(self, employee_list): 11 self.employee_list = employee_list 12 13 def __getitem__(self, item): 14 return self.employee_list[item] 15 16 def __len__(self): 17 # 参数item是序列的索引 18 return len(self.employee_list) 19 20 21 company = Company(["tom", "jerry", "page"]) 22 23 """ 24 列表的extend(self, iterable)方法,参数必须是可迭代的类型iterable. 25 在python中所有的方法参数是告知你需要传递什么类型(比如:可迭代的类型),而不是一个具体的对象类型(比如:list,set等). 26 以为list,set,tuple,Company都是可迭代的类型或者说是对象,因此可以作为extend()方法的参数. 27 自定义Company对象,extend()方法会到Company对象中查找是由有魔法函数__iter__(),如果没有回去找魔法函数__getitem__() 28 __iter__()函数表示一个对象是可迭代的类型,__getitem__()函数表示一个对象是可迭代的序列类型. 29 """ 30 31 # 以下是独立运行的结果. 32 33 # 结果: ['python', 'java', 'hello', 'world'] 34 a.extend(b) 35 36 # 结果: ['python', 'java', 'python1', 'java1'] 37 a.extend(name_tuple) 38 39 # 结果: ['python', 'java', 'june', 'jayden'] 40 a.extend(name_set) 41 42 # 结果: ['python', 'java', 'tom', 'jerry', 'page'] 43 a.extend(company) 44 45 print(a) 46 47 48 """ 49 以上的结果证明,python的鸭子类型贯穿整个python. 50 """
抽象基类( abc模块 (abstract class的缩写) )
- 抽象基类是什么???
- 在基础类当中设定好一些方法,然后所有继承基类的子类都必须覆盖抽象基类中所有的方法
- 在python中提供两种abc模块
- collections下的abc模块 ( import abc )
- python下全局abc模块. ( from collections.abc import * )
- 抽象基类类似于java语言中的接口
- 抽象基类是不能实例化的
- python是动态语言,动态语言是没有变量类型的,在python中变量只是一个符号而已,变量可以指向任何类型的对象
所以说在python中不存在多态的概念,我们可以复制任何类型的数据给python中的任何变量,而且是可以修改的.
不需要像java一样去实现一个多态.因为python本身从语言的层面上来讲,python就是一个支持多态的语言. - 动态语言与静态语言最大的区别:
- 在于动态语言不需要指明变量的类型.所以动态语言中间就少了一个编译时检查错误的环境,在python中如果写错代码,事先是很难知道的.只有在运行的时候才会发现错误,这就是动态语言共有的缺陷,无法做类型检查
- python信奉的是鸭子类型,鸭子类型贯穿于整个python的面向对象的过程当中.在设计python类的时候,一定把鸭子类型放在第一位.
- 与java最大区别在于实现一个class的时候,不需要去继承指定的类型
- 在魔法函数中说过,一个类究竟有什么特性或者说属于什么类型,是看这个类到底实现了什么魔法函数.不同的魔法函数赋予类不同的特性.不需要继承某一个指定的抽象类.
- 鸭子类型是动态语言设计时的一个优秀的理念.
- 鸭子类型和魔法函数构成了整个python语言的一个基础,也就是python里面的协议,因为python本身不是去通过继承某一个类或者说去实现某一个接口就有某些特性,
而是只需要去实现指定的魔法函数后,这个类就是某种类型的对象不同的魔法函数它具有的特性,使类实现魔法函数之后.对象就会被指定一种类型.这种特性在python中也称为协议.
这种事先约定好的做法可以称之为协议,在编程的时候尽量遵循这种协议.这样写出来的代码才更pythonic. - 虽然python中提供了类似于静态语言的抽象基类模式,但实际上python在重用python的鸭子类型.所以说鸭子类型才是python的根本.
- 抽象基类两个用途(也是抽象基类的好处):
- isinstance()中使用collections.abc模块中的抽象基类.
- 用来做接口的强制规定.
- 抽象基类在python中不推荐使用.
- collections.abc模块中抽象基类不是用来继承的,而是为了加深对这些接口里面函数的理解,
也可以把collections.abc模块中抽象基类的代码理解为代码文档.通过代码的形式让我们以一种类似文档的体验去搞懂collections.abc模块的抽象基类有哪些是共同构成python中(dict,list等)的接口,
这样可知道(dict,list等)有哪些函数(在这里只做简单说明,具体在后面的文章中详细说明). - collections.abc模块中抽象基类不是用来继承的,主要用来让我们理解python中的继承关系以及接口定义.所以我们自己在使用的时候,尽量使用鸭子类型.因为他非常灵活.
- 如果一定要继承某些接口的话,推荐使用mixin这种多继承方式来实现而不是使用抽象基类,因为抽象基类在使用时很容易设计过度,反而不容易理解.
- python是基于鸭子类型来设计的,那为什么多出一个抽象基类的概念呢???直接去实现某些方法不就行了吗???
- 为了解释这个问题,假设两种应用场景:
- 检查某个类是否具有某种方法
-
- 在某些情况下希望判定某个对象的类型,使用抽象基类(collection.abc模块下)
2.需要强制某个子类必须实现某些方法.使用抽象基类
collections.abc模块中的抽象基类Sized的源码
1 class Sized(metaclass=ABCMeta): 2 __slots__ = () 3 4 def __len__(self): 5 return 0 6 7 def __subclasshook__(cls, C): 8 if cls is Sized: 9 return _check_methods(C, "__len__") 10 return NotImplemented
1 """检查某个类是否具有某种方法""" 2 3 4 class Company(object): 5 def __init__(self, employee_list): 6 self.employee_list = employee_list 7 8 def __len__(self): 9 # 参数item是序列的索引 10 return len(self.employee_list) 11 12 13 company = Company(["tom", "jayden", "python"]) 14 15 # 结果: 3 16 print(len(company)) 17 18 # 如何检查company是否具有长度的类型,也就是如何判断一个类中有__len__函数的呢??? 19 # hasattr(obj, "魔法函数的名称"): 对某一个对象进行判断,是否具有某一个属性(在类当中函数其实也是一个属性). 20 # 结果: True --> 通过结果可以证明company是具有__len__()函数 21 print(hasattr(company, "__len__")) 22 23 """ 24 通hasattr()方法来判断company是否具有__len__()函数,但是通常不这样做, 25 而是通过instance(obj,"类型名称")方法来判断某个对象是否是某种类型. 26 isinstance()是python内置函数,它可以判断某种类或者对象是否是某种类型. 27 在python中collection.abc模块中有很多抽象基类,而这些基类继承ABCMeta. 28 python中所有的抽象基类的的metaclass的值都必须是ABCMeta. 29 """ 30 31 from collections.abc import Sized 32 33 34 """ 35 isinstance(company, Sized)中company并没有继承Sized,但是能够判断company是一个Sixed类型,为什么??? 36 在Sized抽象基类中实现了一个魔法函数__subclasshook__(cls, C).在__sunclasshook__()魔法函数中, 37 执行方法_check_methods(C,"__len__")来判断传递进来的对象(C)中是否有魔法函数__len__(), 38 如果有返回True.这时利用抽象基类中的魔法函数__subclasshook__()返回的结果给isinstance()方法得出的结果 39 40 isinstance()是python的内置函数,它的内部实现不是我们所看到的那么简单,实际上isinstance()方法内部会去 41 做很多的尝试. 42 比如: 43 class A: 44 pass 45 class B(A): 46 pass 47 b = B() 48 isinstance(b, A) --> True 49 50 以上示例证明isinstance()内部函数不仅仅只是简单的去调用抽象基类Sized的__subclasshook__()函数, 51 isinstance()还会去做其他的尝试,比如会找到A,B之间的继承链.就可以知道b是否是A类型. 52 """ 53 # 结果: True 54 print(isinstance(company, Sized))
1 """ 2 如何模拟一个抽象基类 3 """ 4 5 """ 6 需求: 7 需要强制某个子类必须实现某些方法 8 比如自己实现一个web框架,集成cache缓存(首先这个缓存希望未来可以使用redis,cache,memorycache等来替换) 9 10 思路: 设计一个抽象类,指定子类必须实现某些方法. 11 12 在写框架的时候我们不知道未来的用户使用redis,cache,memorycache等缓存来替换 13 但是又希望用户在集成缓存之后,尽量减少自己去调用cache的代码,所以说事先约定一个组件CacheBase抽象基类 14 """ 15 16 17 class CacheBase(): 18 def get(self, key): 19 pass 20 21 def set(self, key, value): 22 pass 23 24 25 class RedisCache(CacheBase): 26 pass 27 28 29 # 如果子类继承父类,没有覆写父类的方法, 30 # 同时也不在get()和set()函数中实现raise NotImplementedError(抛异常). 31 # 代码执行后是没有任何结果 32 redis_cache = RedisCache() 33 redis_cache.set("key", "value")
1 """ 2 如何模拟一个抽象基类 3 """ 4 5 """ 6 需求: 7 需要强制某个子类必须实现某些方法 8 比如自己实现一个web框架,集成cache缓存(首先这个缓存希望未来可以使用redis,cache,memorycache等来替换) 9 10 思路: 设计一个抽象类,指定子类必须实现某些方法. 11 12 在写框架的时候我们不知道未来的用户使用redis,cache,memorycache等缓存来替换 13 但是又希望用户在集成缓存之后,尽量减少自己去调用cache的代码,所以说事先约定一个组件CacheBase抽象基类 14 """ 15 16 17 class CacheBase(): 18 def get(self, key): 19 raise NotImplementedError 20 21 def set(self, key, value): 22 raise NotImplementedError 23 24 25 class RedisCache(CacheBase): 26 pass 27 28 29 # 如果子类继承父类,没有覆写父类的方法, 30 # get()和set()函数里面抛出异常raise NotImplementedError. 31 # 执行代码后,会抛出异常NotImplementedError 32 redis_cache = RedisCache() 33 redis_cache.set("key", "value")
1 """ 2 如何模拟一个抽象基类 3 """ 4 5 """ 6 需求: 7 需要强制某个子类必须实现某些方法 8 比如自己实现一个web框架,集成cache缓存(首先这个缓存希望未来可以使用redis,cache,memorycache等来替换) 9 10 思路: 设计一个抽象类,指定子类必须实现某些方法. 11 12 在写框架的时候我们不知道未来的用户使用redis,cache,memorycache等缓存来替换 13 但是又希望用户在集成缓存之后,尽量减少自己去调用cache的代码,所以说事先约定一个组件CacheBase抽象基类 14 """ 15 16 17 class CacheBase(): 18 def get(self, key): 19 raise NotImplementedError 20 21 def set(self, key, value): 22 raise NotImplementedError 23 24 25 class RedisCache(CacheBase): 26 def set(self, key, value): 27 pass 28 29 30 # 子类继承父类,覆写父类的set()方法, 31 # get()和set()函数里面抛出异常raise NotImplementedError. 32 # 执行代码后,由于子类覆写父类的set()方法,当调用set()方法时,调用的是子类的set()方法,而不是父类的set()方法,所以就不会抛出异常 33 redis_cache = RedisCache() 34 redis_cache.set("key", "value") 35 36 # 以上,是一个简单的模拟抽象基类的方法.但是这个方法有一个不好的地方是, 37 # 当redis_cache.set("key", "value")时,只有调用set()方法的时候,才会抛异常NotImplementedError
1 """ 2 如何模拟一个抽象基类 3 """ 4 5 """ 6 这是一个简单的模拟抽象基类的方法.但是这个方法有一个不好的地方是, 7 当redis_cache.set("key", "value")时,只有调用set()方法的时候,才会抛异常NotImplementedError. 8 9 --------------------------------------------------- 10 | from collections.abc import * | 11 | | 12 | class CacheBase(): | 13 | def get(self, key): | 14 | raise NotImplementedError | 15 | | 16 | def set(self, key, value): | 17 | raise NotImplementedError | 18 | | 19 | class RedisCache(CacheBase): | 20 | pass | 21 | | 22 | redis_cache = RedisCache() | 23 | redis_cache.set("key", "value") | 24 --------------------------------------------------- 25 26 那希望在类的初始化的时候,就开始抛出异常. 27 如何来实现???这就需要使用python全局的abc模块() 28 关于abc模块,python存放在两个地方,一个是全局的abc,一个collection模块下的abc 29 重点了解全局下的abc模块 30 """ 31 import abc 32 33 34 class CacheBase(metaclass=abc.ABCMeta): 35 @abc.abstractclassmethod 36 def get(self, key): 37 pass 38 39 @abc.abstractclassmethod 40 def set(self, key, value): 41 pass 42 43 44 class RedisCache(CacheBase): 45 pass 46 47 48 # 实例化时抛出异常TypeError: Can't instantiate abstract class RedisCache with abstract methods get, set 49 # 强制必须实现get()和set()方法. 50 # 使用抽象基类,强制子类实现某些方法.这就抽象基类的好处 51 redis_cache = RedisCache() 52 # redis_cache.set("key", "value") 53 54 # 在python源码中实现了一些通用的抽象基类 55 # 抽象基类不推荐大家使用,在其他三方框架中abc模块使用的比较少的. 56 # 在开发中推荐使用鸭子类型,因为鸭子类型非常灵活 57 # 抽象基类collection.abc实际上是python提供给我们对抽象基类的了解,而不是开发使用. 58 # 推荐使用多继承来实现我们想要的功能(mixin)
使用isinstance()而不是type()
举例说明原因(判断类型推荐使用isinstance())
1 class A(): 2 pass 3 4 5 class B(A): 6 pass 7 8 9 b = B() 10 11 12 """ 13 isinstance()方法内部实现检测继承链的功能 14 isinstance()方法会根据继承关系,会自动去查找.判断b是否在继承链里. 15 因为B继承A,A是链条的顶端,isinstance()会顺着链条往上找. 16 所以使用isinstance()方法来判断类型(不要使用type()来判断类型) 17 """ 18 # 结果: True 19 print(isinstance(b, B)) 20 # 结果: True 21 print(isinstance(b, A)) 22 23 24 """ 25 is身份运算符,判断是否是一个对象(判断id值是否相同) 26 ==比较运算符,判断两个变量值是否相等. 27 28 判断b的类型,type(b)实际是指向B类,B是模板对象,B在全局中只有一个对象. 29 因此type(b)指向B类,B也是指向B类,这样他们的id值相同,所以print(type(b) is B)结果是True 30 31 type(b)指向B类,B是一个对象,虽然B类继承A类,但是他们是两个不同的对象,A类是另外一个对象. 32 因此type(b)和A类的id值不同,所以print(type(b) is A)结果是False 33 34 """ 35 36 # 结果: <class '__main__.B'> 37 print(type(b)) 38 # 结果: True 39 print(type(b) is B) 40 # 结果: False 41 print(type(b) is A)
类变量与实例变量以及查找顺序
类变量与实例变量示例
1 class A: 2 # 类变量 3 aa = 1 4 5 def __init__(self, x, y): 6 # 实例变量 7 self.x = x 8 self.y = y 9 10 11 a = A(2, 3) 12 13 A.aa = 11 14 15 # 此处的a.aa = 100 的意思是在a对象中创建一个新的变量aa并赋值100.其他A的实例不具有实例变量aa 16 # 当执行a.aa语句的时候.会先到实例变量中查找是否有aa变量,没有的话到类变量查找变量aa. 17 a.aa = 100 18 print(a.x, a.y) 19 # 因为执行a.aa=100语句,所以a对象中有实例变aa,所以结果是100 20 print(a.aa) 21 # 此处是类变量aa,所以结果是11. 22 print(A.aa) 23 24 # 获取a对象中所有的实例变量{'x': 2, 'y': 3, 'aa': 100} 25 print(a.__dict__) 26 27 b = A(5, 3) 28 # 结果: 5 3 29 print(b.x, b.y) 30 # 结果: 11 31 print(b.aa) 32 # 获取b对象中所有的实例变量{'x': 5, 'y': 3},b中不具有实例变量aa 33 print(b.__dict__)
类属性和实例属性的查找顺序--MRO查找
- 在多继承的情况下,类属性和实例属性的查找顺序相对比较复杂
- MRO算法 : Method Resolution Order 方法查询顺序
- DFS算法 : Deep First Search 深度优先搜索算法
- BFS算法 : Breadth First Search 广度优先搜索算法
- python3使用的是MRO算法,而MRO算法的内部实现使用的是C3算法
- 在Python2.2以前如果一个类没有写继承object类,那么该类是自动不会继承object类.
- 在Python3以后所有的类默认继承object类,不管写没写继承object类.
- 这种继承关系使用BFS算法会出现问题,如果D,C类中有一个同名的方法get()的话,在B类中没有查找到get()方法,遵循BFS算法,会到C类查找get()方法.
- 但是B类继承D类,而C类没有继承D类.所以B类和D类应该成为一个整体.因为C类没有继承D类,同时B类在C类之前被A类继承.所以说D类应该在C类之前先被查找(B类找不到get()方法,直接到D类查找get()方法,这种查找顺序是合理的)
- 如果BFS算法的话,B类中没有找到get()方法,先查找C类中查找,这时C类中的get()方法会覆盖掉D类中get()方法.所以说BFS算法不适用这种继承关系.
-
1 """ 2 通过__mro__查看继承关系的查找顺序. 3 """ 4 5 6 class D: 7 pass 8 9 10 class E: 11 pass 12 13 14 class B(D): 15 pass 16 17 18 class C(E): 19 pass 20 21 22 class A(B, C): 23 pass 24 25 26 # 结果: 27 # (<class '__main__.A'>, 28 # <class '__main__.B'>, 29 # <class '__main__.D'>, 30 # <class '__main__.C'>, 31 # <class '__main__.E'>, 32 # <class 'object'>) 33 print(A.__mro__)
- 菱形继承关系,这种继承关系使用DFS算法会出问题,因为B,C类同时继承D类,如果C类中覆写D类中的get()方法,B类中没有覆写get()方法.
- 这时调用A类get()方法的时候,会到B类查找get()方法,如果没有找到按照DFS算法会到D类中查找get()方法.
- 然而C类永远起不到覆写D类中get()方法的效果.因为深度优先搜索,在D类中查找到get()方法后,将停止查找.C中的get()方法永远不会被查找.
- 所以DFS算法不适用于菱形继承关系.
-
1 """ 2 通过__mro__查看继承关系的查找顺序. 3 """ 4 5 6 class D: 7 pass 8 9 10 class B(D): 11 pass 12 13 14 class C(D): 15 pass 16 17 18 class A(B, C): 19 pass 20 21 22 # 结果: 23 # (<class '__main__.A'>, 24 # <class '__main__.B'>, 25 # <class '__main__.C'>, 26 # <class '__main__.D'>, 27 # <class 'object'>) 28 print(A.__mro__)
- 综上两个图的结论:
- 在python2.3以后一直到现在python3,继承关系搜索顺序算法统一成一种算法 --> C3算法.
- C3算法大概原理是根据继承关系,拟定一个适合该继承关系的查找顺序算法(DFS,BFS)
类方法,静态方法,实例方法
- 举例说明
1 """ 2 通过举例说明类方法,静态方法,实例方法 3 4 需求: 5 1.将输入的年月日,拼接成指定格式的字符串输出"xxxx/xx/xx" 6 """ 7 8 9 class Date: 10 def __init__(self, year, month, day): 11 self.year = year 12 self.month = month 13 self.day = day 14 15 def __str__(self): 16 return "{year}/{month}/{day}".format(year=self.year, month=self.month, day=self.day) 17 18 19 new_date = Date(2019, 3, 11) 20 21 # 结果: 2019/3/11 22 print(new_date)
1 """ 2 通过举例说明类方法,静态方法,实例方法 3 4 需求: 5 1.将输入的年月日,拼接成指定格式的字符串输出"xxxx/xx/xx" 6 2.获取明天的年月日 7 因为对实例变量进行操作,所以需要定义实例方法来操作实例变量. 8 """ 9 10 11 class Date: 12 def __init__(self, year, month, day): 13 self.year = year 14 self.month = month 15 self.day = day 16 17 def tomorrow_date(self): 18 """ 19 实例方法 20 :return: 21 """ 22 self.day += 1 23 24 def __str__(self): 25 return "{year}/{month}/{day}".format(year=self.year, month=self.month, day=self.day) 26 27 28 new_date = Date(2019, 3, 11) 29 30 # self参数不需要传递.内部实现是系统自动将对象传递给tomorrow_date(new_date) 31 new_date.tomorrow_date() 32 33 # 结果: 2019/3/12 34 print(new_date)
1 """ 2 通过举例说明类方法,静态方法,实例方法 3 4 需求: 5 1.将输入的年月日,拼接成指定格式的字符串输出"xxxx/xx/xx" 6 2.获取明天的年月日 7 因为对实例变量进行操作,所以需要定义实例方法来操作实例变量. 8 3.将输入的年月日字符串("2019-3-11")进行处理后输出指定格式. 9 在类外部实现需求3 10 """ 11 12 13 class Date: 14 def __init__(self, year, month, day): 15 self.year = year 16 self.month = month 17 self.day = day 18 19 def tomorrow_date(self): 20 """ 21 实例方法 22 :return: 23 """ 24 self.day += 1 25 26 def __str__(self): 27 return "{year}/{month}/{day}".format(year=self.year, month=self.month, day=self.day) 28 29 30 str_date = "2019-3-11" 31 32 # 拆包 33 year, month, day = str_date.split("-") 34 35 new_date = Date(year, month, day) 36 37 # 结果: 2019/3/11 38 print(new_date)
1 """ 2 通过举例说明类方法,静态方法,实例方法 3 4 需求: 5 1.将输入的年月日,拼接成指定格式的字符串输出"xxxx/xx/xx" 6 2.获取明天的年月日 7 因为对实例变量进行操作,所以需要定义实例方法来操作实例变量. 8 3.将输入的年月日字符串("2019-3-11")进行处理后输出指定格式. 9 在类外部实现需求3(优化) 10 使用静态方法来实现需求3. 11 """ 12 13 14 class Date: 15 def __init__(self, year, month, day): 16 self.year = year 17 self.month = month 18 self.day = day 19 20 def tomorrow_date(self): 21 """ 22 实例方法 23 :return: 24 """ 25 self.day += 1 26 27 @staticmethod 28 def parse_from_string(str_date): 29 # 拆包 30 year, month, day = str_date.split("-") 31 # 此处Date()属于硬编码,如果更改类名的话,同时此处也需要更改.使用类方法实现会更好 32 return Date(year, month, day) 33 34 def __str__(self): 35 return "{year}/{month}/{day}".format(year=self.year, month=self.month, day=self.day) 36 37 38 str_date = "2019-3-1" 39 40 new_date = Date.parse_from_string(str_date) 41 42 # 结果: 2019/3/1 43 print(new_date)
1 """ 2 通过举例说明类方法,静态方法,实例方法 3 4 需求: 5 1.将输入的年月日,拼接成指定格式的字符串输出"xxxx/xx/xx" 6 2.获取明天的年月日 7 因为对实例变量进行操作,所以需要定义实例方法来操作实例变量. 8 3.将输入的年月日字符串("2019-3-11")进行处理后输出指定格式. 9 在类外部实现需求3(优化) 10 使用静态方法来实现需求3.(优化) 11 使用类方法来实现需求3,解决硬编码的问题 12 """ 13 14 15 class Date: 16 def __init__(self, year, month, day): 17 self.year = year 18 self.month = month 19 self.day = day 20 21 def tomorrow_date(self): 22 """ 23 实例方法 24 :return: 25 """ 26 self.day += 1 27 28 @staticmethod 29 def parse_from_string(str_date): 30 # 拆包 31 year, month, day = str_date.split("-") 32 # 此处Date()属于硬编码,如果更改类名的话,同时此处也需要更改.使用类方法实现会更好 33 return Date(year, month, day) 34 35 @classmethod 36 def parse_string(cls, str_date): 37 # 拆包 38 year, month, day = str_date.split("-") 39 # 此处解决静态方法中硬编码的问题,类名不管怎么改变,此处代码不需要更改. 40 return cls(year, month, day) 41 42 def __str__(self): 43 return "{year}/{month}/{day}".format(year=self.year, month=self.month, day=self.day) 44 45 46 str_date = "2019-3-12" 47 48 new_date = Date.parse_string(str_date) 49 50 # 结果: 2019/3/12 51 print(new_date)
1 """ 2 通过举例说明类方法,静态方法,实例方法 3 4 需求: 5 1.将输入的年月日,拼接成指定格式的字符串输出"xxxx/xx/xx" 6 2.获取明天的年月日 7 因为对实例变量进行操作,所以需要定义实例方法来操作实例变量. 8 3.将输入的年月日字符串("2019-3-11")进行处理后输出指定格式. 9 在类外部实现需求3(优化) 10 使用静态方法来实现需求3.(优化) 11 使用类方法来实现需求3,解决硬编码的问题 12 4.使用静态方法来判断输入的字符串是否是一个合法的时间字符串 13 静态方法与类的业务逻辑相关,但是不能对类属性和实例属性进行操作. 14 此时对输入的字符串是否是合法的时间字符串进行判断,与类Date逻辑有关,同时没有对类属性和实例属性进行操作 15 """ 16 17 18 class Date: 19 def __init__(self, year, month, day): 20 self.year = year 21 self.month = month 22 self.day = day 23 24 def tomorrow_date(self): 25 """ 26 实例方法 27 :return: 28 """ 29 self.day += 1 30 31 @staticmethod 32 def parse_from_string(str_date): 33 year, month, day = str_date.split("-") 34 return Date(year, month, day) 35 36 @classmethod 37 def parse_string(cls, str_date): 38 year, month, day = str_date.split("-") 39 return cls(year, month, day) 40 41 @staticmethod 42 def valid_date_string(str_date): 43 year, month, day = str_date.split("-") 44 # 此处做简单的时间判断,真正的时间格式判断有正则表达式 45 if int(year) > 0 and (int(month) > 0 and int(month) <= 12) and (int(day) > 0 and int(day) <= 31): 46 return True 47 else: 48 return False 49 50 def __str__(self): 51 return "{year}/{month}/{day}".format(year=self.year, month=self.month, day=self.day) 52 53 54 str_date = "2019-3-12" 55 str_date1 = "2019-3-42" 56 57 # True 58 print(Date.valid_date_string(str_date)) 59 60 # False 61 print(Date.valid_date_string(str_date1))
数据封装和私有变量
- 举例说明
1 """ 2 数据封装和私有属性 3 """ 4 5 from chapter04.class_method_06 import Date 6 7 8 class User: 9 def __init__(self, birthday): 10 self.__birthday = birthday 11 12 def get_age(self): 13 return 2019 - self.__birthday.year 14 15 16 user = User(Date(1988, 3, 11)) 17 18 # {'_User__birthday': <chapter04.class_method_06.Date object at 0x00000000027EEC18>} 19 print(user.__dict__) 20 21 # 外界访问不到user的私有变量.会报错:User对象没有属性'xxx' 22 # 结果: 'User' object has no attribute '__birthday' 23 print(user.__birthday) 24 25 # python将私有变量变化成一种格式: _classname(类名)__privatevarname(私有变量名). 26 # 结果: 1988/3/11 27 print(user._User__birthday) 28 29 # 结果: 31 30 print(user.get_age())
python对象的自省机制
- 自省是通过一定的机制查询到对象的内部结构.
- __dict__: 获取对象的自定义属性
- dir(): 列出对象的所有特性,如:魔法函数等
示例
1 class Person: 2 name = "person" 3 4 5 class Student(Person): 6 """ 7 学生 8 """ 9 10 def __init__(self, school_name): 11 self.school_name = school_name 12 13 14 stu = Student("光明路小学") 15 16 # 通过__dict__查询属性 17 # 结果: {'school_name': '光明路小学'} 18 print(stu.__dict__) 19 20 # 结果: {'__module__': '__main__', '__doc__': '\n 学生\n ', '__init__': <function Student.__init__ at 0x000000000251FBF8>} 21 print(Student.__dict__) 22 23 # 结果: {'__module__': '__main__', 'name': 'person', '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None} 24 print(Person.__dict__) 25 26 # dir()函数,列出对象的所有属性. 27 # 结果: 28 # ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', 29 # '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', 30 # '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', 31 # '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 32 # 'name', 'school_name'] 33 print(dir(stu)) 34 35 a = [1, 2] 36 # 列出列表所有的特性(包括魔法函数) 37 # 结果: 38 # ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', 39 # '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', 40 # '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', 41 # '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', 42 # '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', 43 # '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 44 # 'remove', 'reverse', 'sort'] 45 print(dir(a))
super()函数
1 """ 2 以下代码,得出结论,super()调用父类的构造函数. 3 这个结论仅限于此代码的结论.此结论是不准确的.后面的代码会详细说明. 4 """ 5 6 7 class A: 8 def __init__(self): 9 print("A") 10 11 12 class B(A): 13 def __init__(self): 14 super().__init__() 15 print("B") 16 17 18 if __name__ == '__main__': 19 # 执行结果: 20 # B 21 # A 22 b = B()
super()调用父类的构造函数以及其他方法,提高代码的复用性
1 """ 2 既然我们重写B的构造函数,为什么还要去调用super??? 3 通过下面的示例得出结论,父类的构造函数已经写好name,age的传递逻辑,不需要再在子类做重复的传递逻辑. 4 为了开发效率以及代码的复用性,所以需要super来调用父类写好的逻辑,提高代码的复用性. 5 """ 6 7 8 class A: 9 def __init__(self, name, age): 10 self.name = name 11 self.age = age 12 13 14 class B(A): 15 def __init__(self, name, age, school): 16 super().__init__(name, age) 17 self.__school = school 18 19 def __str__(self): 20 return "我是%s,今年%d岁,在%s上学." % (self.name, self.age, self.__school) 21 22 23 if __name__ == '__main__': 24 b = B("python", 8, "光明路小学") 25 26 # 结果: 我是python,今年8岁,在光明路小学上学. 27 print(b)
super()真的是调用父类吗???
1 """ 2 super到底执行顺序是什么样的??? 3 4 我们之前说的super.__init__()是调用父类的构造函数,猜想下面的代码打印结果的顺, 5 首先执行D的构造函数,D的构造函数里执行调用父类的构造函数B,C.由于B在C的前面,应该先调用B的构造函数, 6 而B的构造函数也执行调用父类的函数,所以执行A的构造函数,打印顺序是D->B->A 7 但是猜想的结果与实际打印的结果不同(D->B->C->A) 8 所以推翻了之前说的super调用父类的构造函数. 9 10 关键点是super()不是调用父类的构造函数. 11 而是super()遵循MRO算法的执行顺序,调用MRO这个顺序里的下一个类的构造函数. 12 13 根据MRO算法顺序分析下面的代码: 14 通过D.__mro__的执行顺序(D->B->C->A). 15 D的构造函数的super().__init__()是调用B类的构造函数, 16 而B的构造函数中的super().__init__()是调用C类的构造函数, 17 而C的构造函数中的super().__init__()是调用A类的构造函数. 18 从而打印结果是 D->B->C->A 19 20 综上分析: 21 证明super是遵循MRO算法的执行顺序,来调用顺序中的下一个类的构造函数,而不是单纯的调用父类的构造函数. 22 这种结论适用于下一个类中的所有属性,不仅仅只对构造函数而言. 23 24 25 """ 26 27 28 class A: 29 def __init__(self): 30 print("A") 31 32 33 class B(A): 34 def __init__(self): 35 print("B") 36 super().__init__() 37 38 39 class C(A): 40 def __init__(self): 41 print("C") 42 super().__init__() 43 44 45 class D(B, C): 46 def __init__(self): 47 print("D") 48 super().__init__() 49 50 51 if __name__ == '__main__': 52 # 结果: (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) 53 print(D.__mro__) 54 55 # 结果: 56 # D 57 # B 58 # C 59 # A 60 d = D()
mixin继承案例(Django rest framework)
- 如何设计类的继承关系???
- Python中支持多继承,但在实际的编码中不推荐去使用多继承,多继承设计不好的话,
- 很容易造成继承关系混乱,以及MRO算法影响造成多继承中一些预料不到的问题.
- 所以在编码中并不推荐使用多继承,一般情况下尽量去继承一个类来实现类的继承关系.
- 那么有人会问python中既然支持多继承,而又说不推荐使用多继承,尽量只继承一个类,
- 那么代码的重用性是不是功能就非常的受限呢?
- 实际上python中有一个推荐做法叫做mixin模式
- mixin模式特点:
- 1.mixin类功能单一
- 2.不和基类关联,可以和任意基类组合.基类可以不和mixin关联就能初始化成功.
- 3.mixin不要使用super这种用法.(因为super遵循MRO顺序)
- 4.在设计mixin模式时,给类命名尽量使用xxxMixin来结尾(不成文的规范,方便代码可读)
python中的with语句
- 在python中的with语句是用来简化try...except...finally...语句而诞生的
********
posted on 2019-04-03 13:54 jaydenjune 阅读(62) 评论(0) 收藏 举报
浙公网安备 33010602011771号