python核心编程 第4章 深入类和对象

4-1 鸭子类型和多态性

什么是鸭子类型

当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子, 那么这只鸟就可以被称为鸭子.

在下面这段代码中, 所有的类实现了同一个方法, 这些类就可以归为同一种类型, 这样在调用的时候就可以都调用say方法, 从而实现了多态, 一种接口多种实现.

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] # 类也是对象, 可以被添加到列表中, 而且python是动态语言, 对于变量不需要指定类型
for animal in animal_list:
    animal().say()
#i am a dog
#i am fish
#i am Duck

列表的extend

def extend(self, iterable): # real signature unknown; restored from doc
“”" L.extend(iterable) -> None – extend list by appending elements from the iterable “”"

list.extend()
因为传入的只需要是iterable类型即可, 所以list可以扩展tuple、set等都可以. 或者我们实现一个类可以迭代,也可以放到extend中.

class Company(object):
    def __init__(self,employee_list):
        self.employee = employee_list
    def __getitem__(self,item):
        return self.employee[item]

company = Company(["tom","bob","zz"])

a = ["xaiomi","xaiohu","uzi"]

a.extend(company)
print(a)

# ['xaiomi', 'xaiohu', 'uzi', 'tom', 'bob', 'zz']

我们在几个对象中都实现了某一个方法名, 这些类我们就可以通用, 比如上面的say和__getitem__, 而魔法函数也是利用的python的鸭子类型, 只要实现了__getitem__就可迭代, 就可以传入extend中.

4-2抽象基类

  • 抽象基类是一个虚拟的类, 相当于一个模板, 定义一些方法, 所有继承这个基类的类必须覆盖抽象基类里面的方法
  • 抽象基类是无法用来实例化的
    为什么要有抽象基类

因为python是基于鸭子类型的, 所以其实只要实现某些方法就可以了, 那为什么还要抽象基类呢?

  • 第一种用法:我们去检查某个类是否有某一种方法

某些情况之下希望判定某个对象的类型, 可以使用hasattr判断是否实现某方法或者使用isinstance(推荐)去判断一个类是否是指定的类型, Sized就是一个实现__len__的抽象基类.

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

from collections.abc import Sized
print(isinstance(com, Sized))
>>> True

# Sized内部的类方法, 在调用isinstance(com, Sized)时cls就是Sized对象, C就是com对象,然后判断com对象有没有实现__len__方法.
@classmethod
def __subclasshook__(cls, C):
    if cls is Sized:
        return _check_methods(C, "__len__")
    return NotImplemented

# isinstance还会找到继承链去进行判断
class A:
    pass

class B(A):
    pass

b = B()
print(isinstance(b, A))
>>> True
  • 第二种用法: 强制某个子类必须实现某些方法
# 模拟抽象基类, 只有在调用set方法的时候才会抛出异常
class CacheBase():
    def get(self, key):
        raise NotImplementedError
    def set(self, key, value):
        raise NotImplementedError

class RedisCache(CacheBase):
    def set(self, key, value):
        pass

redis_cache = RedisCache()
redis_cache.set("key", "value")

# 使用abc模块, 在初始化的时候就会去判断有没有重载基类的方法,没有就抛异常
import abc

class CacheBase(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def get(self, key):
        pass

    @abc.abstractmethod
    def set(self, key, value):
        pass

class RedisCache(CacheBase):
    def set(self, key, value):
        pass
    def get(self, key):
        pass

redis_cache = RedisCache()

collection.abc模块

在这个模块中定义了很多通用的抽象基类, 比如Sized. 但是这些抽象基类定义出来并不是用来继承的, 更多的是让我们理解接口的一些定义. 推荐使用鸭子类型或者多继承(Mixin)实现, 而少用抽象基类.

__all__ = ["Awaitable", "Coroutine",
           "AsyncIterable", "AsyncIterator", "AsyncGenerator",
           "Hashable", "Iterable", "Iterator", "Generator", "Reversible",
           "Sized", "Container", "Callable", "Collection",
           "Set", "MutableSet",
           "Mapping", "MutableMapping",
           "MappingView", "KeysView", "ItemsView", "ValuesView",
           "Sequence", "MutableSequence",
           "ByteString",
           ]

声明抽象基类

  • metaclass = abc.ABCMeta
  • @abc.abstractmethod

type与isinstance区别:

  1. type用于求一个未知数据类型的对象,isinstance用于判断一个对象是否是已知类型;
  2. type不认为子类是父类的一种类型,isinstance认为子类是父类的一种类型,即子类对象也属于父类类型.

class A:
   pass

class B(A):
   pass

b = B()



# instance用于判断一个对象是否是已知类型
print(isinstance(b, B)) # True
print(isinstance(b, A)) # True isinstance会根据继承链去判断

# 求一个未知数据类型的对象
print(type(b)) # <class '__main__.B'>

# type不认为子类是父类的一种类型,isinstance认为子类是父类的一种类型,即子类对象也属于父类类型.

# is是去判断这两个是不是一个对象, 即id是否相同
# ==是判断值是否相等
print(type(b) is B) # True 得到的是B的地址
print(type(b) is A) # False

print(id(B)) # 1943999460392
print(id(type(b))) # 1943999460392
print(B) # <class '__main__.B'>

a=[1,2,3]
b=[1,2,3]
c=a

print(id(a)) # 2065494827720
print(id(b)) # 2065493557192
print(id(c)) # 2065494827720

print(a is b) # False
print(c is a) # True
print((a==b)) # True 

类变量和实例变量

class A:
    aa = 1 # 类变量
     __init__(self, x, y):
        self.x = x # 实例变量
        self.y = y

a = A(2,3)

A.aa = 11 # 会将类A内存中的的aa修改为11
a.aa = 22 # 会在实例a内存中创建一个变量aa赋值22, 类A内存中的aa还是11
print(a.x, a.y, a.aa) # a.aa首先会去实例a的内存中找是否有aa, 如果没有再去类A的内存中找
print(A.aa) # 去类A的内存中找aa, 值为11

b = A(3,5)
print(b.aa) # 类A内存中aa已经修改为11

>>> 2 3 22
>>> 11
>>> 11
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值