面向对象编程的三个基础概念是数据封装(类)、继承和多态,在这三个记住概念的基础上,进一步扩展出更加高级的多重继承、定制类和元类等概念,这些概念和相关的功能使得python称为一种强大的面向对象的高级动态语言。
面向对象编程中最重要的概念就是类和实例,类是对象的抽象的概念,而实例是对象的具象的结果。python中通过class关键字定义一个类,
class person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def show(self):
print("name: %s, age: %s" % (self.name, self.age))
laowang = person("laowang", 20)
laowang .show()
___________
输出:
name: laowang , age: 20
在类中为了代码安全性的要求,有些属性和方法不应该被外部直接获取到,需要对这些属性或方法设置访问限制,这一点和其他面向对象编程语言是一样的,即属性和方法有private
和public
的区分。在python不使用这两个关键字来设置访问权限,而是直接在属性名或函数名之前添加两个下划线__
来表示这个属性是私有的属性,不允许外部直接修改,如果需要修改该属性,可以增加一个专门的函数用来修改该属性。
class person(object):
def __init__(self, name, age):
self.__name = name
self.__age = age
def show(self):
print("name: %s, age: %s" % (self.__name, self.__age))
def set_age(self, age):
self.__age = age
qq = person("laowang", 20)
qq.show()
qq.set_age(90)
qq.show()
____________
输出:
name: laowang, age: 20
name: laowang, age: 90
设置的访问限制的属性无法通过类似qq.__age
的形式直接访问,因为在解释器中它的名称已经被改变为qq._person__age
,通过这一变量可以访问到该属性,但是并不建议这么做,因为不同版本的解释器的修改方法可能不一致,这会导致代码的不兼容,同时这么做也是不安全的。
如果在设置的访问限制之后,再直接使用__age
强制的修改属性的内容,此时其实是新建了一个类的属性,原先类中的__age
其实并未改变。
class person(object):
def __init__(self, name, age):
self.__name = name
self.__age = age
def show(self):
print("name: %s, age: %s" % (self.__name, self.__age))
def set_age(self, age):
self.__age = age
qq = person("laowang", 20)
qq.show()
qq.__age = 50
qq.__age
qq.show()
qq.set_age(90)
qq.show()
————————————————————
输出:
name: laowang, age: 20
name: laowang, age: 20
name: laowang, age: 90
当我们新建一个类时,可以从一个现有的类上继承,这个新建的类被称为子类,这个被继承的类被称为子类的基类、父类或超类。
继承可以获得父类的全部功能,包括属性和方法,同时可以在此基础上进行添加新的属性和方法,或者重写原有的方法。同时,由于继承存在层层传递的关系,子类的实例,也是父类的实例,也是父类的父类的实例,以此类推,直到最上层的那个类,这也是为什么子类可以使用父类以及父类的父类中所定义的属性和方法,这也是由继承所导致的多态性,一个类可以具有包括其自身及所有父类的。
同时在使用子类,一定是先使用子类所定义的方法和属性,如果没有,才会去依次向上追溯寻找可用的属性或方法,如果直到最终的根类object
都没有,才会爆错。
#!/usr/bin/python
# -*- encoding:utf-8 -*-
class person(object):
def __init__(self, name, age):
self.__name = name
self.__age = age
def show(self):
print("name: %s, age: %s" % (self.__name, self.__age))
def set_age(self, age):
self.__age = age
class doctor(person):
def set_major(self, major):
self.__major = major
def show_major(self):
print("major : %s" % self.__major)
laowang = doctor("laowang", 20)
laowang.show()
laowang.set_major("Pediatrics")
laowang.show_major()
print(isinstance(laowang, doctor))
print(isinstance(laowang, person))
_________________________
输出:
name: laowang, age: 20
major : Pediatrics
True
True
多态的好处在于,如果一个函数的输入可以是多种子类,但如果这些子类具有一个公共的父类,那么就可以在定义是时将输入的参数定义为该父类,在使用时可以输入其各自子类,而不用关注它具体的类型(注意在函数内不应使用只有某一个子类才具有的属性或函数),在他具体执行的时候,才会根据它具体的类型,执行对应的函数。
#!/usr/bin/python
# -*- encoding:utf-8 -*-
class person(object):
def show(self):
print("this is person speaking")
class doctor(person):
def show(self):
print("this is doctor speaking")
def show(person):
person.show()
show(person())
show(doctor())
——————————————————————————
输出:
this is person speaking
this is doctor speaking
对于上述情况,甚至我们不需要严格要求输入的类是定义时要求的类的子类,只需要传入的类具有show
方法即可,这和JAVA
这种静态语言有很大不同,JAVA
要求定义时如果是要求传入一个类,那么在使用时比如传入该种类或其子类,而python作为一门动态语言可以使用上述这种被称为鸭子模型。比如,很多对象只要有read
方法,都可以被视为file-like object
,而很多函数的传入参数就是file-like object
类型,此时你可以不必传入文件对象,而是直接传入实现了read
方法的对象即可。
作为动态语言,python具有另一个特性,就是可以直接给实例绑定新特性,新特性如果和类已有的特性重名,由于实例特性的优先级较高,在直接访问时会覆盖掉类的特性,但是类的特性依然存在。所以,在给实例添加新特性时,最好不要使用和已有属性相同的名字,不然会导致调用的混乱。
>>> class Chinese(object):
... country = "China"
...
>>> laowang = Chinese()
>>> print(laowang.country)
China
>>> laowang.country = 'PRC' #修改实例的country特性
>>> print(laowang.country) #实例的country被修改
PRC
>>> print(Chinese.country) #但是类的country值没有被修改
China
>>> del laowang.country #删除实例的country特性
>>> print(laowang.country) #类的country特性仍保留
China
>>> laowang.hometown = "taiwan" #添加新的实例特性
>>> print(laowang.hometown) #新的实例特性可以正常使用
taiwan
同样的,我们可以给一个实例绑定一个新的方法,这里需要使用types
模块中的MethodType
方法,
>>> def set_sex(self, sex):
... self.sex = sex
...
>>> from types import MethodType
>>> laowang.set_sex = MethodType(set_sex, laowang)
>>> laowang.set_sex("male")
>>> laowang.sex
'male'
如果想给所有的实例都绑定新方法,则需要直接给类绑定新方法
>>> Chinese.set_sex = set_sex
但是如果这样随意给实例或类添加新的特性,代码的安全性可能得不到保证,所以python提供了__slots__
变量,在定义函数的时候通过它来限定实例能添加的属性。
>>> class Chinese(object):
... __slots__ = ('country', 'hometown')
...
>>> laowang = Chinese()
>>> laowang.country = 'China'
>>> laowang.hometown = 'taiwan'
>>> laowang.sex = 'male'
Traceback (most recent call last):
File "" , line 1, in
AttributeError: 'Chinese' object has no attribute 'sex'
Error in sys.excepthook:
..........
Original exception was:
Traceback (most recent call last):
File "" , line 1, in
AttributeError: 'Chinese' object has no attribute 'sex'
>>>
同时,该限制只在该类中起作用,在其子类中并不起作用,子类但如果子类也使用了__slots__
变量,那么就将接收父类__slots__
的限制,此时子类实例可以添加的属性就是子类和父类__slots__
指定内容的并集。
>>> class taiwanren(Chinese):
... pass
...
>>> jojo = taiwanren()
>>> jojo.sex = 'female'
>>> class taiwanren(Chinese):
... __slots__ = ()
...
>>> jj = taiwanren()
>>> jj.sex = 'male'
Traceback (most recent call last):
File "" , line 1, in
AttributeError: 'taiwanren' object has no attribute 'sex'
Error in sys.excepthook:
Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/apport_python_hook.py", line 53, in apport_excepthook
if not enabled():
File "/usr/lib/python3/dist-packages/apport_python_hook.py", line 28, in enabled
return re.search('^\s*enabled\s*=\s*0\s*$', conf, re.M) is None
AttributeError: module 're' has no attribute 'search'
Original exception was:
Traceback (most recent call last):
File "" , line 1, in
AttributeError: 'taiwanren' object has no attribute 'sex'
在自定义类时,如果我们想给一个属性赋值,可以直接调用class.age = 20
,但这样会直接把age
属性暴露给外部,而且如果想要在赋值的同时对传入的参数进行检查,就必须使用set_age
这样的方法,但是这样的方法使用起来比较麻烦,python提供了一种在直接给属性赋值的同时进行参数检查的方法,即@property
,如下代码所示的age
属性。
#!/usr/bin/python
# -*- encoding:utf-8 -*-
class person(object):
@property
def age(self):
return self.__age
@age.setter
def age(self, age):
if not isinstance(age, int):
raise ValueError('age must be an integer')
if age < 0 or age > 120:
raise ValueError('invalid age, beyond [0, 120]')
self.__age = age
pp = person()
pp.age = 30
print(pp.age)
pp.age = 190
——————————
输出:
30
Traceback (most recent call last):
File "proper.py", line 21, in
pp.age = 190
File "proper.py", line 14, in age
raise ValueError('invalid age, beyond [0, 120]')
ValueError: invalid age, beyond [0, 120]
同时,如果想要给一个属性设置为只读的,那么只要在类中只给它创建getter
的方法,即直接的@property
方法,而不创建setter
的方法,如下所示的sex
属性。
#!/usr/bin/python
# -*- encoding:utf-8 -*-
class person(object):
def __init__(self, sex):
self.__sex = sex
@property
def age(self):
return self.__age
@age.setter
def age(self, age):
if not isinstance(age, int):
raise ValueError('age must be an integer')
if age < 0 or age > 120:
raise ValueError('invalid age, beyond [0, 120]')
self.__age = age
@property
def sex(self):
return self.__sex
pp = person('female')
pp.age = 30
print('person age:%d, sex:%s' % (pp.age, pp.sex))
pp.sex = 'male'
——————————————
输出:
person age:30, sex:female
Traceback (most recent call last):
File "proper.py", line 28, in
pp.sex = 'male'
AttributeError: can't set attribute
继承可以使子类直接获得父类的所有功能,并且在此基础上扩展父类的功能。但是正如类的概念来自真实世界中的对象,每个对象都可以有不同的分类,比如cat
,dog
、bat
和parrot
,既可以按照能飞不能飞来分类,也可以按照哺乳动物和非哺乳动物来分类,如果按照简单的单一继承关系,我们需要很复杂的层次关系来实现这种逻辑,所以可以使用多重继承,来让一个子类获得其所有相关父类的继承关系,这样在我们设计类时,只需要按照大类设计所有的基本的类,随后让子类直接多重继承其所有所归属的大类。为了
#!/usr/bin/python
# -*- coding: utf-8 -*-
class animal(object):
pass
class mammal(animal):
pass
class unmammal(animal):
pass
class runnable(animal):
pass
class flyable(animal):
pass
class dog(mammal, runnable):
print("I am a dog")
在上文中我们看到,使用__slots__
可以限定类可以添加的属性额范围,这一形如__xxxx__
的变量或函数名在定义类时一般也是有特殊的,比如__len()__
方法使得类可以作用于len()
函数,__str()__
函数让类在被print
直接打印时,输出函数制定的字符串,__repr()__
函数的作用和__str__
类似,但是他会打印一些用于调试服务的信息。
在python中,每一组数据结构都被抽象成了一个类,因此自定义的数据可以在这些基本数据结构的基础上继承并使用其提供的丰富的接口,一个典型的应用就是在创建枚举类时。
使用python自带的枚举类型,首先要引入枚举模块,然后就可以直接创建自己的枚举类型,随后可以直接通过week.Mon
直接引用一个常量,或者使用for
循环枚举它所有成员。
>>> from enum import Enum
>>> week = Enum('week', ('Mon', 'Tues', 'Wed', 'Thur', 'Fri', 'Sat', 'Sun'))
>>> week.Tues
2>
>>> week['Mon']
1>
>>> for name,member in week.__members__.items():
... print(name, '-->', member, ',', member.value)
...
('Mon', '-->', 1>, ',', 1)
('Tues', '-->', 2>, ',', 2)
('Wed', '-->', 3>, ',', 3)
('Thur', '-->', 4>, ',', 4)
('Fri', '-->', 5>, ',', 5)
('Sat', '-->', 6>, ',', 6)
('Sun', '-->', 7>, ',', 7)
value是自动分配给成员的int
性常量,默认从1
开始,如果想要自定义更复杂的枚举类型,可以从Enum
派生自定义的类,在类的前面加上@unique
装饰器用于检查成员没有重复。
在上文中我们说到,python是一种动态语言,而动态语言和静态语言最大的不同之处在于,函数和类的定义不是在编译时定义的,而是在运行时动态创建的。type
函数可以查看一个类型或变量的类型,如果输入一个class
,那么他的类型就是type
,如果输入一个类型的实例,即一个变量,那么他的类型就是该class
。
但是type()
还可以在运行时动态的创建class
,而无需在编译之前通过class
来定义。
>>> def foo(self, name='laowang'):
... print('hello, i am %s.' % name)
...
>>> person = type('person', (object,), dict(hello=foo))
>>> lw = person()
>>> lw.hello()
hello, i am laowang.
>>> print(type(person))
'type'>
>>> print(type(lw))
<class '__main__.person'>
注意到,使用type()
创建一个新的class,要依次传入3个参数:
class
的名称tuple
的写法,如果是继承单个父类,要注意单个tuple
的写法(object,)
class
的方法名称和已有的函数的绑定,使用dict
的写法,左侧是要绑定的类的方法名称,右侧是已有的函数名称