Python(8)面向对象编程

文章目录

    • 一、什么是面向对象编程
    • 二、类(class)和实例(instance)
    • 三、特性之一——数据封装
    • 四、访问限制
    • 五、特性之二、三——继承和多态
      • -继承
      • -多态
    • 六、获取对象信息
      • -Type()
      • -isinstance()
      • -dir()
    • 七、实例属性和类属性

此文参考廖雪峰官网:面向对象编程 - 廖雪峰的官方网站 (liaoxuefeng.com)

一、什么是面向对象编程

  • 面向对象编程(Object Oriented Programming),简称OOP,是一种程序设计思想,OOP把对象当作程序的基本单元,一个对象包含了数据和操作数据的函数
  • 面向对象的程序设计把计算机程序当作一组对象的集合,每个对象都可以接受其他对象发来的消息,并且进行处理,而计算机程序的执行就是一系列消息在各对象之间进行传递,和面向对象不同,面向过程的程序设计把计算机程序当作一系列的命令集合,即一组函数的顺序执行,为了简化程序设计,面向过程把函数继续切分为子函数,从而降低系统的复杂度
  • 在Python中,所有的数据类型都可以看作为对象,当然也可以自定义对象,自定义的对象数据类型就是面向对象中类(class)的概念,下面来看一个案例来说明面向对象和面向过程的区别:
- 现在我们需要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以使用一个字典表示,例如:
# -*- coding: utf-8 -*-
std_1 = {'name':'zhangsan','score':98}
std_2 = {'name':'lisi','score':97} 

def print_score(std):
    return '%s : %s' % (std['name'],std['score'])
    
print(print_score(std_1))
print(print_score(std_2))

#输出:
zhangsan : 98
lisi : 97
  
- 如果使用面向对象的程序设计思想,首先思考的不是程序的执行流程,而是'学生'的这种数据类型应该被看作一个'对象',这个对象有'name''score'两种'属性(property)',如果想要输出一个学生的成绩,首先就需要先创建一个'学生'对应的对象,然后给这个'学生'对象发送一个'打印成绩''消息',让对象自己把指定的数据打印出来,例如:
# -*- coding: utf-8 -*-
class Student(object):

    def __init__(self,name,score):
        self.name = name
        self.score = score

    def print_score(self):
        print('%s : %s' % (self.name,self.score))

- 给对象发送消息实际就是调用对象对应的'关联函数',这个关联函数也叫做'对象的方法',下面就是面向对象的程序调用
zhangsan = Student('zhangsan',98)
lisi = Student('lisi',97)
zhangsan.print_score()
lisi.print_score()

#输出:
zhangsan : 98
lisi : 97
  • 对象(class)是一种抽象的概念,上面定义的对象Student,指的就是学生这个概念,而实例(instance)则指一个个具体的对象,例如上面的zhangsanlisi就是两个具体的Student,也就是实例
  • 从上面的案例可以看出,面向对象的程序设计思想其实就是抽象出对象(class),然后根据对象创建实例(instance)
  • 最后,面向对象的抽象程度比函数高,因为一个对象既包含数据,也包含操作数据的方法,数据封装、继承、多态是面向对象的三大特点

二、类(class)和实例(instance)

  • 面对对象最重要的概念就是类(class)实例(instance),类是抽象的模板,比如上面的Student类,而实例是根据类创建出来的具体的对象,每个对象都有相同的方法,但是各自的数据可能不同,例如上面的zhangsanlisi
  • Student类为例:
# -*- coding: utf-8 -*-
class Student(object):
    pass

(1)先看第2行

class Student(object):
 
在Python中,类是通过'class'关键字进行定义的,'class'后面跟着的是类名,类名通常是以大写字母开头的,紧接着就是'(object)',这个表示的是'Student'类是从'object'类继承下来的,继承这个概念在后面会说
通常如果说没有合适的继承类,那么就可以直接使用'object'类,这是所有的类最终都会继承的类

(2)定义好了Student类,就可以根据Student类创建出Student的实例,而创建实例是通过类名()实现的,例如:

>>> class Student(object):
...     pass
... 
>>> zhangsan = Student()
>>> zhangsan
<__main__.Student object at 0x0000018C3E6A6A10>
>>> Student
<class '__main__.Student'>

可以看到,变量'zhangsan'指向的是'Student'实例,输出的信息中,'object at'后面是内存地址,每个object的地址都不一样,而'Student'本身就是一个类

还可以给一个实例变量自由的绑定属性,例如:
>>> zhangsan.name = 'zhangsan' 
>>> zhangsan.name
'zhangsan'

(3)由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些必要的属性写进入,通过一个特殊的__init__方法,在创建实例的时候,把namescore等属性绑定,例如:

>>> class Student(object):
...     def __init__(self,name,score):
...             self.name = name
...             self.score = score
... 
>>> zhangsan = Student('zhangsan',98) 
>>> zhangsan.name
'zhangsan'
>>> zhangsan.score
98
>>> lisi = Student()    #传入空参数
Traceback (most recent call last):
  File "", line 1, in <module>
TypeError: Student.__init__() missing 2 required positional arguments: 'name' and 'score'
>>> lisi = Student('lisi',97,22)  #多传入一个参数
Traceback (most recent call last):
  File "", line 1, in <module>
TypeError: Student.__init__() takes 3 positional arguments but 4 were given

#注意特殊方法__init__是两个_
可以看到在定义特殊方法'__init__'时,后面的第一个参数是'self',而且也必须是'self',这个参数表示创建的实例本身,因此,在'__init__'方法内部,就可以把各种属性绑定到'self',因为'self'就指向创建的实例本身
不过,在有了'__init__'方法之后,在创建实例的时候,就不能传入空参数或多个参数,必须传入和'__init__'方法相匹配的参数,但是'self'参数不需要传,Python解释器会自己把'实例变量'传入到self

(4)和普通函数相比,在类中定义的函数只有一点不同,那就是函数的第一个参数永远都是self,并且在调用该函数时,不用传递参数,除此之外,和普通函数没有其他区别,仍然可以使用默认参数、可变参数、关键字参数、命名关键字参数

三、特性之一——数据封装

  • 面向对面编程的一个重要的特点就是数据封装,在上面的Student类中,根据对象创建的实例里,都有各自的namescore的数据,可以通过函数来访问这些数据,例如:

    >>> class Student(object):
    ...     def __init__(self,name,score):
    ...             self.name = name
    ...             self.score = score
    ... 
    >>> zhangsan = Student('zhangsan',98) 
    >>> zhangsan.name
    'zhangsan'
    >>> zhangsan.score
    98
    >>> def print_score(std):
    ...     return '%s : %s' % (std.name,std.score) 
    ... 
    >>> print_score(zhangsan) 
    'zhangsan : 98'
    
  • 但是,既然Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样,就可以把数据封装起来了,这些封装数据的函数和Student类本身是关联起来的,我们称之为类的方法,更改后可以这样写:

    >>> class Student(object):
    ...     def __init__(self,name,score):
    ...             self.name = name
    ...             self.score = score
    ...     def print_score(self):
    ...             return '%s : %s' % (self.name,self.score) 
    ... 
    >>> zhangsan = Student('zhangsan',98) 
    >>> zhangsan.print_score()           
    'zhangsan : 98'
    
    可以发现,'zhangsan'可以直接引用'Student'类中的'print_score'函数
    同样的'print_score'函数的参数也是'self',也是不用传递的,直接在实例变量上调用即可
    
    如果有第三个参数age,但是在__init__中并没有定义,可以这样写:
    # -*- coding: utf-8 -*-
    class Student(object):
        
        def __init__(self,name,score):
            self.name = name
            self.score = score
    
        def print_score(self):
            return print('%s %s %s' % (self.name,self.score,self.age))
    
    zhangsan = Student('zhangsan',98)
    zhangsan.age = 22  #定义实例变量age
    zhangsan.print_score()        
    
    #输出:
    zhangsan 98 22
    
  • 从上面可以看出,在根据Student类创建实例时,只需要指定namescore的值即可,关于如何打印出来,这些都是在Student类的内部定义的,从而使这些数据和逻辑被封装起来,调用时并不知道内部的细节

  • 封装的另一个好处就是可以给Student类增加新的方法,例如:

# -*- coding: utf-8 -*-
class Student(object):
    
    def __init__(self,name,score):
        self.name = name
        self.score = score

    def print_score(self):
        return print('%s : %s' % (self.name,self.score))

    def get_grade(self):
        if self.score >= 90 and self.score <= 100:
            return print('A')
        elif self.score >= 80:
            return print('B')
        else:
            return print('C')

zhangsan = Student('zhangsan',98)
zhangsan.print_score()    
zhangsan.get_grade()    

#输出:
zhangsan : 98
A
  • 总结:

    1. 类是创建实例的模板,而实例是一个个具体的对象,每个实例拥有的数据都是相互独立的,互不影响

    2. 方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据

    3. 通过在实例上调用方法,其实就是直接操作了对象内部的数据,并且无需指定方法内部的实现细节

    4. 和静态语音不同,Python允许对实例变量绑定任何数据,这样的效果就是,就算是根据同一类创建出的实例,实例拥有的变量名称可能都是不一样的,例如:

      # -*- coding: utf-8 -*-
      class Student(object):
          
          def __init__(self,name,score):
              self.name = name
              self.score = score
      
      
      zhangsan = Student('zhangsan',98)
      lisi = Student('lisi',98)
      zhangsan.age =  22
      lisi.aaa = 333
      print(zhangsan.age)
      print(lisi.aaa)
      print(zhangsan.aaa)
      #输出
      22
      333
      Traceback (most recent call last):
        File "d:\工作\work\py\test02.py", line 15, in <module>
          print(zhangsan.aaa)
      AttributeError: 'Student' object has no attribute 'aaa'
          
          
      因为可以绑定任何数据,所有说'zhangsan'拥有name,score,age三个变量,而'lisi'则拥有name,score,aaa三个变量,因为是实例是相互独立的,所有'zhangsan''lisi'之间的变量数量、变量名等都不是互通的,所以zhangsan在最后调用aaa变量时报错了
      

四、访问限制

  • 类(class)中,可以有属性和方法,而外部代码可以通过调用实例变量的方法来操作或获取数据,从而隐藏了内部的复杂逻辑

  • 但是,从上面Student类的定义来看,外部代码可以随意改变一个实例的属性,例如:

    >>> class Student(object):
    ...     def __init__(self,name,score):
    ...             self.name = name
    ...             self.score = score
    ...     def print_score(self):
    ...             return print('%s : %s' % (self.name,self.score)) 
    ... 
    >>> zhangsan = Student('zhangsan',98) 
    >>> zhangsan.print_score()
    zhangsan : 98
    >>> zhangsan.score = 22   #修改score的值
    >>> zhangsan.print_score()           #再次调用,发现值已经变了
    zhangsan : 22
    
  • 可以看到实例属性的值是可以随意修改的,如果想要实例的内部属性不被外部修改,可以这样做:

    #可以在属性的名称前面加"__",在Python中,实例的变量名如果以__开头的话,那么这个变量就成了私有变量,只有内部可以访问,外部无法访问
    >>> class Student(object):
    ...     def __init__(self,name,score):
    ...             self.__name = name
    ...             self.__score = score
    ...     def print_score(self):
    ...             return print('%s : %s' % (self.__name,self.__score)) 
    ... 
    >>> zhangsan = Student('zhangsan',98)
    >>> zhangsan.print_score()
    zhangsan : 98
        
    #现在想对Student类中的score属性的值进行修改,再调用print_score方法时,发现并没有修改,最后发现,其实这相当于是新创建了一个score属性
    >>> zhangsan.score = 22   
    >>> zhangsan.print_score()   
    zhangsan : 98
    >>> zhangsan.score
    22
    
    #上面不行的话,有的人可能会说是因为属性名称不一样,那么现在来调用一下__score属性,发现也无法调用,这是因为在使用私有变量后,Python解释器就把__name的对外名称变成了_Student__score,使用这样的格式进行调用,是可以调用成功的,不过强烈建议不要使用这种方法进行修改、调用属性数据
    >>> zhangsan.__score 
    Traceback (most recent call last):
      File "", line 1, in <module>
    AttributeError: 'Student' object has no attribute '__score'. Did you mean: 'score'?
    >>> zhangsan._Student__score 
    98
    
    #在不使用_Student__score这种格式去访问、修改指定的属性时,可以修改一下类,例如
    >>> class Student(object):
    ...     def __init__(self,name,score):
    ...             self.__name = name
    ...             self.__score = score
    ...     def print_score(self):
    ...             return print('%s : %s' % (self.__name,self.__score))
    ...     def set_name(self,name):
    ...             self.__name = name
    ...     def set_score(self,score):
    ...             self.__score = score
    ...     def get_name(self):
    ...             return print(self.__name) 
    ...     def get_score(self):
    ...             return print(self.__score) 
    ... 
    >>> zhangsan = Student('zhangsan',98) 
    >>> zhangsan.print_score()
    zhangsan : 98
    >>> zhangsan.get_name()    
    zhangsan
    >>> zhangsan.set_name('lisi') 
    >>> zhangsan.get_name()       
    lisi
    
    #虽然原先的"zhangsan.name = 98"也可以之间进行修改,但是通过在类中添加方法可以进行参数控制,例如:
    >>> class Student(object):
    ...     def __init__(self,name,score):
    ...             self.__name = name
    ...             self.__score = score
    ...     def set_score(self,score):        
    ...             if score >= 90 and score <=100:
    ...                     self.__score = score
    ...             else:
    ...                     return print('error')
    ...     def get_score(self):
    ...             return self.__score
    ... 
    >>> zhangsan = Student('zhangsan',98) 
    >>> zhangsan.get_score()  
    98
    >>> zhangsan.set_score(80) 
    error
    >>> zhangsan.get_score()   
    98
    >>> zhangsan.set_score(96) 
    >>> zhangsan.get_score()   
    96
    
    可以看到在'Student'类中,'set_score'方法添加了参数控制
    
  • 注意:

    1. 在Python中,变量名称类似于__xx__这样的以双下划线开头和结尾的是特殊变量,特殊变量是可以直接访问的,并不是私有变量
    2. 以一个下划线开头的,类似于_name这样的变量,是可以被外部访问的,但是在看到这样的变量时,要把它当成私有变量,不要随意访问,这也是一个不成文的规定
    3. 根据拥有私有变量的类去创建的实例,在调用时,不要直接使用类似于zhangsan.__score 这样的,因为虽然在类中定义的是__score,但其实Python解释器对外的名称是_Student__score,所以直接调用或修改zhangsan.__score,其实是调用的是另外一个变量

五、特性之二、三——继承和多态

-继承

  • 在上面的内容中,有说到过继承这一概念,例如:

    class Student(object)
    
  • 在这里Student(object)object就是Student继承的类,也就是说,我们在定义类时,是可以继承现有的类的,创建的新类叫做子类(subclass),而被继承的类叫做父类,也可以叫做基类、超类

  • 下面来看几个案例:

    - 定义一个'Animal'类,添加一个'run'方法
    >>> class Animal(object):
    ...     def run(self):
    ...             return print('Animal is running!!!')
    ... 
    
    - 定义'Dog''Cat'类,两个类都继承'Animal'>>> class Dog(Animal):
    ...     pass
    ... 
    >>> class Cat(Animal):
    ...     pass
    ... 
    
    - 创建'Dog''Cat'的实例,发现创建后的实例是可以直接使用'Animal'的方法的
    >>> dog = Dog()
    >>> dog.run()
    Animal is running!!!
    >>> cat = Cat()
    >>> cat.run()
    Animal is running!!!
    
  • 对于上面的案例来说,DogCat就是Animal的子类,AnimalDogCat的父类

  • 可以看到在DogCat继承Animal后,是可以直接使用Animal中的方法的,这也是继承最大的好处:子类可以获得父类的全部功能

  • 当然,也可以对子类修改或增加一些方法,例如:

    >>> class Dog(Animal):
    ...     def run(self): 
    ...             return print('Dog is running!!!')
    ...     def eat(self):
    ...             return print('Dog is Eating meat') 
    ... 
    
    - 需要注意的是,在修改完'Dog'类之后,直接调用'dog.run'变量,值还是原来的,需要重新创建实例
    >>> dog.run()
    Animal is running!!!
    >>> dog = Dog()
    >>> dog.run()   
    Dog is running!!!
    >>> dog.eat()  
    Dog is Eating meat
    
  • 在对子类添加、修改方法后,可以看到创建实例并调用时,输出的run变量的值变成了子类Dog中的run方法的输出,而不是Animal类中的run方法的输出,这是因为:当子类和父类同时存在相同的方法时,在调用子类的时候,子类的方法会覆盖掉父类的方法

    例如:DogAnimal的子类,并且DogAnimal类中都有run的方法,在这种情况下调用Dog的实例dog.run(),输出的是Dog类中run方法的输出,而不是Animal

-多态

  • 看过上面的案例之后,我们又获得了继承的另一个好处,多态

  • 当我们定义一个类时,我们实际上就定义了一种新的数据类型,并且这个数据类型和Python的基础数据类型,列表、字符串、字典等没有什么太大的区别,例如:

    - 先定义三个变量为不同的实例
    >>> a = list()
    >>> b = Animal()
    >>> c = Dog()
    
    - 使用'isinstance()'进行判断
    >>> isinstance(a,list) 
    True
    >>> isinstance(b,Animal) 
    True
    >>> isinstance(c,Dog)    
    True
    >>> isinstance(c,Animal)    
    True
    >>> isinstance(b,Dog)    
    False
    
    可以看到'a''b''c'分别对应着'list''Animal''Dog'三种类型,并且'c'不仅对应着'Dog',而且还对应的'Animal',这是因为'Dog'是从'Animal'继承下来的,即'Dog'本身就是'Animal'的一种,所以'c'才可以对应两种类型
    
    #注意:
    在继承关系中,如果一个实例的数据类型是某个子类,那么这个实例的数据类型也可以被看作是父类,但是,父类不能看作是子类,可以看到最后一个'isinstance(b,Dog)'的输出是'False'
    
  • 要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal类型的变量:

    >>> def run_twice(animal):
    ...     animal.run() 
    ...     animal.run()
    ... 
    >>> run_twice(Animal()) 
    Animal is running!!!
    Animal is running!!!
    >>> run_twice(Dog())    
    Dog is running!!!
    Dog is running!!!
    
    - 只看上面的感觉没有什么,但是现在来创建一个新的'Animal'的子类,去调用'run_twice'函数
    >>> class Tortoise(Animal):
    ...     def run(self):
    ...             return print('Tortoise is running slowly!!!') 
    ... 
    >>> run_twice(Tortoise()) 
    Tortoise is running slowly!!!
    Tortoise is running slowly!!!
    
    可以发现新创建的子类'Tortoise'可以在调用'run_twice'函数时作为参数直接传入,并且可以正常运行,输出的数据都不一样,这就是因为'多态'的特性
    
  • 看过上面的案例后,其实可以看出多态的作用就是:让具有不同功能的函数可以使用相同的函数名称,从而可以使用一个函数名调用不同功能的函数

  • 多态的特点:

    1. 只需要关心对象的方法名是否相同,不需要关系对象所属的类型
    2. 对象所属的类之间,继承关系可有可无,因为就算是子类,但是只要有相应的方法,最终还是会按照子类的方法执行,父类的方法不用考虑
    3. 多态还可以增加代码的外部调用灵活度,让代码更加通用,兼容性更强
    4. 多态是调用方法的技巧,不会影响类的内部设计
  • 对于多态的个人理解:

    其实就是在把类作为参数传入函数时,无需考虑是什么类(不管是子类还是父类),只要说传入类的方法和函数内调用的方法名称是相同的就行,例如:

    这里定义一个run_test函数
    >>> def run_test(aaaa):
    ...     aaaa.run()
    ... 
        
        
    根据上面的函数内容,我们在传入类时,其实只要保证类中有'run'的方法就行,就跟上面的案例一样,在传入'Dog''Animal'后,输出的内容都是他们自己'run'方法的操作步骤
    >>> class Animal(object):
    ...     def run(self):      
    ...             return print('Animal is running!!')
    ... 
    >>> class Dog(Animal):
    ...     def run(self):
    ...             return print('Dog is running !!!') 
    ... 
    >>> run_test(Dog()) 
    Dog is running !!!
    >>> run_test(Animal()) 
    Animal is running!!
    
    
    但是比如传入的'Dog'类中并没有新定义'run'方法,那么他会从'Animal'类中继承'run'方法,那么输出的就是'Animal''run'方法了,
    >>> class Dog(Animal):
    ...     pass
    ... 
    >>> run_test(Dog()) 
    Animal is running!!
    
    
    如果父类'Animal'也没有'run'方法的话,就会报错
    >>> class Animal(object):
    ...     pass
    ... 
    >>> run_test(Dog())         #在重新定义Animal后,先传入Dog发现输出还是原来的,需要重新定义Dog
    Animal is running!!
    >>> class Dog(Animal):
    ...     pass
    ...         
    >>> run_test(Dog()) 
    Traceback (most recent call last):
      File "", line 1, in <module>
      File "", line 2, in run_test
    AttributeError: 'Dog' object has no attribute 'run'
    
  • 扩展:

    • 根据上面调用函数传入类来说,静态语言和动态语言是不一样的
    • 对于静态语音例如JAVA来说,如果需要传入的类是Animal,那么传入的参数必须是Animal类或者它的子类,否则就算传入的类有run方法,也是无法调用的
    • 但是对于动态语言来说,只要保证传入的类中有run方法即可,这就是动态语言的鸭子类型

六、获取对象信息

  • 当我们拿到一个对象的引用时,怎么能够知道这个对象的相关信息,如是什么类型、有什么方法呢,下面来看几个函数,这些函数可以帮助我们获取对象信息

-Type()

  • 使用type()函数可以判断对象类型,下面直接来看案例:

    >>> type(123) 
    <class 'int'>
    >>> type('123') 
    <class 'str'>
    >>> type(None)   
    <class 'NoneType'>
    
    - 当一个变量指向函数或者类,也可以使用type判断
    >>> class Animal(object): 
    ...     pass
    ... 
    >>> type(abs)    #判断abs函数
    <class 'builtin_function_or_method'>
    >>> a = Animal()
    >>> type(a)    #判断a实例
    <class '__main__.Animal'>
    
    - 可以看到type返回的信息中,都是以'class'开头的,其实可以看出返回的都是'对象'对应的'类',可以使用'if'语句进行判断
    >>> type(123) == type(345) 
    True
    >>> type(123) == int      
    True
    >>> type('123') == str     
    True
    >>> type('123') == type(123) 
    False
    
    - 从上面可以看出,判断基本的数据类型可以使用'int''str'等,但是如果想要判断一个对象是否是函数呢?可以使用'Types'模块中定义的常量进行判断
    >>> import types
    >>> def fn():   #定义一个函数
    ...     pass
    ... 
    >>> type(fn) == types.FunctionType
    True
    >>> type(abs) == types.BuiltinFunctionType 
    True
    >>> type(lambda x :x ) == types.LambdaType 
    True
    >>> type((x for x in range(10))) == types.GeneratorType
    True
    

-isinstance()

  • 对于类的继承关系来说,使用Type()就很不方便,我们想要判断类的类型,可以使用isinstance()函数

    - 现在来创建几个类
    >>> class Animal(object):
    ...     def run(self):
    ...             return print('Animal is running!!!') 
    ... 
    >>> class Dog(Animal):
    ...     def run(self):
    ...             return print('Dog is running!!!') 
    ... 
    现在的继承关系为:object——>Animal——>Dog
    
    -现在使用'isinstance()'去判断
    >>> a = Animal()
    >>> b = Dog()
    >>> isinstance(a,Animal) 
    True
    >>> isinstance(b,Dog)    
    True
    >>> isinstance(b,Animal) 
    True
    >>> isinstance(b,object) 
    True
    >>> isinstance(a,Dog)    
    False
    可以看到Dog因为是子类,所以既是'Animal''Dog'也是'object',和上面的一样,子类可以当作父类,而父类不能当作子类
    
  • 一般来说可以使用type()判断的类型,也可以使用isinstance()进行判断

    >>> isinstance('a',str) 
    True
    >>> isinstance(123,int) 
    True
    >>> isinstance(b'a',bytes) 
    True
    
  • 还可以判断一个变量是否是某种类型的一种,其实就是在后面多加几中数据类型,判断是否在多种数据类型之中,例如:

    >>> isinstance([1,2,3],(list,tuple)) 
    True
    >>> isinstance((1,2,3),(list,tuple)) 
    True
    >>> isinstance(1,(str,int))          
    True
    >>> isinstance('1',(str,int)) 
    True
    

推荐直接使用isinstance()进行判断,可以将指定类型以及子类一次性进行判断

-dir()

  • 如果想要获取指定对象的所有属性和方法,可以使用dir()函数,dir()函数会返回一个包含字符串的list,例如:

    >>> dir("abc") 
    ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'removeprefix', 'removesuffix', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
    >>> dir(123)   
    ['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
    
  • 上面说过了,类似于__xx__这种的属性和方法在Python中都是有特殊用途的,例如:

    - 在Python调用'len()'函数,实际上,在'len()'函数的内部,会自动去调用'__len__()'方法,所以下面的作用是相同的
    >>> len("aaa") 
    3
    >>> "aaa".__len__()
    3
    
  • 在创建类时,也可以加__len__()方法:

    >>> class Test(object):
    ...     def __len__(self):
    ...             return 100
    ... 
    >>> aaa = Test()
    >>> len(aaa) 
    100
    >>> class Test(object):
    ...     def __init__(self,name):
    ...             self.name = name
    ...     def __len__(self):
    ...             return len(self.name) 
    ... 
    >>> aaa = Test('aaaaaa') 
    >>> aaa.__len__()
    6
    
  • 剩下的都是普通属性和方法,例如:

    - lower()返回小写的字符串
    >>> 'AAAAA'.lower()
    'aaaaa'
    
  • 配合getattr()setattr()hasattr()可以直接操作一个对象的状态:

    #注释:
    getattr(实例名称,"属性"):获取指定'实例''属性'
    setattr(实例名称,"属性","设置属性的值"):设置指定'实例''属性'    
    hasattr(实例名称,"属性"):判断'实例'是否有指定的'属性'
    
    >>> class Test(object):
    ...     def __init__(self,name):
    ...             self.name = name 
    ...     def cj(self):
    ...             return self.name * self.name
    ... 
    >>> aaa = Test(9)  
    >>> hasattr(aaa,'name')   
    True
    >>> aaa.name
    9
    >>> hasattr(aaa,'sorce')  
    False
    >>> setattr(aaa,'sorce',20) 
    >>> hasattr(aaa,'sorce')    
    True
    >>> aaa.sorce
    20
    >>> getattr(aaa,'sorce') 
    20
    >>> getattr(aaa,'acx')      #没有则会报错
    Traceback (most recent call last):
      File "", line 1, in <module>
    AttributeError: 'Test' object has no attribute 'acx'
    >>> getattr(aaa,'acx',123)   #如果acx属性不存在,则返回默认值123
    123
    
  • 除了判断实例的属性,还可以判断实例的方法,这个方法是对象里的方法

    >>> hasattr(aaa,'cj')      
    True
    >>> getattr(aaa,'cj')   #可以看到判断是一个函数
    <function Test.cj at 0x000001B6A277ECB0>
    >>> bbb = getattr(aaa,'cj') 
    >>> bbb
    <bound method Test.cj of <__main__.Test object at 0x000001B6A2756D40>>
    >>> bbb()
    81
    
  • 小结:

    • 通过上面所说的一系列内置函数,我们可以对任意一个Python对象进行解析,需要注意的是,只有在不知道对象信息的时候,我们才会去获取对象信息,例如:
    可以写:
    sum = obj.x + obj.y
    就不要这样写:
    sum = getattr(obj, 'x') + getattr(obj, 'y')
    
    • 一般来说可以利用这些内置函数做if判断,判断一个对象丽是否存在指定的方法
    def readImage(fp):
        if hasattr(fp, 'read'):
            return readData(fp)
        return None
    

七、实例属性和类属性

  • 由于Python是动态语言,所以根据类创建的实例可以任意绑定属性,而给实例绑定属性的方法就是通过创建实例时设置的变量,即实例变量(实例属性),或者通过self变量

    >>> class Student(object):         
    ...     def __init__(self,name):
    ...             self.name = name
    ... 
    >>> test = Student('zhangsan')   #这种在创建实例时设置或者使用test.score创建的属性都叫实例属性,和类属性不互通
    >>> test.score = 98
    >>> test.score     
    98
    >>> test.name 
    'zhangsan'
    
  • 如果Student类本身就需要绑定一个属性的话,可以直接在class中定义属性,而这种属性叫做类属性,归Student类所有:

    >>> class Student(object):
    ...     name = 'zhangsan' 
    ... 
    >>> test = Student()
    >>> test.name     #可以看到,test实例是没有设置name属性的,但是输出的数据是类中的name属性的值,这是因为当实例没有该属性时,会继续查找类中的相应属性的值
    'zhangsan'
    >>> Student.name
    'zhangsan'
    >>> test.name = 'lisi'   #给test实例的name属性赋值
    >>> test.name          
    'lisi'
    >>> Student.name           
    'zhangsan'
    >>> del test.name  #删除test实例的name属性的值
    >>> test.name  #发现又变成了类的name属性
    'zhangsan'
    
  • 从上面可以看出,当实例没有指定的属性时,python会寻找类的属性,所以说,在编写代码时,不要对实例属性和类属性设置相同的名称,因为相同名称的情况下,实例属性会覆盖掉类属性,当删除实例属性后,再次使用相同的名称进行访问,将会重新访问到类属性

你可能感兴趣的:(Python系列,python,开发语言,面对对象编程,类的特性,获取对象信息)