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用于两个可迭代对象的拼接,查看源码我们可以知道,我们需要传入一个可迭代的对象

大多数我们使用是这样的
a = ["bobby1", "bobby2"] b = ["bobby2", "bobby"] a.extend(b)
既然是一个可迭代的对象,那么我们在类中定义__getitem__方法后那么这个类也就编程了一个可迭代的对象,所有extend也就可以拼接这个类的时候就会触发其内部的__getitem__方法
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的内部源码
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判断一个对象是否属于某个类型
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")
这样加入用户在继承我们的类时,如果没有指定,我们要重写的方法,在调用的时候就会抛出异常写

我们可以利用抽象的基类,在用户继承我们类的时候,如果没有指定我们要重写的方法,就给它抛出异常
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 )
输出结果如下

类变量和实例变量
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查找顺序如下
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查找顺序如下
#新式类
class D:
pass
class E:
pass
class C(E):
pass
class B(D):
pass
class A(B, C):
pass
print(A.__mro__)
输出结果如下

类方法,静态方法和实例方法
所谓静态方法就相当于将需要外部调用的方法集成到类的内部,将命名空间并入到类中,不能在内部调用实例变量和实例方法。静态方法的缺陷是函数内return语句要调用类的名字,如果类的名字变化,静态方法也要修改,也就是通常说的硬编码
类方法相比静态方法,不在需要硬编码了(即返回一个对象只需cls()),不能在内部调用实例变量和实例方法
实例方法就很简单了,使用实例+方法+()即可
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"))
输出结果如下

数据封装和私有属性
私有属性:
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__顺序一样
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
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源码 我们可以看到实现的功能单一
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来处理异常。
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__
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)
输出结果


浙公网安备 33010602011771号