返回顶部

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"))

 

输出结果如下

数据封装和私有属性

私有属性:

  1 以双下划线开头的变量是私有变量,不能用对象名+变量名直接调用,如果用user.__birthday的话会报错
  2 但是可以通过类中的公有函数来调用
  3 其实在python内部是把私有变量做了一个变形,"__属性名"变成"_类名__属性名".
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对象的自省机制

自省机制:也就是通过一些方法查询对象的内部结构

 说明:

  1 __dict__可查看对象的属性和值,同时也能给对象定义新属性和值
  2 python中字典是用c编写实现的,很多python的内部结构的数据都是用字典来编写存储的
  3  dir()可查看对象的方法和属性名,不能查看其值
  4 如果对象不是类,而是列表,那么是不能用__dict__的,只能用dir. 比如a=[1,2], 使用a.__dict__会报错
#自省是通过一定的机制查询到对象的内部结构
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)

输出结果

 

posted @ 2018-12-05 11:55  Crazymagic  阅读(341)  评论(0编辑  收藏  举报