类与对象的深入— 上篇

1. 鸭子类型和多态

鸭子类型

  • 鸭子类型是动态类型的一种风格,不是由继承特定的类或者实现特定的接口,而是当前的方法和属性的集合所决定的。
  • 鸭子类型关注的不是对象的类型本身,而是它如何使用
  • 动态语言调用实例方法时不检查类型,只要方法存在,参数正确,就可以调用。这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
  • 在一下代码中的animal_l 列表中,Cat、Dog和Duck都是变量,而只有在调用的同时给类对象加上() 才能够调用 类对象里面的方法。否则会抛出异常。
 class Duck(object):

    def walk(self):
        print('I am a duck')

    def swim(self):
        print('I can swim')

class Cat(object):

    def walk(self):
        print('I am a cat')

    def swim(self):
        print('I can swim')

class Dog(object):

    def walk(self):
        print('I am a dog')

    def swim(self):
        print('I can swim')

animal_l = [Duck, Cat, Dog]
for animal in animal_l:

    # animal.walk()   # TypeError: walk() missing 1 required positional argument: 'self'
    animal().walk()
  • 在看网上的经典代码:
class Duck(object):

    def walk(self):
        print('I am a duck and I can walk')

    def swim(self):
        print('I am a duck and I can swim')

class Cat(object):

    def walk(self):
        print('I am a cat and I can walk')

    def swim(self):
        print('I am a cat and I can swim')

class Dog(object):

    def walk(self):
        print('I am a dog and I can walk')

    def swim(self):
        print('I am a dog and I can swim')

dog = Dog()
duck = Duck()
cat = Cat()

def swim_walk(animal):
    animal.walk()
    animal.swim()

print(swim_walk(cat))
# print(swim_walk(dog))
# print(swim_walk(duck))

在以上的代码中,函数swim_walk的参数animal时任意类型,它可以接受任意类型的参数。而Dog类、Duck类和Cat类有着一样的方法,当一个函数调用Cat类,并且用到了两个方法walk()和swim()。我们传入Dog类或者Duck类一样可以运行,函数并不会检查传入的是不是Cat类,只要它拥有 walk() 和swim() 方法就可以。

  • 鸭子类型的特定总结:
    • 变量绑定的 类型具有不确定性
    • 函数可以接受任意类型的参数
    • 调用方法时不检查提供的参数类型
    • 调用是否成功由参数的属性和方法决定
    • 调用不成抛出错误,也就是调用的对象中没有实现该功能的方法

多态

  • 定义类型与运行类型不一样就是多态
  • Python 不支持多态,也不用支持多态,而是一种多态语言。 多态的概念时应用于Java 和 c# 这一类强类型语言中

2. 抽象基类(abc模块)

抽象基类的简单介绍
  • 抽象基类就是类里定义了纯虚成员函数的类。纯虚函数只提供了接口,并没有具体实现。抽象基类不能被实例化,经常作为基类供子类继承,子类中重写虚函数,实现具体的接口
  • 简单来说,抽象基类就是定义各种方法而不做具体实现的类,任何继承自抽象基类的类必须实现这些方法,否则无法实例化。
  • 抽象基类的特点(主要用于接口设计):
    • 规定继承类必须具有抽象基类指定的方法
    • 抽象基类无法实例化
  • 注意:python 中并没有提供抽象类和抽象方法,但是提供了内置模块 abc 来模拟实现抽象类
  • 抽象基类的应用场景
    • 检查某个基类中是否含有某种方法
    • 强制子类必须实现某些方法
  1. 检查某个基类中是否含有某种方法

class demo(object):

    def __init__(self, l):
        self.l = l

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

list = [1, 2, 3, 4]

d = demo(list)
print(len(d)) 
 # 之所以输出的结果会是4。是因为demo 类中含有__len__ 的方法中刚好返回 renturn len(self.l)  所以返回的结果会是4
  • 如何判断 对象中含有某种方法呢,以 d 为例
print(hasattr(d, '__len__'))  # 判断的是 d 中是否含有__len__方法
hasattr 的源码如下:
  • hasattr 的源码如下:我们可以通过源码得知 hasattr 返回的类型的判断,返回的结果也就是布尔值

def hasattr(*args, **kwargs): # real signature unknown
    """
    Return whether the object has an attribute with the given name.
    
    This is done by calling getattr(obj, name) and catching AttributeError.
    """
  • 判断 实例化对象 d 中是否含有__len__ 方法,也是判断d 是否是Sized 的子类

from collections.abc import Sized   # 判断d 是否是 Sized 这个类
print(isinstance(d, Sized))  # True   isinstance 判断的是 d 是否是 Sized 的一个对象

Sized 的源码如下:
  • 观察以下的源码可以发现,Sized类是通过判断d 是否是Sized的子类 进而判断d 中是否含有__len__ 方法。 该类也可以进行其他方法的判断
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

注意:
  • 注册的虚拟子类不会继承抽象基类的任何方法和属性
  1. 强制子类必须实现父类的方法
  • 如果我们在父类 中主动抛出异常,我们不重写子类的方法的话,结果会输出异常
class CacheBase(object):

    def create(self):
        raise NotImplemented

    def dele(self):

        raise NotImplemented

class RedisBase(CacheBase):


    pass

    # def dele(self):
    #     pass
r = RedisBase()
print(r.create())   # TypeError: exceptions must derive from BaseException

  • 使用抽象基类重写之后
 import abc

class CacheBase(metaclass=abc.ABCMeta):

    @abc.abstractmethod
    def create(self):
        raise NotImplemented

    def dele(self):

        raise NotImplemented

class RedisBase(CacheBase):

    def create(self):
        print('create')

r = RedisBase()   
print(r.create())  # create 
注意:我们为什么使用抽象基类进行重写呢?
  • 使用的抽象基类的强制重写装饰器 可以使子类继承父类的时候,强制对某些特定的方法进行重写,否则会抛出错误
  • 我们使用正常方法重写的时候,如果子类没有对父类的方法进行重写,然后实例化类对象的时候,就算父类中主动抛出异常,最后的输出结果上也不会出现异常
class CacheBase(object):

	def create(self):
        raise NotImplemented

    def dele(self):
        raise NotImplemented

class RedisBase(CacheBase):

    pass

r = RedisBase()

  • 使用抽象基类装饰器去重写的时候,没有进行方法的重写就算不去访问父类的方法,但是结果也会抛出异常
import abc

class CacheBase(metaclass=abc.ABCMeta):

    @abc.abstractmethod
    def create(self):
        pass

    def dele(self):

        pass

class RedisBase(CacheBase):

    pass

r = RedisBase()   
# TypeError: Can't instantiate abstract class RedisBase with abstract methods create

collection.abc模块常用的抽象基类::

__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",
           ]

3. type 与 isinstance 的区别

  • type 不考虑继承关系
  • isinstance 考虑继承关系
class A(object):

    pass

class B(A):
    pass

b = B()

print(isinstance(b, B))  # True
print(isinstance(b, A))  # True

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

# 判断内存地址
print(type(b) is B)  # True
print(type(b) is A)  # False

4. 类属性和实例属性

  • 查找顺序
  • 对象是可以向上查找的,所以实例属性可以访问到类属性
    • 如果对象拥有自己实例属性,则输出的就是自己的
  • 类只能访问到类属性,不能向下访问实例属性
class A(object):

    cls = 1

    def __init__(self, z, l):
        self.z = z
        self.l = l

a = A(3, 4)

print(a.cls, a.z, a.l)  # 1 3 4

print(A.cls)  # 1
print(A.z)  # AttributeError: type object 'A' has no attribute 'z'
  • 多继承查找顺序
    • 继承关系如下:
      在这里插入图片描述
      在这里插入图片描述
    • 继承关系如下又该如何继承:
      在这里插入图片描述
      在这里插入图片描述
注意:
  • 继承顺序的查看可以依据 __mro__ 方法
# 菱形继承
class A(object):

    pass

class B(A):

    pass

class C(A):

    pass

class D(B, C):

    pass

print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

5. Python的自省机制

  • 在日常生活中,自省就是自我检查的一种行为

  • 在计算机编程中,自省是指:检查某些事物以确定它是什么、它知道什么、它能够做什么。自省提供了极大的灵活性和控制力

  • 简单来说:自省就是面向对象的语言所写的程序在运行的时候,能够知道对象的类型。也就是说,运行时能够获知对象的类型

  • python中常用的自省机制(函数)有:dir()、type()、hasattr()和isinstance(),通过这些函数,我们能够在程序运行时得知对象的类型,判断对象是否存在某种属性,访问对象的属性。

  • dir()

    • dir() 函数可能是最知名的自省机制了。它返回传递给它任何对象的属性名称经过排列的列表。如果不指定对象,则dir() 返回当前作用域中的名称。
import keyword
print(dir(keyword))

# ['__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'iskeyword', 'kwlist', 'main']


  • type()

    • type() 函数有助于我们确定对象是字符串还是整数,或其它类型的对象
  • hasattr()

    • 对象拥有属性,并且dir() 函数会返回这些属性的列表。但是,有时候我们只想测一个或多个属性是否存在。如果对象具有我们正在考虑的属性,那么通常希望只检索该属性。这个任务可以由 hasattr() 和 getattr() 函数来完成。
print(hasattr(id, '__doc__'))
# True
  • isinstance()

  • 可以使用 isinstance() 来测试函数对象,以确定它是否是某个特定类型或定制类的实例:
print(isinstance('python', str))
# True

6. super 函数

  • 如果我们想要使用父类中的方法,以下的方法不使用继承会造成代码的冗余
class Teacher(object):

    def __init__(self, age, name, weight):
        self.age = age
        self.name = name
        self.weight = weight

    def speak(self):

        print('我叫{}, 我{}岁, 我体重{}kg'.format(self.name, self.age, self.weight))


class Student(Teacher):

    def __init__(self, age, name, weight, height):
        self.age = age
        self.name = name
        self.weight = weight
        self.height = height

    def speak(self):

        print('我叫{}, 我{}岁, 我体重{}kg, 我身高{}cm'.format(self.name, self.age, self.weight, self.height))

s = Student(22, 'g', 70, 180)
print(s.speak())


  • 如果使用下面的继承方法,又会指定的是特定的类

class Teacher(object):

    def __init__(self, age, name, weight):
        self.age = age
        self.name = name
        self.weight = weight

    def speak(self):

        print('我叫{}, 我{}岁, 我体重{}kg'.format(self.name, self.age, self.weight))


class Student(Teacher):

    def __init__(self, age, name, weight, height):
        Teacher.__init__(self, age, name, weight)

        self.height = height

    def speak(self):

        print('我叫{}, 我{}岁, 我体重{}kg, 我身高{}cm'.format(self.name, self.age, self.weight, self.height))

s = Student(22, 'g', 70, 180)
print(s.speak())



  • 使用super 函数继承 可以省去很不顾虑

class Teacher(object):

    def __init__(self, age, name, weight):
        self.age = age
        self.name = name
        self.weight = weight

    def speak(self):

        print('我叫{}, 我{}岁, 我体重{}kg'.format(self.name, self.age, self.weight))


class Student(Teacher):

    def __init__(self, age, name, weight, height):
        super().__init__(age, name, weight)

        self.height = height

    def speak(self):

        print('我叫{}, 我{}岁, 我体重{}kg, 我身高{}cm'.format(self.name, self.age, self.weight, self.height))

s = Student(22, 'g', 70, 180)
print(s.speak())
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值