python/继承和多态-获取对象信息-实例属性和类属性

文章目录

    • 继承和多态
      • 继承
      • 多态
    • 静态语言 vs 动态语言
    • 获取对象信息
      • 使用type()
        • 判断基本类型
        • 判断对象是否是函数
      • 使用isinstance()
      • 使用dir()
    • 实例属性和类属性

继承和多态

继承

在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
如:编写的一个名为Animal的class,有一个run的方法可以直接打印

class Animal(object):
    def run(self):
        print('Animal is running...')

当我们需要编写DogCat类时,就可以直接从Animal继承

class Dog(Animal):
    pass

class Cat(Animal):
    pass

Dog和Cat就是子类,Animal就是父类

继承最大的好处是子类获得了父类的全部功能。由于Animial实现了run()方法,因此,DogCat作为它的子类,什么事也没干,就自动拥有了run()方法:

dog = Dog()
dog.run()--》Animal is running...

cat = Cat()
cat.run()---》Animal is running...

也可以对子类增加一些方法,比如Dog类:

class Dog(Animal):

    def run(self):
        print('Dog is running...')

    def eat(self):
        print('Eating meat...')

继承的第二个好处需要我们对代码做一点改进
无论是Dog还是Cat,它们run()的时候,显示的都是Animal is running...,符合逻辑的做法是分别显示Dog is running...Cat is running...,因此,对DogCat类改进,以Dog为例:

class Dog(Animal):

    def run(self):
        print('Dog is running...')--->Dog is running...

当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态

多态

要理解什么是多态,我们首先要对数据类型再作一点说明。当我们定义一个class的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样

a = list() # a是list类型
b = Animal() # b是Animal类型
c = Dog() # c是Dog类型

判断一个变量是否是某个类型可以用isinstance()判断:

>>> isinstance(a, list)
True
>>> isinstance(b, Animal)
True
>>> isinstance(c, Dog)
True
'看来a、b、c确实对应着list、Animal、Dog这3种类型'
'但是,还可以发现:'
>>> isinstance(c, Animal)
True
c不仅仅是Dog,c还是Animal!

因此如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类
,但是反之不行

>>> b = Animal()
>>> isinstance(b, Dog)
False

再看一个例子:

'一个函数接受了一个`Animal类型`的变量'
def run_twice(animal):
    animal.run()
    animal.run()

当我们传入Animal的实例时,run_twice()就打印出:

'传入实例,相当于:s = Animal() run_twice(s)'
run_twice(Animal())
Animal is running...
Animal is running..

当我们传入Dog的实例时,run_twice()就打印出:

>>> run_twice(Dog())
Dog is running...
Dog is running...

仔细想想,现在,如果我们再定义一个Tortoise类型,也从Animal派生:

class Tortoise(Animal):
    def run(self):
        print('Tortoise is running slowly...')

当我们调用run_twice()时,传入Tortoise的实例:


>>> run_twice(Tortoise())
Tortoise is running slowly...
Tortoise is running slowly...

你会发现,新增一个Animal的子类,不必对run_twice()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。

多态的好处就是,当我们需要传入Dog、Cat、Tortoise……时,我们只需要接收Animal类型就可以了,因为Dog、Cat、Tortoise……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,这就是多态的意思:

对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog、Cat还是Tortoise对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:

特性
对扩展开放:允许新增Animal子类;
对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。
继承还可以一级一级地继承下来,
python/继承和多态-获取对象信息-实例属性和类属性_第1张图片

静态语言 vs 动态语言

对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法

对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了:

class Timer(object):
    def run(self):
        print('Start...')

这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()方法,返回其内容。但是,许多对象,只要有read()方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()方法的对象。

获取对象信息

当我们拿到一个对象的引用时,如何知道这个对象是什么类型、有哪些方法呢?

使用type()

判断基本类型

判断对象类型,使用type()函数:
基本类型都可以用type()判断:

>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>

如果一个变量指向函数或者类,也可以用type()判断:

>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>

但是type()函数返回对应的Class类型。
如果我们要在if语句中判断,就需要比较两个变量的type类型是否相同

>>> type(123)==type(456
>'判断基本的数据类型可以直接写int,str等'
>>> type(123)==int--->True
>>> type('abc')==type('123')--->True
>>> type('abc')==str--->True
>>> type('abc')==type(123)--->False

判断对象是否是函数

要判断一个对象是否是函数,可以使用types模块中定义的常量:

import types
def fn():
	pass
print(type(f) == types.FunctionType)--->Ture
print(type(abs) == types.BuiltinFunctionType)--->Ture
print(type(lambda x:x*x) == types.LambdaType)--->Ture
print(type((x for x in range(10)))==types.GeneratorType)--->Ture

使用isinstance()

对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数
回顾上面的例子,继承关系为:

object -> Animal -> Dog -> Husky

isinstance()就可以告诉我们,一个对象是否是某种类型。先创建3种类型的对象:

>>> a = Animal()
>>> d = Dog()
>>> h = Husky()
>>> isinstance(h, Husky)
True --->'因为h变量指向的就是Husky对象。'
>>> isinstance(h, Dog)
True--->'Husky是从Dog继承下来的,所以,h也还是Dog类型'

也就是说:
isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。
因此:

>>> isinstance(h, Animal)
True-->'h还是Animal类型:'
>>> isinstance(d, Dog) and isinstance(d, Animal)
True

但是:属于Dog类型的d不是Husky类型:

>>> isinstance(d, Husky)
False

能用type()判断的基本类型也可以用isinstance()判断:

>>> isinstance('a', str)--->True
>>> isinstance(123, int)--->True
>>> isinstance(b'a', bytes)--->True

并且还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple:

>>> isinstance([1, 2, 3], (list, tuple))--->True
>>> isinstance((1, 2, 3), (list, tuple))--->True

总是优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽”。

使用dir()

如果要获得一个对象的所有属性和方法可以使用dir()函数它返回一个包含字符串的list
比如,获得一个str对象的所有属性和方法:

>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的:

>>> len('ABC')--->3
>>> 'ABC'.__len__()--->3

自己写的类,如果也想用len(myObj)的话,就自己写一个__len__()方法:

>>> class MyDog(object):
...     def __len__(self):
...         return 100
...
>>> dog = MyDog()
>>> len(dog)--->100
'剩下的都是普通属性或方法,比如lower()返回小写的字符串:'
>>> 'ABC'.lower()
'abc'

仅仅把属性和方法列出来是不够的,配合getattr()setattr()以及hasattr(),我们可以直接操作一个对象的状态:

>>> class MyObject(object):
...     def __init__(self):
...         self.x = 9
...     def power(self):
...         return self.x * self.x
...
>>> obj = MyObject()
紧接着,可以测试该对象的属性(注意属性都要加'')>>> hasattr(obj, 'x')---> # 有属性'x'吗?
>>> obj.x--->9
>>> hasattr(obj, 'y')--->False # 有属性'y'吗?
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') --->True# 有属性'y'吗?
>>> getattr(obj, 'y')--->19 # 获取属性'y'
>>> obj.y --->19# 获取属性'y'

如果试图获取不存在的属性,会抛出AttributeError的错误:

>>> getattr(obj, 'z') # 获取属性'z'
Traceback (most recent call last):
  File "", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'
'可以传入一个default参数,如果属性不存在,就返回默认值:'
>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404

也可以获得对象的方法:

>>> hasattr(obj, 'power') # 有属性'power'吗?
True
>>> getattr(obj, 'power') # 获取属性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # 调用fn()与调用obj.power()是一样的
81

实例属性和类属性

由于Python是动态语言,根据类创建的实例可以任意绑定属性。

给实例绑定属性的方法是通过实例变量,或者通过self变量:

class Student(object):
    def __init__(self, name):
        self.name = name
s = Student('Bob')
s.score = 90

但是,如果Student类本身需要绑定一个属性呢?可以直接在class中定义属性,这种属性是类属性,归Student类所有:

class Student(object):
    name = 'Student'

当我们定义了一个类属性后,这个属性虽然归类所有,但类的所有实例都可以访问到

在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。

练习
为了统计学生人数,可以给Student类增加一个类属性,每创建一个实例,该属性自动增加:

class Student(object):
    count = 0#公共类属性

    def __init__(self, name):
        self.name = name
        Student.count += 1
        self.count += 1#会对上一步的count值产生影响

你可能感兴趣的:(Python笔记,python)