python基础学习(四)---面向对象编程

  • 面向对象编程
    • 数据封装
    • 继承和多态
    • 动态语言特性
    • property
    • 多重继承
    • 定制类
    • 枚举类
    • 元类

面向对象编程

面向对象编程的三个基础概念是数据封装(类)、继承和多态,在这三个记住概念的基础上,进一步扩展出更加高级的多重继承、定制类和元类等概念,这些概念和相关的功能使得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

在类中为了代码安全性的要求,有些属性和方法不应该被外部直接获取到,需要对这些属性或方法设置访问限制,这一点和其他面向对象编程语言是一样的,即属性和方法有privatepublic的区分。在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'

@property

在自定义类时,如果我们想给一个属性赋值,可以直接调用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

多重继承

继承可以使子类直接获得父类的所有功能,并且在此基础上扩展父类的功能。但是正如类的概念来自真实世界中的对象,每个对象都可以有不同的分类,比如catdogbatparrot,既可以按照能飞不能飞来分类,也可以按照哺乳动物和非哺乳动物来分类,如果按照简单的单一继承关系,我们需要很复杂的层次关系来实现这种逻辑,所以可以使用多重继承,来让一个子类获得其所有相关父类的继承关系,这样在我们设计类时,只需要按照大类设计所有的基本的类,随后让子类直接多重继承其所有所归属的大类。为了

#!/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个参数:

  1. class的名称
  2. 继承的父类集合,注意如果要继承多个父类,要使用tuple的写法,如果是继承单个父类,要注意单个tuple的写法(object,)
  3. class的方法名称和已有的函数的绑定,使用dict的写法,左侧是要绑定的类的方法名称,右侧是已有的函数名称

你可能感兴趣的:(Python)