03 python深入类和对象
鸭子类型和多态
当你看到一只鸟走起来像鸭子,叫起来也想鸭子,那么这只鸟就可以被成为鸭子
当我们在多个类中,定义了相同的方法,那么我们就可以给它归为一类,这就是鸭子类型
如 一个对象有__iter__( )方法或__getitem__( )方法,则称这个对象是可迭代的
如果一个对象有__iter__( )方法和__next__( )方法,则称这个对象是迭代器
何实现了 __enter__() 和 __exit__() 方法的对象都可称之为上下文管理器
‘’‘’‘’‘’‘’‘’‘’‘
python中为一个类实现某个特性的,不需要继承某一个类或使用一个接口,只要在其内部指定的魔法函数即可(鸭子类型在python中的使用),不关注对象的本身,只关注它的行为
class Cat(object): def say(self): print("i am a cat") class Dog(object): def say(self): print("i am a fish") class Duck(object): def say(self): print("i am a duck") animal_list = [Cat, Dog, Duck] for animal in animal_list: animal().say()
在python中的魔法函数就是使用了鸭子类型,我们可以在任意一个类中使用python为我们提供的魔法函数,python在调用他们时触发的条件都是统一的
extend用于两个可迭代对象的拼接,查看源码我们可以知道,我们需要传入一个可迭代的对象
大多数我们使用是这样的
1 2 3 4 5 | a = [ "bobby1" , "bobby2" ] b = [ "bobby2" , "bobby" ] a.extend(b) |
既然是一个可迭代的对象,那么我们在类中定义__getitem__方法后那么这个类也就编程了一个可迭代的对象,所有extend也就可以拼接这个类的时候就会触发其内部的__getitem__方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Company( object ): def __init__( self , employee_list): self .employee = employee_list def __getitem__( self , item): return self .employee[item] company = Company([ "tom" , "bob" , "jane" ]) a = [ "bobby1" , "bobby2" ] a.extend(company) print (a) |
输出结果如下
抽象基类(abc模块)
用途:
1 可以判断一个类中有没有一个魔法方法
2 接口的强制规定
假如我们判断一个类中是否有__len__魔法方法 , 可以使用collections.abc中的Sized
class Company(object): def __init__(self, employee_list): self.employee = employee_list def __len__(self): return len(self.employee) com = Company(["bobby1","bobby2"]) from collections.abc import Sized print(isinstance(com, Sized))
输出结果
Sized的内部源码
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Sized(metaclass = ABCMeta): __slots__ = () @abstractmethod def __len__( self ): return 0 @classmethod def __subclasshook__( cls , C): if cls is Sized: return _check_methods(C, "__len__" ) return NotImplemented |
当然我们也可以使用简单的
isinstance判断一个对象是否属于某个类型
1 2 3 4 5 6 7 | class A: pass class B: pass b = B() print ( isinstance (b, A)) |
使用抽象基类实现接口的强制规定
当我们在写一个类的时候,强制的让别人实现某个方法通常是这样写的
class CacheBase(): def get(self, key): raise NotImplementedError def set(self, key, value): raise NotImplementedError # class RedisCache(CacheBase): pass redis_cache = RedisCache() redis_cache.get("key")
这样加入用户在继承我们的类时,如果没有指定,我们要重写的方法,在调用的时候就会抛出异常写
我们可以利用抽象的基类,在用户继承我们类的时候,如果没有指定我们要重写的方法,就给它抛出异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import abc from collections.abc import * class CacheBase(metaclass = abc.ABCMeta): @abc .abstractmethod def get( self , key): pass @abc .abstractmethod def set ( self , key, value): pass class RedisCache(CacheBase): pass redis_cache = RedisCache() |
输出信息如下
isinstance 和 type的区别
isinstance 判断一个对象是否属于某个类型会根据这个对象的继承链去找,如果找到就返回True否则Flase
type()用来判断某个对象是属于那个类,它不会根据继承链向上找
class A: pass class B(A): pass b = B() print(isinstance(b, B)) print(isinstance(b, A)) print('='*40) print(type(b) is B) print(type(b) is A )
输出结果如下
类变量和实例变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class A: aa = 1 def __init__( self , x, y): self .x = x self .y = y a = A( 2 , 3 ) A.aa = 11 a.aa = 100 print (a.x, a.y, a.aa) print (A.aa) b = A( 3 , 5 ) print (b.aa) |
输出结果如下
实例变量的查找顺序
1 在当前的实例中查找,如果没找到的话就会去类中获取同名的类变量
2 假如给一个实例变量动态的赋值一个和类变量同名的实例变量,不会修改类变量的值,只是动态的给自己增加一个实例变量而已
类变量是不能够向下查找的,因为对象是根据类创建的且每个对象的实例变量的值可能不同
类属性和实例属性以及查找的顺序(采用的是c3算法)
A继承B和C, B和C继承D查找顺序如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class D: pass class C(D): pass class B(D): pass class A(B, C): pass print (A.__mro__) |
输出结果如下
A继承B和C,B继承D,C继承E查找顺序如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #新式类 class D: pass class E: pass class C(E): pass class B(D): pass class A(B, C): pass print (A.__mro__) |
输出结果如下
类方法,静态方法和实例方法
所谓静态方法就相当于将需要外部调用的方法集成到类的内部,将命名空间并入到类中,不能在内部调用实例变量和实例方法。静态方法的缺陷是函数内return语句要调用类的名字,如果类的名字变化,静态方法也要修改,也就是通常说的硬编码
类方法相比静态方法,不在需要硬编码了(即返回一个对象只需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 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 | class Date: #构造函数 def __init__( self , year, month, day): self .year = year self .month = month self .day = day def tomorrow( self ): self .day + = 1 @staticmethod def parse_from_string(date_str): year, month, day = tuple (date_str.split( "-" )) return Date( int (year), int (month), int (day)) @staticmethod def valid_str(date_str): year, month, day = tuple (date_str.split( "-" )) if int (year)> 0 and ( int (month) > 0 and int (month)< = 12 ) and ( int (day) > 0 and int (day)< = 31 ): return True else : return False @classmethod def from_string( cls , date_str): year, month, day = tuple (date_str.split( "-" )) return cls ( int (year), int (month), int (day)) def __str__( self ): return "{year}/{month}/{day}" . format (year = self .year, month = self .month, day = self .day) if __name__ = = "__main__" : new_day = Date( 2018 , 12 , 31 ) new_day.tomorrow() print (new_day) #2018-12-31 date_str = "2018-12-31" year, month, day = tuple (date_str.split( "-" )) new_day = Date( int (year), int (month), int (day)) print (new_day) #用staticmethod完成初始化 new_day = Date.parse_from_string(date_str) print (new_day) #用classmethod完成初始化 new_day = Date.from_string(date_str) print (new_day) print (Date.valid_str( "2018-12-32" )) |
输出结果如下
数据封装和私有属性
私有属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class Date: #构造函数 def __init__( self , year, month, day): self .year = year self .month = month self .day = day def __str__( self ): return "{year}/{month}/{day}" . format (year = self .year, month = self .month, day = self .day) class User: def __init__( self , birthday): self .__birthday = birthday def get_age( self ): return 2018 - self .__birthday.year if __name__ = = "__main__" : user = User(Date( 1990 , 2 , 1 )) print (user.get_age()) print (user._User__birthday) |
输出结果如下
python对象的自省机制
自省机制:也就是通过一些方法查询对象的内部结构
说明:
#自省是通过一定的机制查询到对象的内部结构 from chapter04.class_method import Date class Person: """ 人 """ name = "user" class Student(Person): def __init__(self, scool_name): self.scool_name = scool_name if __name__ == "__main__": user = Student("学校") #通过__dict__查询属性 print(user.__dict__) user.__dict__["school_addr"] = "北京市" print(user.school_addr) print(Person.__dict__) print(user.name) a = [1,2] print(dir(a))
输出结果如下
super函数真的就是调用父类吗
既然我们重写了继承的构造函数, 为什么还要去调用super?
这样我们就可以用到父类的和我们自己的方法,如果不调用super那么只能用我们自己的
super到底执行顺序是什么样的?
执行的顺序和类的查找 __mro__顺序一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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__" : print (D.__mro__) d = D() |
输出结果如下
Mixin继承案例 django restframework
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class GoodsListViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): """ 商品列表页, 分页, 搜索, 过滤, 排序 """ # throttle_classes = (UserRateThrottle, ) queryset = Goods.objects. all () serializer_class = GoodsSerializer pagination_class = GoodsPagination # authentication_classes = (TokenAuthentication, ) filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) filter_class = GoodsFilter search_fields = ( 'name' , 'goods_brief' , 'goods_desc' ) ordering_fields = ( 'sold_num' , 'shop_price' ) def retrieve( self , request, * args, * * kwargs): instance = self .get_object() instance.click_num + = 1 instance.save() serializer = self .get_serializer(instance) return Response(serializer.data) |
在这个类中,一个类可以完成商品列表页, 分页, 搜索, 过滤, 排序,
mixins.ListModelMixin源码 我们可以看到实现的功能单一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class ListModelMixin( object ): """ List a queryset. """ def list ( self , request, * args, * * kwargs): queryset = self .filter_queryset( self .get_queryset()) page = self .paginate_queryset(queryset) if page is not None : serializer = self .get_serializer(page, many = True ) return self .get_paginated_response(serializer.data) serializer = self .get_serializer(queryset, many = True ) return Response(serializer.data) |
Mixin其实和普通的多继承是本质一样的,但是Mixin有以下特点:
① Minin里面功能比较单一,尽量简化
② 不和我们真正的类一样,不和基类关联,可以和任意基类组合,基类不和Mixin关联就能初始化成功,Minin只是定义了一个方法
③ Minin中不要使用super的方法,因为super会根据mro算法去调用他的方法,因为尽量不要和基类关联
④ 命名尽量使用Mixin结尾(约定俗成)
上下文管理器
里面包括两个魔法函数__enter__和__exit__
__enter__():主要执行一些环境准备工作,同时返回一资源对象。如果上下文管理器open("test.txt")的__enter__()函数返回一个文件对象。
__exit__():完整形式为__exit__(type, value, traceback),这三个参数分别为异常类型、异常信息和堆栈。如果执行体语句没有引发异常,则这三个参数均被设为None。否则,它们将包含上下文的异常信息。__exit_()方法返回True或False,分别指示被引发的异常有没有被处理,如果返回False,引发的异常将会被传递出上下文。如果__exit__()函数内部引发了异常,则会覆盖掉执行体的中引发的异常。处理异常时,不需要重新抛出异常,只需要返回False,with语句会检测__exit__()返回False来处理异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 Sample() as sample: sample.do_something() |
输出结果如下
contextlib简化上下文管理器
其中yield上面的print相当于__enter__,下面的print相当于__exit__
1 2 3 4 5 6 7 8 9 10 11 12 | import contextlib @contextlib .contextmanager def file_open(file_name): print ( "file open" ) yield {} # 返回一个空字典,可以不返回 print ( "file end" ) with file_open( "bobby.txt" ) as f_opened: print ( "file processing" ) f_opened[ 'name' ] = 'zhang' print (f_opened) |
输出结果
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理