1.鸭子类型和多态
什么是鸭子类型
当一只鸟走起路来像鸭子,游泳起来像鸭子,叫起来像鸭子,就可以称这只鸟为鸭子.
也就是,当我们有很多类都实现了同样的方法,这些类就可以被看成是同一个种类.
Python中的变量本身是没有类型的,所以它本身就代表着多态,它可以表示成为任何的类型.
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/14 14:52'
class Cat(object):
def say(self):
print('我是一只猫')
class Dog(object):
def say(self):
print('我是一只狗')
class Duck(object):
def say(self):
print('我是一只鸭子')
animal_list = [Cat(),Dog(),Duck()]
for animal in animal_list:
animal.say()
还有一个例子:list的extend方法
lst.extend(self ,iteralbe)
可以看到iterable就是一种鸭子类型,它代表的是一个可迭代的类型
a = [1,2]
b = [2,1]
c = (3,4)
d = dict(x=1,y=2) # 字典也是可迭代对象,但是默认迭代是字典的键
e = set()
e.add(5)
e.add(6)
a.extend(b)
a.extend(c)
a.extend(d)
a.extend(e)
print(a)
output:
1, 2, 2, 1, 3, 4, 'x', 'y', 5, 6]
2.抽象基类(abc abstract class的缩写)
什么是抽象基类
1. 如果有的类继承了抽象基类,它就必须实现抽象基类中的所有的方法
2. 抽象基类是没有办法实例化的,就是通过它不能创建对象
既然Python是基于鸭子类型的,也就是说所有的类增加一些魔法方法就可以实现特殊的特性,它不需要像静态语言那样必须继承某个类才能获取一些特性.那为什么需要抽象基类呢?因为有时候,我们可以通过抽象基类来判断某个是否属于某种类型.
Python的collections.abc模块有一些抽象基类,你可以根据isinstance(object,abcClass)来判定某个类是不是某种类型
如何去模拟一个抽象基类呢?
最简单的方法是通过抛出异常的方式来实现
# 最简单的方法就是在基类中抛出一个异常,但是这种方法有一个缺点.
# 就是在调用方法的时候才会抛出异常
class CacheBase():
def get(self,key):
raise NotImplementedError
def set(self,key,value):
raise NotImplementedError
class RedisCache(CacheBase):
pass
redis_cache = RedisCache()
redis_cache.set('key','value')
这样有一个缺点,就是在调用方法的时候才抛出异常,而在初始化的时候没有问题,有没有什么办法可以在初始化的时候就可以抛出异常,告诉你必须实现抽象基类的方法呢.使用abc模块中的@abc.abstractmethod装饰器的方式可以实现.
import abc
class CacheBase(metaclass=abc.ABCMeta):
@abc.abstractmethod
def get(self,key):
pass
@abc.abstractmethod
def set(self,key,value):
pass
class RedisCache(CacheBase):
def get(self,key):
pass
def set(self,key,value):
pass
cache = RedisCache()
3.isinstance和type的区别?
type只会判断这个对象或者类的直属对象,而isinstance可以向上追溯到它的父类.所有有时候type没有isinstance准确.
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/14 16:53'
class A:
pass
class B(A):
pass
b = B()
print(isinstance(b,B))
print(isinstance(b,A))
print(type(b) is B)
print(type(b) is A)
4. 类变量和实例变量
1. 类变量属于类,也属于实例.所有的实例对象和类共有一份类变量.并且类和实例都可以调用类变量
2. 实例变量属于实例,不属于类.实例变量是具体的实例单独拥有.并且实例变量只能由具体的实例来调用.不能由类变量来调用.
3. 如果实例变量和类变量重名,则实例变量调用的就是自己的实例变量,如果通过实例对象对类变量进行赋值,则它不会调用类变量,而是重新增加一个同名的实例属性.这点是个坑,有点类似全局变量和局部变量的味道.所以我们在使用的时候,最好的习惯是使用类来调用类变量,而减少使用实例对象调用类变量的情况.其实这里如果用类来调用,就相当于是加了一个global声明,表示这个变量是类变量.而如果你用实例对象来调用的时候,如果是访问,它就会向上查找,如果是修改,例如赋值,它就不会向上查找,如果没有这个属性,它就会直接创建
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/14 16:59'
class A:
aa = 1 # 类变量
def __init__(self,x,y):
self.x = x # 实例变量
self.y = y # 实例变量
a = A(2,3) # a是一个实例
A.aa = 11 # 类变量是所有的实例和类共有的变量,只有一份.
a.aa = 100 # 如果创建的实例变量和类变量重合,则实例变量就多出来了一个属性aa
# 这样在调用的时候获取的就是实例的属性,有点类似作用域局部和全局的味道
print(a.x,a.y,a.aa) # 实例找变量的时候,会先找自己的,再找类拥有的
print(A.aa) # 类变量也可以通过类来调用
# print(A.x,A.y) # 实例变量不能通过类来方法
打印结果:
2 3 100
11
5.类属性和实例属性以及查找顺序
属性: 变量和方法统称为属性
Python3采用的属性查找顺序是C3算法,也就是先广度优先,然后有子类会调用子类的方式.
通过一个类的mro属性可以查看一个类的属性查找顺序.
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/14 19:03'
# 新式类
class D:
pass
class E:
pass
class C(E):
pass
class B(D):
pass
class A(B,C):
pass
print(A.__mro__)
(
'__main__.A'>, '__main__.B'>, '__main__.D'>, < class '__main__.C'
>, <class '__main__.E'
>,)
6.类方法,静态方法,实例方法
类方法是属于类的,所有的对象和类公有这个类方法,用@classmethod装饰器来装饰.实例对象和类对象都可以调用类方法,但是类方法不能使用实例属性,里面必须有cls参数,也就是一个类参数
静态方法,跟一个普通的函数定义没有什么不同,对参数也没有要求,它就是普通的函数放到一个类中,然后加上@staticmethod装饰器.静态方法,一般不能使用实例变量.类对象和实例对象都可以调用静态方法
实例方法,第一个参数必须是具体的实例对象,我们一般定义的时候使用self来代替,不需要添加额外的装饰器.实例方法一般只有实例对象才可以调用,类对象不可以调用
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/14 19:08'
class Date:
# 构造初始化函数
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
@staticmethod
def parse_from_string(date_str):
year,month,day = tuple(date_str.split('-'))
return Date(int(year),int(month),int(day))
@classmethod
def from_string(cls,date_str):
year,month,day = tuple(date_str.split('-'))
return cls(int(year),int(month),int(day))
def tomorrow(self):
self.day += 1
return self.day
def __str__(self):
return "{year}/{month}/{day}".format(year=self.year, month=self.month, day=self.day)
if __name__ == '__main__':
new_day = Date(2019,1,2)
print(new_day)
print(new_day.tomorrow())
date_str = '2018-12-31'
# 用staticmethod来完成初始化
new_day = Date.parse_from_string(date_str)
print(new_day)
# 用classmethod来完成初始化
new_day = Date.from_string(date_str)
print(new_day)
7.数据封装和私有属性.
私有属性:
通过双下划线来声明私有属性,不可以直接访问,但是它只是改变了一个名字不可以直接访问.但是还是可以通过_classname__var的方式来访问.
这种方法,其实就是为了使得我们写程序的时候更加规范,更多的是一种提示程序员这个变量该怎么使用的作用.
这种方法还有一个好处,就是有效的区分了继承之后变量名重名的问题,因为它会在变量前面加上自己的_classname
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/14 19:45'
class User:
def __init__(self, birthyear):
self.__birthyear = birthyear
def get_age(self):
# 返回年龄
return 2019 - self.__birthyear
if __name__ == '__main__':
user = User(1990)
# print(user.__birthyear) # 这里访问不到,以双下划线开头的私有属性
# 不可以直接通过实例对象来访问,但是可以通过_User__birthyear来访问.
print(user.get_age())
8.Python的自省
什么是自省
自省就是在Python中一个对象在运行的时候可以通过某种机制知道自己的类型.
dir()
可以获取一个对象的多有的属性列表,只有属性没有值.
__dict__
变量可以获取一个对象的自己的属性,一般是对象自身拥有的,不是继承的,也不是内置的.
并且它返回的是一个字典,字典的键就是属性名,字典的值就是属性变量对应的值.可以通过这个字典进行添加和修改属性的操作.
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/14 19:57'
class Person:
name = 'user'
class Student(Person):
def __init__(self, school_name):
self.school_name = school_name
if __name__ == '__main__':
user = Student('慕课网')
person = Person()
# 通过__dict__查询这个类都有哪些属性
print(user.__dict__) #{'school_name': '慕课网'}
print(person.__dict__) # {}
print(Person.__dict__)
# {'__module__': '__main__', 'name': 'user', '__dict__': ,
# '__weakref__': , '__doc__': None}
# 可以通过__dict__添加和修改一个对象的属性
user.__dict__['school_addr'] = '北京市'
print(user.school_addr) # 北京市
print(dir(user))
# dir()函数会列出一个对象的所有的属性,包括它继承到的,还有内置的各种属性
Python中的super()函数的使用
两个问题?
1.比如我们已经重写了某个方法,为什么还要用super()调用它的上一级的这个方法
因为有时候,我们需要重用它的上一级完成的某些功能.比如我们重写Thread类的时候,就可以重用它的__init__里面的一些功能
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/14 20:12'
# 既然我们重写了构造方法,为什么还要去调用super()呢?
# 因为我们有时候要重用它的上一级的类的方法去实现某种功能.
# 还有super()并不是简单的调用父类的方法,它的调用顺序又是怎么样的呢?
# super()的调用顺序是按照__mro__的顺序来调用的,也就是说它是和通过.
# 来查询属性的顺序是一致的.
from threading import Thread
class MyThread(Thread):
def __init__(self, name, target, time):
self.time = time
super().__init__(target=target, name=name)
def run(self):
pass
class A:
def __init__(self):
print('A')
class B(A):
def __init__(self):
print('B')
super().__init__()
class C(A):
def __init__(self):
print('C')
super().__init__()
class D(B,C):
def __init__(self):
print('D')
super().__init__()
if __name__ == '__main__':
d = D()
print(D.__mro__)
[output:] D B C A
(main.D'>, main.B'>, main.C'>, main.A'>, )
如果只是简单的调用父类super()方法,则上面的打印结果应该是 d=D()的时候应该是:D,B,A,C,A
但是实际的调用情况是D B C A和D.__mro__
的顺序是一致的
Python中的with语句
首先看下try ... except .. else ... finally的用法
try
: 先执行的模块,这里会捕获异常.
except
:如果捕获到异常就会执行except语句的代码
else
: 如果没有捕获到异常,就会执行else语句的代码
finally
: 无论except和else是否执行了,finally一定会执行.
def exe_try():
try:
print('code started')
raise KeyError
return 1
except KeyError as e:
print('key error')
return 2
else: # 没有抛异常的时候会运行
print('other error')
return 3
finally: # 无论是否抛异常都会执行,如果finally里面有return语句.则
# 返回的永远都是finally里面的return语句
print('finally')
return 4
总结:
finally一般用来释放资源,做最后的处理.
Python的with语句
with语句又叫上下文管理器,之所以一个对象可以使用with语句,是因为这个对象实现了上下文管理器协议.
而这个协议的实现,就是通过魔法方法__enter__()
和__exit__
来实现的.
只要我们使用了with语句,它后面跟的对象,在初始化的时候就会调用这个对象的__enter__()
方法,而在结束的时候就会自动调用它的__exit__()
方法.
class Sample():
def __enter__(self):
print('__enter__')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('exit')
def do_something(self):
print('doing something')
# with表示的含义是初始化的时候先调用对象的__enter__方法
# 结束的时候会调用__exit__方法 __enter__ 和__exit__方法就是
# 用来实现上下文管理器协议的,然后实现了这个协议的就可以使用with语句.
with Sample() as simple:
simple.do_something()
还有另外一种方式可以实现上下文管理器,使用contexlib的contextmanager装饰器可以将一个函数变成上下文管理器对象.
注意:
这里的函数必须是使用yield,yield之前的代码可以理解成都是__enter__()
实现的逻辑代码.
而yield之后的代码可以理解成都是__exit__()
实现的代码逻辑.
@contextlib.contextmanager
def file_open(file_name):
print('file open')
yield {}
print('file end')
with file_open('bob.txt') as f_opened:
print('中间')
output:
file open
中间
file end
这种书写的好处是,代码比较容易理解,更加的直观.