admin管理员组

文章数量:1794759

Python(8)面向对象编程

Python(8)面向对象编程

文章目录
    • 一、什么是面向对象编程
    • 二、类(class)和实例(instance)
    • 三、特性之一——数据封装
    • 四、访问限制
    • 五、特性之二、三——继承和多态
      • -继承
      • -多态
    • 六、获取对象信
      • -Type()
      • -isinstance()
      • -dir()
    • 七、实例属性和类属性
此文参考廖雪峰官网:面向对象编程 - 廖雪峰的官方网站 (liaoxuefeng)

一、什么是面向对象编程
  • 面向对象编程(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)则指一个个具体的对象,例如上面的zhangsan和lisi就是两个具体的Student,也就是实例
  • 从上面的案例可以看出,面向对象的程序设计思想其实就是抽象出对象(class),然后根据对象创建实例(instance)
  • 最后,面向对象的抽象程度比函数高,因为一个对象既包含数据,也包含操作数据的方法,数据封装、继承、多态是面向对象的三大特点
二、类(class)和实例(instance)
  • 面对对象最重要的概念就是类(class)和实例(instance),类是抽象的模板,比如上面的Student类,而实例是根据类创建出来的具体的对象,每个对象都有相同的方法,但是各自的数据可能不同,例如上面的zhangsan和lisi
  • 以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__方法,在创建实例的时候,把name和score等属性绑定,例如:

>>> 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 "<stdin>", 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 "<stdin>", 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类中,根据对象创建的实例里,都有各自的name和score的数据,可以通过函数来访问这些数据,例如:

    >>> 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类创建实例时,只需要指定name和score的值即可,关于如何打印出来,这些都是在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
  • 总结:

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

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

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

  • 和静态语音不同,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 "<stdin>", 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'方法添加了参数控制
  • 注意:

  • 在Python中,变量名称类似于__xx__这样的以双下划线开头和结尾的是特殊变量,特殊变量是可以直接访问的,并不是私有变量
  • 以一个下划线开头的,类似于_name这样的变量,是可以被外部访问的,但是在看到这样的变量时,要把它当成私有变量,不要随意访问,这也是一个不成文的规定
  • 根据拥有私有变量的类去创建的实例,在调用时,不要直接使用类似于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!!!
  • 对于上面的案例来说,Dog和Cat就是Animal的子类,Animal是Dog和Cat的父类

  • 可以看到在Dog和Cat继承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方法的输出,这是因为:当子类和父类同时存在相同的方法时,在调用子类的时候,子类的方法会覆盖掉父类的方法。

    例如:Dog是Animal的子类,并且Dog和Animal类中都有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'函数时作为参数直接传入,并且可以正常运行,输出的数据都不一样,这就是因为'多态'的特性
  • 看过上面的案例后,其实可以看出多态的作用就是:让具有不同功能的函数可以使用相同的函数名称,从而可以使用一个函数名调用不同功能的函数

  • 多态的特点:

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

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

    这里定义一个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 "<stdin>", line 1, in <module> File "<stdin>", 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 "<stdin>", 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