自定义对象是 Python 的核心概念。
对象 在面向对象中,对象意味着一系列数据(属性)以及一套访问和操作这些数据的方法。 使用对象的好处:
多态:对不同类型的对象执行相同的操作
封装:对外部隐藏有关对象工作原理的细节
继承:可基于通用类创建出专用类
多态 多态指的是即使不知道变量指向的是哪种对象(字符串、整型、列表、字典)也能够对其执行操作,且操作的行为将随对象所属的类型而异。
多态和方法 与对象属性相关联的函数称为方法。 每当无需知道对象是什么样类型的都能对其执行操作,都是多态在起作用。 内置运算符和大量的内置类的方法都使用了多态,例如:
1 2 3 4 5 6 7 8 1 + 2 3 'Hello' + 'World' 'HelloWorld' 'Hello' .count('o' )1 [1 ,2 ,3 ,1 ].count(1 ) 2
封装 封装是指向外部隐藏不必要的细节,例如方法或变量。 当调用对象中的方法时不需要操心其它事情,如避免干扰全局变量,通过属性将名称封装在对象中。 属性是归属于对象的变量,就像方法一样。
继承 如果已经有了一个类,并要创建一个与之类似的类,可能是在此基础上增加了几个方法,此时可以继承已有的类,在新创建的类中可以直接调用被继承类中的属性,如变量或方法。
类 每个对象都属于特定的类,并称为该类的实例。本质上类也是一种对象。
创建自定义类 自定义类的示例:
1 2 3 4 5 6 7 class Dog : def set_name (self, name ): self .name = name def get_name (self, name ): return self .name def run (self ): print ("Dog " , self .name, "running" )
class 语句会创建独立的命名空间,用于在其中定义函数。self 是指向对象本身:
1 2 3 4 5 6 7 8 d1 = Dog() d1.set_name('大黄' ) d1.run() Dog 大黄 running d2 = Dog() d2.set_name('大白' ) d2.run() Dog 大白 running
如果没有传递 self 所有的方法都无法访问对象本身(也就是所有要操作的属性所属的对象)。 也可以从外部访问这些属性:
1 2 3 4 d1.name '大黄' d2.name '大白'
属性、函数和方法 方法和函数的区别表现在是否具有 self 参数上。方法将第一个参数关联到它所属的实例,所需无需提供这个参数,属性也可关联到函数,但是就没有特殊的 self 参数了。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Demo : def method (self ): print ('I have a self' ) def method (): print ('I not have self' ) instance = Demo() instance.method() I have a self instance.method = method instance.method() I not have self
事实上,可以将变量指向一个方法:
1 2 3 instance2 = d1.run instance2() Dog 大黄 running
当调用该方法时,它也可以访问 self 参数。
隐藏 Python 没有为私有属性提供直接的支持,意味着能够直接从外部访问对象内部的属性。 要使方法或属性称为私有,只需让名称以两个下划线开头即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Dog : def __get_name (self ): return '大黄' def run (self ): print ('Dog' , self .__get_name(), 'run' ) d1 = Dog() d1.__get_name() Traceback (most recent call last): File "<pyshell#63>" , line 1 , in <module> d1.__get_name() AttributeError: 'Dog' object has no attribute '__get_name' . Did you mean: '_Dog__get_name' ? d1.run() Dog 大黄 run
当从外部直接调用方法时,出现报错,但是内部可以根据名称进行调用。 私有是不可能存在的,其实错误提示中已经告知了方法,使用 _Dog__get_name 进行访问,也就是在方法名称前面加上 _类名 即可:
1 2 d1._Dog__get_name() '大黄'
针对变量的隐藏也是如此:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Student : __name = 'Bob' def hi (self ): print ('hi' , Student.__name) s1 = Student() s1.__name Traceback (most recent call last): File "<pyshell#78>" , line 1 , in <module> s1.__name AttributeError: 'Student' object has no attribute '__name' s1._Student__name 'Bob' s1.hi() hi Bob
如果不希望名称被修改,又向发出不要从外部修改属性或方法的信号,可以用一个下划线打头,这是一种约定俗成的操作,例如 from module import * 不会导入以一个下划线开头的名称。
类的命名空间 在 class 语句中定义的代码都是在一个特殊的命名空间(类的命名空间)内执行的,类的所有成员都可以访问这个命名空间。class 中不仅仅存在 def 语句,还可以存在变量等语句,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 class Num : i = 0 def init (self ): Num.i += 1 n1 = Num() n1.init() n1.i 1 n2 = Num() n2.init() n2.i 2
可以看到每个实例都能访问类中的对象,i 被初始化两次后,值变成了 2,类似于全局变量和局部变量的遮盖问题。 如果直接对属性进行修改,就会遮住类级的变量:
1 2 3 4 5 n1.i = 'Bob' n1.i 'Bob' n2.i 2
如果是定义成以下方式就可以避免:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Num : def set_i (self, n ): self .i = n def init (self ): self .i += 1 n1 = Num() n1.set_i(100 ) n1.init() n1.i 101 n2 = Num() n2.set_i(1000 ) n2.init() n2.i 1001 n1.i 101
指定超类 子类扩展了超类的定义,要指定超类,需要在 class 语句中的类名称后面加上超类名,并用圆括号括起:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Filter : def init (self ): self .blocked = [] def filter (self, sequence ): return [x for x in sequence if x not in self .blocked] class SPAMFilter (Filter ): def init (self ): self .blocked = ['SPAM' ] s = SPAMFilter() s.init() s.filter (['SPAM' , 'hi' ,1 ,'SPAM' ,'123' ]) ['hi' , 1 , '123' ]
可以看到 SPAMFilter 集成了 Filter , Filter 是 SPAMFilter 的超类,在 SPAMFilter 中改写了 init 方法,没有修改 filter 方法,但是实例中可以直接调用。
验证集成的关系 使用内置方法 issubclass 可以确定一个类是否是另一个类的子类:
1 2 3 4 issubclass (SPAMFilter, Filter)True issubclass (Filter, SPAMFilter)False
如果知道子类,想要知道其基类,可以使用类的特殊属性 __bases__:
1 2 3 4 SPAMFilter.__bases__ (<class '__main__.Filter' >,) Filter.__bases__ (<class 'object' >,)
要确定实例是否是特定对象的实例,可以使用 isinstance 内置方法:
1 2 3 4 5 6 7 8 s = SPAMFilter() isinstance (s, SPAMFilter)True f = Filter() isinstance (f, SPAMFilter)False isinstance (f, Filter)True
也可以使用实例的 __class__ 属性:
1 2 3 4 f.__class__ <class '__main__.Filter' > s.__class__ <class '__main__.SPAMFilter' >
多个超类 基类可以有多个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Info : def set_info (self, name ): self .name = name class Print : def print_info (self ): print ('Hi, my name is ' , self .name) class PrintInfo (Info, Print): pass pp = PrintInfo() pp.set_info('imxcai' ) pp.print_info() Hi, my name is imxcai
除非万不得已,应避免使用多重继承。 同时在 class 语句中,超类的位置很重要,位于前面的类中的方法将覆盖位于后面的类的方法。如果 Info 类中存在 print方法,就会覆盖 Print 中的 print 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Info : def set_info (self, name ): self .name = name def print (self ): print ('hi from Info class' ) class Print : def print_info (self ): print ('Hi, my name is ' , self .name) class PrintInfo (Info, Print): pass pp = PrintInfo() pp.set_info('imxcai' ) pp.print () hi from Info class
接口和内省 检查实例是否具有特定的方法可以通过内置方法 hasatter:
1 2 3 4 hasattr (pp, 'print' )True hasattr (pp, 'delete' )False
也可以通过 callable 来检查是否能够被调用。
抽象基类 Python 通过引入 abc 模块来实现接口的理念。 抽象类是不能实例化的类,其职责是定义子类应实现的一组抽象方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from abc import ABC, abstractmethodclass Brid (ABC ): @abstractmethod def fly (self ): pass Brid() Traceback (most recent call last): File "<pyshell#221>" , line 1 , in <module> Brid() TypeError: Can't instantiate abstract class Brid without an implementation for abstract method ' fly' class BlueBrid(Brid): def fly(self): print(' fly') BlueBrid() <__main__.BlueBrid object at 0x7f4d19c78c80>
在 Brid 类中定义了抽象基类,那么就无法进行实例化,必须使用子类实现了抽象基类才能实例化。
只要实现了 fly 方法,即使不是 Brid 的子类,依然可以通过类型检查:
1 2 3 4 5 6 7 8 9 10 11 12 13 class Bee : def fly (self ): print ('fly..' ) b = Bee() isinstance (b, Brid)False Brid.register(Bee) <class '__main__.Bee' > isinstance (b, Brid)True issubclass (Bee, Brid)True
使用类中的 register 属性对非子类的类进行注册,这样就能通过类型检查,可以看到子类检查也通过了,但是无法使用超类中的属性。
面向对象设计的一些思考 设计对象时:
将相关东西放在一起
方法应只关心其所属实例的属性
慎用继承
保持简单
确定需要哪些类,以及类中应包含哪些方法,可以按照以下步骤筛选:
将问题描述记录,标记名词、动词和形容词
在名词中找出可能的类
在动词中找出可能的方法
在形容词中找出可能的属性
然后将找出的方法、属性分配给各个类
有了面对对象的模型之后,还需考虑类和对象之间的关系,加以改进。
评论· · · · · ·