1.鸭子类型和多态
当你看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。
########################伪代码##############################
class Animal:
def say(self):
print(“i am a animal”)
class Cat (Animal):
def say(self):
print(" i am a cat")
Animal an=Cat()
an.say()
java中创建对象时必须指明变量的类型,an属于Cat类,猫又属于动物Animal类,用,其中Animal类是Cat类的父类。且子类必须重写父类的say方法。java中如果要使用多态的话所有的类必须继承同一个父类,并且重写父类中的方法。
#####################################################
python中多态就非常的简单,只需要包含同一个方法,不用继承同一个类,就可以调用这个方法。
如果一些类里包含一个相同的名称的方法,那么可以遍历调用这个方法。如图:
#结果如图:
另一个例子:
代码:
list1=[“xiaopang1”,“xiaopang2”]
list2=[“xioapang2”,“xiaopang”]
name_tupe=(“xiaopang3”,“xiaopang4”)
name_set=set()
name_set.add(“xiaopang5”)
name_set.add(“xiaopang6”)
list1.extend(list2)
print(list1)
list1.extend(name_tupe)
print("----------------------------------------")
print(list1)
list1.extend(name_set)
print("----------------------------------------")
print(list1)
结果:
[‘xiaopang1’, ‘xiaopang2’, ‘xioapang2’, ‘xiaopang’]
[‘xiaopang1’, ‘xiaopang2’, ‘xioapang2’, ‘xiaopang’, ‘xiaopang3’, ‘xiaopang4’]
[‘xiaopang1’, ‘xiaopang2’, ‘xioapang2’, ‘xiaopang’, ‘xiaopang3’, ‘xiaopang4’, ‘xiaopang5’, ‘xiaopang6’]
Process finished with exit code 0
所有练习代码:
class Cat(object):
def say(self):
print("i am a cat")
class Dog(object):
def say(self):
print("i am a dog")
class Duck(object):
def say(self):
print("i am a duck")
def sum(self,x,y):
print(self.x+self.y)
# animal=Cat
# animal().say()
animal_list=[Cat,Dog,Duck]
for animal in animal_list:
animal().say()
class Pig(object):
def __init__(self,food):
self.food=food
#在使用可迭代对象不能使用 obj[]形式的时候,可以使用这个方法
def __getitem__(self, item):
return self.food[item]
#注释类本身不可迭代,如list dict这些对象本身不可迭代,
#可以使用iter(list)将其转化为可迭代对象。
def __iter__(self,):
return iter(self.food)
def __str__(self):
return " ".join(self.food)
p=Pig({"a":"apple","b":"banana","c":"grass"})
print(p)
# p=Pig(["apple","banana","grass"])
# # print(p)
for pitem in p:
print(pitem,p[pitem])
#字典类型本身不可迭代
#java中我们会这样写
'''
class Animal:
def say(self):
print("i am a animal")
class Cat (Animal):
def say(self):
print(" i am a cat")
Animal an=Cat()
an.say()
java中创建对象时必须指明变量的类型,但是an属于Cat类,但猫又属于动物Animal类,
用,其中Animal类是Cat类的父类。且子类必须重写父类的say方法。
'''
list1=["xiaopang1","xiaopang2"]
list2=["xioapang2","xiaopang"]
name_tupe=("xiaopang3","xiaopang4")
name_set=set()
name_set.add("xiaopang5")
name_set.add("xiaopang6")
list1.extend(list2)
dict1={"a":1,"b":2,"c":3}
print(list1)
list1.extend(name_tupe)
print("----------------------------------------")
print(list1)
list1.extend(name_set)
print("----------------------------------------")
print(list1)
print("----------------------------------------------")
list1.extend(list(dict1))
print(list1)
print("------------------------------------------------")
# for xitem in p:
# s=xitem+":"+p[xitem]
# list1.extend(s)
list1.clear()
for pitem in p:
print(pitem+":"+p[pitem])
list1.extend(pitem+":"+p[pitem])
list1.append(pitem+":"+p[pitem])
print(list1)
代码运行结果:
H:\ANACONDA\python.exe I:/ainlp/pythonHight/chapter04/t4_1.py
i am a cat
i am a dog
i am a duck
a b c
a apple
b banana
c grass
[‘xiaopang1’, ‘xiaopang2’, ‘xioapang2’, ‘xiaopang’]
[‘xiaopang1’, ‘xiaopang2’, ‘xioapang2’, ‘xiaopang’, ‘xiaopang3’, ‘xiaopang4’]
[‘xiaopang1’, ‘xiaopang2’, ‘xioapang2’, ‘xiaopang’, ‘xiaopang3’, ‘xiaopang4’, ‘xiaopang5’, ‘xiaopang6’]
[‘xiaopang1’, ‘xiaopang2’, ‘xioapang2’, ‘xiaopang’, ‘xiaopang3’, ‘xiaopang4’, ‘xiaopang5’, ‘xiaopang6’, ‘a’, ‘b’, ‘c’]
a:apple
b:banana
c:grass
[‘a’, ‘:’, ‘a’, ‘p’, ‘p’, ‘l’, ‘e’, ‘a:apple’, ‘b’, ‘:’, ‘b’, ‘a’, ‘n’, ‘a’, ‘n’, ‘a’, ‘b:banana’, ‘c’, ‘:’, ‘g’, ‘r’, ‘a’, ‘s’, ‘s’, ‘c:grass’]
Process finished with exit code 0
抽象基类(abc模块) abstract bases class
首先python当中不建议使用抽象基类的方式设计高级的系统,而是建议采用多继承的方式。
在java当中一个类只能继承一个类,但是它可以继承多个接口。接口是不能用来实例化的。
在python当中的抽象基类也是不能实例化的。python是动态语言,它是没有变量的类型的,变量只是一个符号而已,它是可以指向任何类型的对象。所以说在python当中就不存在多态这么一个概念,我们可以赋值任何类型的数据给Python中的变量,而且可以修改指向的。所以它就不需要像java当中一样去实现一个多态,它本身就是一个支持多态的语言。动态语言与静态语言的最大的区别就是不需要指明变量的类型,所以说动态语言就少了一个编译时检查错误的环境,只有在运行时才会发现错误,这也是动态语言一个共有的缺陷,无法做类型检查。
鸭子类型始终贯穿于python的设计当中,当我们实现一个class的时候,是不需要去继承指定的类型的。
抽象基类使用的情况:
1.我们在某些情况下希望判定某个对象的类型 isintance
2.我们需要强制某个子类必须实现某些方法
#我们去检查某个类是否有某种方法
class Company(object):
data="hello xiaopang"
def __init__(self,employee_list):
self.employee=employee_list
def __len__(self):
return len(self.employee)
company=Company(["xiaopang","xiaohe","like","to","play","with","eachother"])
print(len(company))
#在类当中函数其实也是它的属性
#使用hasattr() 判断是否
'''
def hasattr(*args, **kwargs): # real signature unknown
"""
Return whether the object has an attribute with the given name.
This is done by calling getattr(obj, name) and catching AttributeError.
"""
pass
'''
print(hasattr(company,"__len__")) # True
print(hasattr(company,"data")) #Ture
#我们在某些情况下希望判定某个对象的类型
print(isinstance(company,Sized))
#我们需要强制某个子类必须实现某些方法:
比如我么自己实现了一个web框架,这个框架我们希望集成缓存进来,
我们可以使用cache或者redis或者memorycache来自定义我们的组件,
能够替换掉默认cache,可以无缝 的继承。
所以说在这里我们就需要设计一个抽象基类,然后子类必须实现某些方法。
为什么我们需要实现某些方法呢?
比如我们实现了一个web系统,如shenxianweb系统
我们开发者不清楚用户是使用memorycache,还是redis来替换系统中cache,
但是我们又不希望用户看到或减少看到或调用系统中的缓存cache代码。
这个时候我们自己建立一个组件 叫做CacheBase。它是一个抽象基类,
抽象基类里面会定义一些方法,如从缓存中获取数据的方法,给缓存中
添加数据的方法,我们也希望强制用户在继承这个类的时候必须把这些方法
实现了,因为如果不实现的话,调用这个抽象基类就会出错。这样不会对原
来的缓存代码做修改,而是按照统一的约定来实现的。
简单模拟一个抽象基类
#如何来模拟一个抽象基类
class CacheBase():
def get(self,key):
raise NotImplementedError
def set(self,key,value):
raise NotImplementedError
#如果子类不去实现抽象基类中的方法,会抛出异常,如果
# 实现了抽象基类中的方法,则不会抛出异常
class RedisCache(CacheBase):
def set(self,key,value):
print("实现了抽象基类当中的一个方法")
rediscache=RedisCache()
rediscache.set("key","value")
上述代码能够在没有实现抽象基类当中的方法的类对象是不能够调用抽象基类的方法的,且会在调用的时候抛出异常。如果我们希望这个异常在初始化的时候就抛出
抽象基类代码图片:
运行结果就是在初始化的时候抛出了异常
运行结果:
Traceback (most recent call last):
File “I:/ainlp/pythonHight/chapter04/abctest.py”, line 62, in
rediscache=RedisCache()
�� <class ‘main.RedisCache’>
TypeError: Can’t instantiate abstract class RedisCache with abstract methods get
#抽象基类
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 set(self,key,value):
print("实现了抽象基类当中的一个方法")
rediscache=RedisCache()
事实上Python当中实现了一些通用的抽象基类让我们可以去了解python数据结构的一些接口,位于collections.abc 模块当中。
使用isinstance()而不是 type()
ptrhon中
is是判断是否为同一个对象,即id是否相同。
“==”是判断两个的值是否相等
类变量和对象变量
如下代码中 aa为类变量。 对象变量 self.x self.y
class A:
aa=1
def init(self,x,y):
self.x=x
self.y=y
a=A(2,3)
print(a.x,a.y,a.aa)
print(A.aa)
#运行结果:
H:\ANACONDA\python.exe I:/ainlp/pythonHight/chapter04/classvar.py
2 3 1
1
Process finished with exit code 0
如果修改了 类的变量值,那么实例化的不同对象的变量会做同样的修改,但是单独修改实例化对象调用的类属性的值是不会影响类本身调用自身属性的值的,也不会影响其它实例化对象调用的类的属性的值。但是如果类调用的属性值本身做了修改,会影响实例化对象调用的类属性的值,而此动作之前已经修改的实例化对象调用的类属性值是不会重新随着类属性值的改变而改变的。
类属性以及实例属性的查找顺序
由下而上的查找顺序
class A:
name="A"
def __init__(self):
self.name="obj"
a=A()
print(a.name)
上述代码中self是实例化a 它先从实例的属性查找name,然后在接着去查找类的 属性name。
这是在简单的情况下,在多继承的情况下就会变得复杂。
python2.3之前采用的是深度优先遍历、广度优先遍历
python2.3之后到现在使用的是C3算法
MRO算法即C3算法。
算法的介绍将会在相应算法教程中作介绍。
显示属性查找顺序的方式:
对象.__ mro __
在python2.2之前,一个类如果没有继承object,那它就没有继承object,叫做经典类。但是后来任何的类都继承自object,叫做新式类。
静态方法、类方法以及对象方法以及参数
class Date:
x="xiaopang"
#构造函数
def __init__(self,year,mounth,day):
self.year=year
self.mounth=mounth
self.day=day
def __str__(self):
return "{year}/{mounth}/{day}".format(year=self.year,mounth=self.mounth,day=self.day)
#普通情况下我们定义的方法都是实例化方法 self代表实例本身
def tomorrow(self):
# day=int(self.day)
self.day+=1# 这里修改的实例的变量值
# Date.x+"like xiaohe" #这力相当于修改的是类的变量值
#静态方法 这个方法是不需要加self参数的,也是不接受class对象的
#调用的时候必须用类本身调用
@staticmethod
def parse_form_string(date_str):
year, mounth, day = tuple(date_str.split("-"))
return Date(int(year),int(mounth),int(day))
@staticmethod
def valid_str(date_str):
year, mounth, day = tuple(date_str.split("-"))
if int(year)>0and (int(mounth)>0 and int(mounth)<=12) and (int(day)>0 and int(day)<=30):
return True
else:
return False
#类方法 第一个参数cls 是代表类对象本身 cls可以任意字符
#返回时 cls 相比较静态方法的Date的硬编码而言是一个进步
#这里允许改变类对象的名称而自身不受影响。
@classmethod
def form_string(cls,date_str):
year, mounth, day = tuple(date_str.split("-"))
return cls(int(year),int(mounth),int(day))
'''
那么staticmethod相比较classmethod而言,有什么有用的场景呢。
'''
if __name__=="__main__":
datestr="2019-08-24"
'''
#处理日期字符串
year,mounth,day=tuple(datestr.split("-"))
print(year,mounth,day)
new_date=Date(int(year),int(mounth),int(day))
'''
#调用静态方法处理字符串的日期完成实例的初始化
Date.parse_form_string(datestr)
new_date=Date.parse_form_string(datestr)
#调用类方法完成初始化
new_date=Date.form_string(datestr)
print(new_date)
new_date.tomorrow()
print("------------------------------------------")
print(new_date)
print("-----------------------------------------")
print(Date.valid_str("2019-8-67"))
运行结果:
H:\ANACONDA\python.exe I:/ainlp/pythonHight/chapter04/class_method.py
2019/8/24
2019/8/25
False
Process finished with exit code 0
数据封装和私有属性
前面带有双下划线的属性,且子类的实例化对象访问不到父类的私有属性,只有本类实例化对象使用
获取方式:
1.实例化对象. _ 类对象 _ _XXX
2.实例化对象.方法名() 这个方法返回了私有属性的值
但是子类实例化对象无论如何访问不到父类中的私有属性。
下面是代码的理解:
项目目录
class_method.py
class Date:
x="xiaopang"
#构造函数
def __init__(self,year,mounth,day):
self.year=year
self.mounth=mounth
self.day=day
def __str__(self):
return "{year}/{mounth}/{day}".format(year=self.year,mounth=self.mounth,day=self.day)
#普通情况下我们定义的方法都是实例化方法 self代表实例本身
def tomorrow(self):
# day=int(self.day)
self.day+=1# 这里修改的实例的变量值
# Date.x+"like xiaohe" #这力相当于修改的是类的变量值
#静态方法 这个方法是不需要加self参数的,也是不接受class对象的
#调用的时候必须用类本身调用
@staticmethod
def parse_form_string(date_str):
year, mounth, day = tuple(date_str.split("-"))
return Date(int(year),int(mounth),int(day))
@staticmethod
def valid_str(date_str):
year, mounth, day = tuple(date_str.split("-"))
if int(year)>0and (int(mounth)>0 and int(mounth)<=12) and (int(day)>0 and int(day)<=30):
return True
else:
return False
#类方法 第一个参数cls 是代表类对象本身 cls可以任意字符
#返回时 cls 相比较静态方法的Date的硬编码而言是一个进步
#这里允许改变类对象的名称而自身不受影响。
@classmethod
def form_string(cls,date_str):
year, mounth, day = tuple(date_str.split("-"))
return cls(int(year),int(mounth),int(day))
'''
那么staticmethod相比较classmethod而言,有什么有用的场景呢。
'''
if __name__=="__main__":
datestr="2019-08-24"
'''
#处理日期字符串
year,mounth,day=tuple(datestr.split("-"))
print(year,mounth,day)
new_date=Date(int(year),int(mounth),int(day))
'''
#调用静态方法处理字符串的日期完成实例的初始化
Date.parse_form_string(datestr)
new_date=Date.parse_form_string(datestr)
#调用类方法完成初始化
new_date=Date.form_string(datestr)
print(new_date)
new_date.tomorrow()
print("------------------------------------------")
print(new_date)
print("-----------------------------------------")
print(Date.valid_str("2019-8-67"))
在private_method.py中调用class_method.py
from pythonHight.chapter04.class_method import Date
class User:
#私有属性 前面带有双下划线的变量,是无法通过实例对象来使用它,
#当然也是无法通过它的子类来调用它的。只能通过公共的方法才能够获取
#到它的属性,实际上python会把他进行一个变形,把它变成另外一个名字
def __init__(self,birthday):
self.__birthday=birthday
def get_age(self):
#返回年龄
return 2019-self.__birthday.year
class Student(User):
def __init__(self,birthday):
self.__birthday=birthday
if __name__=="__main__":
new_date=Date(1996,3,8)
# print(new_date)
# print(isinstance(new_date,Date))
user=User(new_date)
# print(User.__birthday)
print(user.get_age())
# 可以通过这种方式来访问User类中的 self.__birthday属性
#因此对于python来说,它不是绝对安全的。
print(".........................................")
print(user._User__birthday)
'''
从语言层面来讲,私有属性不是绝对安全的,java中的私有属性
也是可以通过反射机制来获取的。对python而言,它的突破方法
比较简单而已。对java来说,它稍微复杂一些。
'''
print("...................................")
student=Student(Date(1996,3,8))
print(student._Student__birthday)
调用的运行结果:
H:\ANACONDA\python.exe I:/ainlp/pythonHight/chapter04/private_method.py
23
…
1996/3/8
…
1996/3/8
Process finished with exit code 0
python对象的自省机制
在计算机编程中,自省是指这种能力:检查某些事物以确定它是什么、它知道什么以及它能做什么。自省向程序员提供了极大的灵活性和控制力。
自省:就是面向对象的语言所写的程序在运行时,能够知道对象的类型。简单一句就是,运行时能够获知对象的类型。
1.type()类型检查
2.类对象的检验 isinstance()、issubclass(class , classinfo)
3.对象的属性操作:getattr(obj, attr[, default])、hasattr(object,name)、setattr(object,name,value)、delattr(object,name) property(fget=None, fset=None, fdel=None, doc=None)
5.动态方法
6.help([obj]):
7.vars([obj])以字典类型返回对象的属性和属性值,如果不指定参数,返回当前调用位置的属性和属性值
8.callable(obj):检测对象是否可以被调用
9. __ dict __
等等
__ dict __
使用__ dict __ 能够清楚地获取到属于自身的属性
如:
1.子类对象.dict 单独获取的子类中属性 不包含父类任何属性及自身实例属。
2.子类实例化对象.dict 单独获取的是子类实例化对象属性 不包含父类任何属性及类对象属性 。
3.父类对象.dict 单独获取的是父类的属性 不包含父类实例化对象属性及子类任何属性。
4. 父类实例化对象.dict 单独获取的是父类实例化对象属性 不包含父类对象属性及子类任何属性。
dir() #可以列出对象的所有属性
#python的自省是通过一定的机制查询到对象的内部结构
from pythonHight.chapter04.class_method import Date
class Person:
"""
人
"""
name="user"
class Student(Person):
def __init__(self,school_name):
self.school_name=school_name
if __name__=="__main__":
stu=Student("上古天真论")
#通过__dict__查询属性 性能非常高
'''
字典在python当中是用C语言实现的,而且效率很高,
且python对它做了性能上的优化。python语言本身也大量的
用了dict做数据的存储,在python类的内部结构里面,
它也是用dict来进行属性管理的。
'''
#使用__dict__进行属性赋值
stu.__dict__["addr"]="hangzhou"
#__dict__也可以将对应的属性值取出来
'''
所以__dict__能够完成动态操控对象
'''
print(stu.addr)
print("..........................")
print(stu.__dict__) #{'school_name': '上古天真论'}
print(stu.name) # user
'''
为什么stu.name可以打印出Person这个父类中
的name属性的值user而却没有显示在stu.__dict__的
属性查找结果当中呢。name是Person的属性,使用
stu.name 实际上会向上查找,最终在父类查找到这个属性
但实际上这个name它是Person的属性,不是student的属性
'''
print("....................................")
person1=Person()
print(Person.__dict__)
print(".................................")
# person1.__dict__["name"]="xiaopang"
# person1.__dict__["age"]="23"
# print(person1.name)
# print(person1.age)
print(person1.__dict__)
'''
这里说明的是 使用__dict__能够清楚地获取到属于自身的属性
如1.子类对象.__dict__ 单独获取的子类中属性 不包含父类任
何属性及自身实例属性。
2.子类实例化对象.__dict__ 单独获取的是子类实例化对象属性
不包含父类任何属性及类对象属性 。
3.父类对象.__dict__ 单独获取的是父类的属性 不包含父类实
例化对象属性及子类任何属性。
4.父类实例化对象.__dict__ 单独获取的是父类实例化对象属性
不包含父类对象属性及子类任何属性。
'''
super 函数
class A:
def __init__(self):
print("A")
class B(A):
def __init__(self):
print("B")
#python2当中的用法 使用了之后会同时将父类的初始化
#函数家再进来super(B,self).__init__()
# super(B,self).__init__()
#在python3中对它做了简化直接调用就可以了
super().__init__()
from threading import Thread
class MyThread(Thread):
def __init__(self,name,user):
self.user=user
# self.name=name
'''
在Thread这个类当中,初始化方法中包含了name
参数,可以直接使用super调用父类中的初始化name
参数,这样构造函数就交给了它的父类,好处就是可
以重用父类中的代码逻辑。
'''
super.__init__(name=name)
if __name__=="__main__":
b=B()
'''
问题:
1.为什么重写了构造函数,还要去调用super
使用场景:
可以重用所调用类的构造方法中的逻辑代码
2.super的执行顺序是怎么样的
不要直接认为super就是调用父类的构造函数,
在多重继承当中super调用的顺序是按照MRO算法
顺序来执行的,并不是单纯调用父类的构造方法
'''
print("--------------------------------------------------------------")
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(D,self).__init__()
if __name__=="__main__":
d=D()
# D B C A
#super执行顺序与mro保持一致
print(D.__mro__) #查找顺序
# (<class '__main__.D'>, < class '__main__.B' >,
# < class '__main__.C' >, < class '__main__.A' >,
# < class 'object' > )
mixin继承案例 django rest framework中对多继承使用的经验
在使用编码的过程中并不推荐使用多继承,设计不好的话会造成类的混乱。
mixin混合模式特点:
#1.Mixin类功能单一
#2.不和基类关联,可以和任意基类组合,基类可以不和minxin关联就能初始化成功。
#3.在mixin中不要使用super
python 中的with语句上下文管理器
一个类中含有 _ _enter _ () , _ _ exit _()两个魔法函数,那么就可以基于上下文管理器使用with语句。
代码实例如下:
#上下文管理器 with语句
#基于上下文管理器协议 主要涉及了两个
#魔法函数__enter__ __exit__
class Smple():
def __enter__(self):
#可以在这里获取资源
return self
def __exit__(self, exc_type, exc_val, exc_tb):
#在这里释放资源
print("exit")
def do_something(self):
print("doing something ")
with Smple() as sample:
sample.do_something()
print(".........................")
'''
打印的结果:
doing something
.........................
exit
说明先初始化的时候调用了__enter__
接下来是调用do_something (显式调用)
最后跳出with时是调用__exit__
'''
使用with语句会对try-except-else-finally语句模式做进一步优化
try-except-else-finally 使用:
#try except 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: #做资源释放
print("finally")
return 4
if __name__=="__main__":
result=exe_try()
print(result)
运行结果:
exit
code started
key error
finally
4
使用contextlib 模块优化上下文管理器
import contextlib
@contextlib.contextmanager
def file_open(file_name):
print("file open")
#在yield语句之前可以写一写__enter__()
# 里面进行的操作,获取资源
yield {}
#在yield语句之后写的是 __exit__()
#里面进行的操作 ,释放资源
print("file")
with file_open("i://test.txt") as fopened:
print("file processing")
运行结果:
H:\ANACONDA\python.exe I:/ainlp/pythonHight/chapter04/contextlib_with.py
file open
file processing
file
Process finished with exit code 0
完结
下一篇 自定义序列类