Python3 学习笔记(面向对象编程)
面向对象编程
面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
类和实例
面向对象最重要的概念就是类(Class)和实例(Instance),类是抽象的模板,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
在Python中,定义类是通过class
关键字,后面紧接着是类名,创建实例是通过类名+()实现的,通过定义一个特殊的__init__
方法,可以在创建实例的时候把认为必须绑定的属性填写进去。
__init__
方法的第一个参数永远是self
,表示创建的实例本身,因此,在__init__
方法内部,就可以把各种属性绑定到self
,因为self
就指向创建的实例本身。
普通的函数相比,在类中定义的函数第一个参数永远是实例变量self
,并且,调用时不用传递该参数。除此之外,类的方法和普通函数没有什么区别,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。
在类中每个实例拥有各自的数据,要访问这些数据可以直接在类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和类本身是关联起来的,称之为类的方法。
访问限制
在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样就隐藏了内部的复杂逻辑。但是外部代码还是可以自由地修改一个实例的属性,如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__
,在Python中,实例的变量名如果以__
开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,通过访问限制的保护使得代码更加健壮,如果需要外部访问内部属性,可以通过增加类的方法实现。
|
|
继承和多态
在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
继承的好处是子类可以获得了父类的全部功能,当子类和父类存在相同的方法时,在代码运行的时候,总是会调用子类的方法,这是继承的另一个好处:多态。
获取对象信息
使用type()
函数来判断对象类型,使用isinstance()
函数来判断class的类型,能用type()
判断的基本类型也可以用isinstance()
判断。
如果要获得一个对象的所有属性和方法,可以使用dir()
函数,它返回一个包含字符串的list。
实例属性和类属性
由于Python是动态语言,根据类创建的实例可以任意绑定属性。给实例绑定属性的方法是通过实例变量,或者通过self
变量。如果类本身需要绑定一个属性,可以直接在class中定义属性,这种属性是类属性,但类的所有实例都可以访问到。
面向对象高级编程
**使用__slots__**
动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现,可以通过一个特殊的__slots__
变量,来限制该class实例添加的属性,但对继承的子类不起作用。
使用@property
Python内置的@property
装饰器,可以把一个getter
方法变成属性,同时又创建了另一个装饰器@score.setter
,负责把一个setter
方法变成属性赋值,只定义getter方法,不定义setter方法就是一个只读属性。
|
|
上面的birth
是可读写属性,而age
就是一个只读属性,因为age
可以根据birth
和当前时间计算出来。
多重继承
通过多重继承,一个子类就可以同时获得多个父类的所有功能。在设计类的继承关系时,通常主线都是单一继承下来的,但是如果需要“混入”额外的功能,通过多重继承就可以实现,这种设计通常称之为MixIn。
MixIn的目的是给一个类增加多个功能,这样在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
定制类
__str__()
、__repr__()
方法:返回一个好看的字符串。
|
|
__iter__()
方法:返回一个迭代对象。
|
|
__getitem__()
、__setitem__()
、__delitem__()
方法:把对象视作list或dict来获取某个元素值、对某个元素合赋值及删除某个元素。
__getattr__()
方法:动态返回一个属性。__call__()
方法:直接对实例进行调用。通过callable()
函数,可以判断一个对象是否是“可调用”对象。
|
|
每次调用类Chain()
都会运行里面的__init__()
函数进行初始化里面的相应属性,在这里边相当于重建实例覆盖之前的实例,具体来看:
Chain()
:初始化一个实例Chain().users
:由于没有给实例传入初始化对应属性的具体信息,从而自动调用__getattr__()
函数,从而有Chain().users = Chain('/users')
,这是重建实例Chain().users('michael') = Chain('/users')('michael')
:这是对实例直接调用,相当于调用普通函数,返回的是Chain('/users/michael')
,再一次重建实例,覆盖掉Chain('/users')
Chain().users('michael').repos
:是查询实例的属性repos
,由于没有这一属性就会执行__getattr__()
函数,再一次返回新的实例Chain('/users/michael/repos')
并且覆盖点之前的实例,并且一但定义了一个新的实例,就会执行__init__
方法print(Chain().users('michael').repos)
:由于我们定义了__str__()
方法,那么打印的时候就会调用此方法,打印的是path
属性,即/users/michael/repos
使用枚举类
Enum
类可以把一组相关常量定义在一个class中,且class不可变,而且成员可以直接比较。例如,定义一个Month
类型的枚举类,可以直接使用Month.Jan
来引用一个常量,或者枚举它的所有成员:
|
|
value
属性则是自动赋给成员的int
常量,默认从1开始计数。
如果需要更精确地控制枚举类型,可以从Enum
派生出自定义类:
|
|
@unique
装饰器可以帮助我们检查保证没有重复值。
使用元类
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。type()
函数可以查看一个类或变量的类型,还可以创建一个class对象。
除了使用type()
动态创建类以外,还可以使用metaclass,直译为元类,简单的解释就是:当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例,如果要想创建出类就必须根据metaclass创建,所以:先定义metaclass,然后创建类,也就是:先定义metaclass,就可以创建类,最后创建实例。
错误、调试
错误处理
高级语言通常都内置了一套try...except...finally...
的错误处理机制,使用try...except
捕获错误的一个巨大的好处就是可以跨越多层调用,不需要在每个可能出错的地方去捕获错误,只要在合适的层次去捕获错误就可以了。
调试
第一种方法简单直接粗暴有效,就是用print()
把可能有问题的变量打印出来;第二种方法是用断言(assert)来替代print()
,可以用-O
参数来关闭assert
;第三种方式把print()
替换为logging
,和assert
比logging
不会抛出错误,而且可以输出到文件,还能指定记录信息的级别,有debug
,info
,warning
,error
等几个级别,也不用删除,最后统一控制输出哪个级别的信息;第四种方式是启动Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态,此外还可以import pdb
,然后在可能出错的地方设置一个pdb.set_trace()
断点,使用命令p
查看变量,或者用命令c
继续运行。
- 原文作者:百年孤独
- 原文链接:https://qoanty.github.io/2019/04/python3-object-oriented-programming/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。