第二课 python进阶深入类和对象
tags:
- Python
- 慕课网
categories:
- 鸭子类型
- 魔法函数
文章目录
第一节 鸭子类型和多态
- wiki定义:当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子
- 鸭子类型关注点在对象的行为,而不是类型。在 Python 和 Go 中都可以实现鸭子类型。
- 举例说明:
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()
a = ["bobby1", "bobby2"]
b = ["bobby0", "bobby"]
name_tuple = ("bobby3", "bobby4")
name_set = set()
name_set.add("bobby5")
name_set.add("bobby6")
# 这里的extend不仅可以传递列表 元组 集合 其他可迭代的对象也可以传入 它内部实际上调用的一个迭代的魔法方法
# 这显然与java语言确定传入确定数据类型的参数不同
a.extend(b)
a.extend(name_tuple)
a.extend(name_set)
print(a)
# 创建一个可迭代的类
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.extend(company)
print(a)
第二节 抽象基类(abc模块)
- ABC,Abstract Base Class(抽象基类),主要定义了基本类和最基本的抽象方法,可以为子类定义共有的API,不需要具体实现。相当于是Java中的接口或者是抽象类。
- 抽象基类可以不实现具体的方法(当然也可以实现,只不过子类如果想调用抽象基类中定义的方法需要使用super())而是将其留给派生类实现。
- ABCMeta 还有一些装饰器:@abstractmethod 和 @abstarctproperty 。abc.ABCMeta 用于在Python程序中创建抽象基类。
- 如果想要声明“抽象方法”,可以使用 @abstractmethod
- 如果想声明“抽象属性”,可以使用 @abstractproperty
- 抽象基类提供了逻辑和实现解耦的能力,即在不同的模块中通过抽象基类来调用,可以用最精简的方式展示出代码之间的逻辑关系,让模块之间的依赖清晰简单。
- 在python中继承某些接口我们推荐用mixin多继承,而不是这种抽象基类,在使用时容易设计过度,不容易理解它。
# 我们需要强制某个子类必须实现某些方法
# 实现了一个web框架,集成cache(redis, cache, memorychache)
# 需要设计一个抽象基类, 指定子类必须实现某些方法
# 如何去模拟一个抽象基类 这里使用的是全局下的abc 而不是collection中的abc
import abc
class CacheBase(metaclass=abc.ABCMeta): # 只能被继承,不能实例化,实例化会报错
@abc.abstractmethod # 加上装饰器 方法子类必须有这个方法,否则报错
def get(self, key):
pass
@abc.abstractmethod
def set(self, key, value):
pass
# 这种方式可以实现抛出异常 但是只有在运行时才可以
# class CacheBase():
# def get(self, key):
# raise NotImplementedError
# def set(self, key, value):
# raise NotImplementedError
class RedisCache(CacheBase):
def set(self, key, value):
pass
# 没有实现装饰器@abc.abstractmethod的方法 就不能够实例化
# def get(self, key, value):
# pass
# 使用抽象基类 在初始化的时候就会抛异常
redis_cache = RedisCache()
redis_cache.set("key", "value")
- 在python实际上已经实现了很多的抽象基类,让我们了解数据结构的一些接口。在from collections.abc import *中
from collections.abc import *
class Company(object):
def __init__(self, employee_list):
self.employee = employee_list
def __len__(self):
return len(self.employee)
company = Company(["tom", "bob", "jane"])
print(hasattr(company, "__len__"))
# 这里虽然没有继承Sized 那它怎么判断出它是Sized类型 点进源码看一下
# Lib\_collections_abc.py中可以看到return _check_methods(C, "__len__")
print(isinstance(company, Sized))
第三节 isinstance和type的区别
- isinstance:判断该变量是否是该类型,或者是否是该类和该类的父类类型
- type():获取该变量名的类型,结合==或is判断该变量的类型是否等于目标类型
class A:
pass
class B(A):
pass
b = B()
# 会根据继承链向上找
print(isinstance(b, B)) # True
print(isinstance(b, A)) # True
# is 判断id()对象地址是否相等
# == 判断值是否相等
print(type(b) is B) # True
print(type(b) is A) # False
第四节 类变量和实例变量
class A:
# aa在类中定义 是类变量
aa = 1
def __init__(self, x, y):
# self实际上是A的实例,x和y是实例变量
self.x = x
self.y = y
a = A(2, 3)
# 修改实例变量
A.aa = 11
# 实例中找不到 就会向上到类中查找aa
print(a.x, a.y, a.aa)
# 在实例上修改aa 这里的aa并不是类变量中的aa 而是创建了一个实例变量aa
a.aa = 100
print(a.x, a.y, a.aa)
print(A.aa)
b = A(3, 5)
print(b.aa)
第五节 类属性和实例属性以及查找顺序
- C3算法推荐博客: https://www.jianshu.com/p/a08c61abe895 C3算法从python2.3开始一直到现在
- 继承顺序的话,更简单,每个类都有一个.mro()的方法。
# 新式类
class D:
pass
class E:
pass
class C(E):
pass
class B(D):
pass
class A(B, C):
pass
# C3算法从python2.3开始一直到现在
print(A.__mro__)
第六节 类方法、静态方法和实例方法
- 实例方法就是普通的方法 传self实例
- 静态方法用 @staticmethod修饰 不用传self或者cls
- 类方法用@classmethod 传递cls 类本身
class Date:
# 构造函数
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
# 实例方法
def tomorrow(self):
self.day += 1
# 静态方法 不需要穿实例self 通过类来调用
@staticmethod
def parse_from_string(date_str):
year, month, day = tuple(date_str.split("-"))
return Date(int(year), int(month), int(day))
# 判断是否是合法的时间字符串 这样没有必要返回值 也没有必要穿cls
@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
# 类方法 传递的类本身cls 解决了上面硬编码的问题 及不论类名是否叫Date都可以返回
@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"))
第七节 数据封装和私有属性
- python中通过双下滑线完成私有属性的封装__
- 私有属性只能通过类中公共的方法进行访问,无法直接通过实例访问回报错。
- 它并不是从语言的层面解决了私有属性的问题,它只是用了一些小技巧, 做了一些变形。 实例._类__属性 依然可以访问到
- 其实java中也是可以突破private的,只是稍微麻烦点。
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, 3, 5))
# 双下划线私有属性不能直接访问 会报错
# print(user.__birthday)
# 实际上 它用了小技巧 实例._类__属性 依然可以访问到
print(user._User__birthday)
print(user.get_age())
第八节 python对象的自省机制
- 自省是通过一定的机制查询到对象的内部结构
- __dict__查询属性,这是个效率非常高的函数。通过C语言写的,返回字典
- dir 可以列出对象中所有属性,比__dict__更强大,但是没有值,返回数组。
class Person:
"""人"""
name = "user"
class Student(Person):
def __init__(self, scool_name):
self.scool_name = scool_name
if __name__ == "__main__":
user = Student("QnHyn")
# 通过__dict__查询属性
print(user.__dict__)
user.__dict__["school_addr"] = "北京市"
print(user.school_addr)
print(Person.__dict__)
print(Person.__dict__["__doc__"])
print(user.name)
a = [1, 2]
print(dir(a))
第九节 super函数
- 在python2中调用父类方法,super(B, self).init()。但是在python3中已经做了简化直接super().init()即可
- super到底执行顺序是什么样的?它并不是调用父类的初始化 而是按照mro的顺序依次调用
from threading import Thread
# 既然我们重写B的构造函数, 为什么还要去调用super?不用自己写逻辑了
class MyThread(Thread):
def __init__(self, name, user):
self.user = user
super().__init__(name=name)
class A:
def __init__(self):
print("A")
class B(A):
def __init__(self):
print("B")
# super(B, self).__init__()
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__":
# b = B()
# super到底执行顺序是什么样的?它并不是调用父类的初始化 而是按照mro的顺序依次调用
print(D.__mro__)
d = D() # DBCA
第十节 django rest framework中对多继承使用的经验
- 不推荐使用多继承可能有奇怪的坑,尽量只继承一个类。建议使用下面minxin模式(混合模式)
- mixin模式特点
- Mixin类功能单一
- 不和基类关联,可以和任意基类组合, 基类可以不和mixin关联就能初始化成功
- 在mixin中不要使用super这种用法
第十一节 try用法
- finally前面不管有没有运行都会运行后面代码 一般用于资源释放
- 如果finally中有return 那么他就会返回finally中的return.如果没有它会返回其他的return
# 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")
# 这里要注意:如果finally中有return 那么他就会返回finally中的return.如果没有它会返回其他的return
# return 4
if __name__ == "__main__":
result = exe_try()
print(result)
第十二节 with的上下文管理器协议
- 上下文管理器协议。python是基于协议来编程的。
- 通过with调用 自动调用__enter__ 执行完成后调用__exit__
# 上下文管理器协议
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简化上下文管理器
- import contextlib 中的**@contextlib.contextmanager修饰的函数必须是一个生成器**。
- @contextlib.contextmanager可以把生成器函数变成上下文管理器。
- yield之前的代码 with调用函数之前执行 yield之后的代码 调用完成后执行
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")
183

被折叠的 条评论
为什么被折叠?



