第四章 深入类和对象

一. 鸭子类型和多态

鸭子类型与多态文章详解

# 简单的例子
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")


animal_list = [Cat, Dog, Duck]
for animal in animal_list:
    animal().say()

        如果一个对象实现了__getitem__方法,那python的解释器就会把它当做一个collection,就可以在这个对象上使用切片,获取子项等方法;如果一个对象实现了__iter__next方法,python就会认为它是一个iterator,就可以在这个对象上通过循环来获取各个子项。
        由于python在定义变量时不指定变量的类型,而是由解释器根据变量内容推断变量类型的(也就是说变量的类型取决于所关联的对象),这就使得python的多态不像是c++或java中那样,定义一个基类类型变量而隐藏了具体子类的细节。
        鸭子类型与魔法函数密不可分

在静态语言中如JAVA,要实现多态的前提条件是必须实现继承和重写,而python中只要通过定义相同的方法(魔法方法)即可实现“多态“”(python中没有多态,而是鸭子类型)。

所谓动态语言,就是类型检查是在运行的时候做的,变量使用之前不需要类型声明,通常变量的类型是就是被赋值的那个值的类型,例如JavaScript等这种脚本语言就是动态语言,在编译阶段它不会判断代码是否符合规范,在运行的时候才会去判断。相反静态语言就是在编译阶段进行类型检查,当编写源程序的时候,出现不符合语法的规范,就会提示错误,在编译时变量的数据类型即可确定,像Java,c,c++这种就属于静态语言。

二. 抽象基类(abc模块)

抽象基类文章详解

特点:

  1. 抽象基类中定义若干要实现的方法,继承于抽象基类的类需要覆盖这些方法。
  2. 抽奖基类无法用来实例化。
# 检查某个类是否有某种方法
class Company(object):
    def __init__(self, employee_list):
        self.employee = employee_list

    def __len__(self):
        return len(self.employee)

com = Company(["bobby1", "bobby2"])
print(hasattr(com, "__len__")) # True

# 判定某个对象的类型需要用到抽象基类
from collections.abc import Sized
isinstance(com, Sized)

抽象基类可以强制子类必须实现某些方法,可以理解为类型检测

# 模拟抽象基类
# 1.需要传入 metaclass=abc.ABCMeta
# 2.需要在必须实现的方法前添加装饰器 @abc.abstractmethod
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
    
    # def eat(self):
    #     raise NotImplementedError
    # def voice(self):
    #     raise NotImplementedError

# 缺少 get 方法的实现,会报错
class RedisCache(CacheBase):
    def set(self, key, value):
        pass
redis_cache = RedisCache()
redis_cache.set("key", "value") # TypeError: Can't instantiate abstract class RedisCache with abstract methods get

三. isinstance和type的区别

isinstance() 函数来判断一个对象是否是一个已知的类型,类似 type()。

isinstance() 与 type() 区别:

type() 不会认为子类是一种父类类型,不考虑继承关系。

isinstance() 会认为子类是一种父类类型,考虑继承关系。

如果要判断两个类型是否相同推荐使用 isinstance()。

class A:
    pass
class B(A):
    pass

b = B()

print(isinstance(b, A)) # True
print(isinstance(b, B)) # True
print(isinstance(b, object)) # True
isinstance (b, (A, B, object)) # 是元组中的一个返回 True

print(type(b) is type(A)) # False
print(type(b) is type(B)) # True
print(type(b) is type(object)) # False

==判断值是否相等,is判断id是否一致

四. 类变量与对象变量

class A:
    aa = 1
    def __init__(self, x, y):
        self.x = x
        self.y = y

a = A(2,3)

A.aa = 11
# 实际上新增了一个实例变量 aa,而没有改变类变量的 aa,先查找实例变量再查找类变量
a.aa = 100
print(a.x, a.y, a.aa) # 2, 3, 100
print(A.aa) # 11
print(A.x) # AttributeError: type object 'A' has no attribute 'x'

b = A(3,5)
print(b.aa) # 11

五. 类属性及实例属性的查找顺序

Python 至少有三种不同的 MRO

  1. 经典类(classic class)的深度遍历。(深度优先算法)
  2. Python 2.2 的新式类(new-style class)预计算。(广度优先算法)
  3. Python 2.3 的新式类的 C3 算法。它也是 Python 3 唯一支持的方式。

具体可以看这篇文章:Python的方法解析顺序(MRO/Method Resolution Order)
A.__mro__ 可以查看新式类的顺序

六. 静态方法、类方法以及对象方法

静态方法直接作为类的属性调用(可以给class传参数但是是硬编码)
对象方法实例化后调用
类方法可给class传入参数(不是硬编码)

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

七. 静态封装和私有属性

私有属性用双下划线定义__xxx
实际上并不是私有,而是将属性 __attr变成了_class__attr来使用户无法直接取到,所以私有属性不是绝对安全的

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.__birthday) # AttributeError: 'User' object has no attribute '__birthday'
   print(user.get_age()) # 28
   print(user._User__birthday) # 1990/2/1

八. python对象的自省机制

自省是通过一定的机制查询到对象的内部结构
__dict__属性详解

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__) # {'scool_name': '慕课网'}  没有name属性
    user.__dict__["school_addr"] = "北京市"
    print(user.school_addr)  # 北京市
    print(Person.__dict__) # '__module__': '__main__', 'name': 'user', '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
    print(user.name) # user

自省机制有(自省机制详解):

  1. dict():列出对象属性
  2. type():列出对象是被谁实例化的(不考虑继承关系)
  3. hasattr():检查是否拥有该属性
  4. isinstance():列出对象的父类(考虑继承关系)

九. super函数

# super的调用顺序就是mro顺序
# 调用父类的 __init__方法
class A:
    def __init__(self):
        print("A")

class B(A):
    def __init__(self):
        print("B")
        # python 2中
        super(B, self).__init__()
        # python3 中
        super().__init__()

思考:

  1. 为什么要调用父类已经被重写的方法?
  2. 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")
        # python2中的用法
        super(D, self).__init__()


if __name__ == "__main__":
    print(D.__mro__) # (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
    d = D() # D B C A

十. django rest framework中对多继承使用的经验

理解并使用mixin
理解并使用mixin 2

十一. Python中的With语句(上下文管理器)

  1. 对try except else finally的理解
# 1. else只有在无异常时才能运行到
# 2. 如果try中有return,则else无法运行到
# 3. finally不论有没有异常,有没有return都能运行到
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
exe_try()  # code started key error 4
  1. 上下文管理器协议
# 上下文管理器协议
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()
# enter
# doing something
# exit
  1. contextlib的使用
    contextlib的使用
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容