Python在设计之初就被设计成支持面向对象的编程语言。实际上Python既能够面向过程,也可以面向对象编程。Python的面向对象比较简单,不像其他的面向对象语言提供了大量的繁杂的特征,它治理与提供简单的,够用的语法和功能。

下面来逐一介绍Python的面向对象特性。

目录

1.命名空间

2.类变量的访问

3.Python的“私有”成员

4.继承与父类方法重写

5.重写父类的构造函数

6. 使用property属性

7.是否自动绑定第一个参数

8.Python的动态性

9.通过type()函数定义类

10. 多态

11.检查类型

12.枚举类:

    > 直接使用Enum列出多个枚举值来创建枚举类

    > 通过继承Enum基类来派生枚举类

>枚举构造器


1.命名空间

Python的类在很大程度上是一个命名空间,当程序在类体中定义变量,方法时,与前面介绍的定义变量,定义函数并没有太大不同。

Python的类就像命名空间。Python程序默认处于全局命名空间,类体则处于类命名空间。Python允许在全局范围内放置可执行代码——当Python执行该程序时,这些代码就会获得执行的机会。类似地,Python同样允许在类范围内放置可执行代码——当Python执行该类定义是,这些代码同样会获得执行的机会。

如下代码

class define_excute():
    #直接在类命名空间放置可执行代码
    print('定义define_excute类')
    for i in range(10):
        if i % 2 == 0:
            print('偶数:', i)
        else:
            print('奇数:', i)
'''
输出:
定义define_excute类
偶数: 0
奇数: 1
偶数: 2
奇数: 3
偶数: 4
奇数: 5
偶数: 6
奇数: 7
偶数: 8
奇数: 9
'''

2.类变量的访问

Python完全允许使用对象来访问该对象所属类的类变量(当然还是推荐使用类访问类变量)需要说明的是,通过对象访问类变量,并没有定义新的实例变量,但是如果通过对象尝试对变量赋值,此时就完全是另一个性质——Python是动态语言,赋值语句往往意味着定义新变量。

class Inventory:
    #definition two class variables
    item = 'mouse'
    quantity = 2000

    def change(self, item, quantity):
        self.item = item
        self.quantity = quantity
    
# create an object

iv = Inventory()
iv.change('displayer', 500)
#vist iv's item and quantity
print(iv.item, iv.quantity)
''' 访问 类Inventory的类变量 '''
print(Inventory.item, Inventory.quantity)

类变量推荐使用类来访问

通过实例对象(如上iv对象)访问类变量,实际上是重新定义了两个实例变量

3.Python的“私有”成员

Python并没有提供类似于其他语言的private灯修饰符,因此Python并不能真正的支持隐藏。Python规定,只要将Python类的成员命名为以双下划线开头的,就意味着隐藏,在类外并不能直接访问。

Python允许支持多继承,但是通常推荐:如果不是很有必要,尽量不要使用多继承,而是使用单继承,这样可以保证编程思路更清晰,减少不必要的麻烦。

4.继承与父类方法重写

4.1Python继承多个父类时,如果子类没有重写父类的某一方法,那么子类对象调用该方法时,会从左到有按优先级寻找父类中该方法,当第一次找到该方法时,Python就不会继续往下查找其他父类的方法。

4.2.如果在子类中调用重写之后的方法,Python总会执行子类重写的方法,不会执行父类中被重写的方法。如果要在子类中调用父类中被重写的实例方法,可以通过使用未绑定方法即可在子类中再次调用父类中被重写的方法,显示将子类对象作为绑定的第一个参数。

class BaseClass:
    def foo(self):
        print('i am function "foo" in BaseClass')

class SubClass(BaseClass):
    def foo(self):
        print('i am function "foo" in SubClass')
    def bar(self):
        print('excute function "bar" in SubClass')
        self.foo() #直接执行foo(),调用的是子类重写之后的foo
        #使用类名调用实例方法(未绑定方法)调用父类的foo
        BaseClass.foo(self)

subcls = SubClass()
subcls.bar()

5.重写父类的构造函数

5.1 Python的子类会继承得到父类的构造方法,如果子类有多个直接父类,那么排在前面的父类的构造方法会被优先调用。

5.2 Python要求:如果子类重写了父类的构造方法,那么子类的构造方法必须调用父类的构造方法。调用方法有两种:

    >使用super函数调用父类的构造方法。常用的方式为super().__init__(parametes), 第一个self参数不必显式绑定;在调用父类的实例方法是,不必显式绑定self。

    通过super对象的方法既可调用父类的实例方法,也可调用父类的类方法。

    >使用未绑定方法调用父类的构造函数。常用方式为:BaseClass.__init__(self,parameters),第一个self参数需要显式绑定。

    >当子类继承于多个父类时,super() 函数只可用于调用第一个父类的构造函数,其余父类的构造函数只能使用未绑定的方式调用

class Employee:
    def __init__(self, salary):
        self.salary = salary
    def work(self):
        print('普通员工正在工作 , 工资是:', self.salary)

class Customer:
    def __init__(self, favorite, address):
        self.favorite = favorite
        self.address = address
    def info(self):
        print('顾客的爱好是%s, 地址是%s.' %(self.favorite, self.address))
#Manager继承了Employee和Customer
class Manager(Employee, Customer):
    #重写父类的构造方法
    def __init__(self, salary, favorite, address):
        print("--Manager's construct function--")
        #通过super()调用父类的构造方法
        '''当子类继承于多个父类时,super() 函数只可用于调用第一个父类的构造函数,其余父类的构造函数只能使用未绑定的方式调用 '''
        super().__init__(salary)
        #效果等同于上一行
        #super(Manager, self).__init__(salary)
        #使用未绑定方法调用父类的构造方法
        Customer.__init__(self, favorite, address)

#创建Manager类
mg = Manager('3000', '电子游戏','上海')
mg.work()
mg.info()

6. 使用property属性

property(fget=None, fset=None,fdel=None,doc=None)

四个属性,分别为:读,写,删除,帮助文档,任意组合都可以

class Rectangle:
    def __init__(self, width, heigth):
        self.width = width
        self.heigth = heigth
    
    def setsize(self, size):
        self.width,heigth = size
    
    def getsize(self):
        return self.width, self.heigth
    
    def delsize(self):
        self.width = self.heigth = 0
    size = property(getsize, setsize, delsize, '用于描述矩形大小的属性')
#vist doc by class
print(Rectangle.size.__doc__)
help(Rectangle.size)

rect = Rectangle(4, 3)
print(rect.size)
rect.size = 8, 9
print(rect.width, rect.heigth)
del rect.size
print(rect.width, rect.heigth)

6.2 使用 property 来修饰方法,使之成为属性

class Cell:
    #使用property 修饰方法,相当于为该属性设置getter方法
    @property
    def state(self):
        return self._state
    # setter
    @state.setter
    def state(self, value):
        if 'alive' in value.lower():
            self._state = 'alive'
        else:
            self._state = 'dead'

    #为is_dead属性设置getter方法
    #只有 getter方法的属性是只读属性
    @property
    def is_dead(self):
        return True  if self._state.lower() == 'dead'  else False

ce = Cell()
ce.state = 'Alive'
print(ce._state)
print(ce.is_dead)

7.是否自动绑定第一个参数

1. 对于在类体中定义的实例方法,Python会自动绑定方法的第一个参数(通常为self),第一个参数总是指向调用该方法的对象。

    由于实例方法(包括构造方法)的第一个self参数会自动绑定,因此程序在调用普通实例方法,构造方法是不需要为第一个参数传值。

2.Python的类在很大程度上是一个命名空间,当程序在类体中定义变量,方法时,与前面介绍的定义变量,定义函数并没有太大不同。

3.Python的类可以调用实例方法,但使用类调用实例方法是,Python不会自动为方法的第一个参数self绑定参数值;程序必须显式地为第一个参数slef传入方法调用者。实际上Python通过类调用实例方法是,只需要手动为第一个参数绑定参数值。并不要求必须绑定对应的类的实例对象。这种类调用实例方法的方式称为未绑定方法。

4.对于类变量而言,它们就是属于在类命名空间内定义的变量,因此程序不能直接访问这些变量。程序必须使用类名来调用类变量。不管是在全局范围内还是函数内方位这些类变量,都必须使用类名进行访问。

8.Python的动态性

Python的动态性:Python是动态语言,动态语言的典型特征是:类,对象的属性,方法都可以动态增加和修改。

下面的程序中,程序动然为Cat类新增了方法walk(),为类动态添加方法是不需要使用MethodType进行包装,该函数的第一个参数会自动绑定。但是这种动态性也给程序带来了一定的隐患,程序定义好的类有可能在后面被其他程序修改,带来了一些不确定性。为此我们可以通过:__slots__属性来指定。__slots__属性的值是一个元组,该元组的所有元素列出了该类的实例允许动态添加的所有属性名和方法名(对Python而言,方法相当于属性值为函数的属性)

class Cat:
    def __init__(self, name):
        self.name = name

def walk_func(obj):
    print('%s 悄悄地从花下走过...' %(obj.name))

c1 = Cat('BoZs')
c2 = Cat('Kitty')
#c1.walk() ''' AttributeError: 'Cat' object has no attribute 'walk' '''
Cat.walk = walk_func
c1.walk()
c2.walk()

class Dog:
    __slots__ = ('walk', 'age', 'name')
    def __init__(self, name):
        self.name = name
        def test():
            print('预先定义的test方法')

d =Dog('Haski')
from types import MethodType
''' 只允许为实例动态添加 walk,age,name三个属性或方法 '''
d.walk = MethodType(lambda self: print('%s正在追逐松鼠' %(self.name) ), d)
d.age = 2
d.walk()
#d.foo = 30 AttributeError: 'Dog' object has no attribute 'foo'

''' 通过类来动态添加方法 不受__slots__限制 '''
Dog.bar = lambda self : print('%s 望着远方的路口...' %(self.name))
d.bar()

>需要说明:__slots__属性并不限制通过类来动态添加属性或方法,因此最后两行的代码是合法的

> 另外,__slots__属性指定的限制只对当前的类的实例其作用,对该类派生出来的子类无约束作用。如果需要限制子类的实例动态添加属性和方法,则需要在子类中也定义__slots__属性。因此子类的实例允许动态添加属性和方法就是子类的__slots__元组加上父类__slots__元组的和。

9.通过type()函数定义类

使用type()函数定义类:type()函数可以直接查看某个类的类型或变量的类型。实际上Python完全允许使用type()函数(相当于type类的构造器函数)来创建type对象,由于type类的实例就是类,因此Python可以使用type()函数来动态创建类。

    >下面的代码使用type()定义了一个Dog类。在使用type()定义类时可指定三个参数:1.创建的类名;2.该类继承的父类集合。因为Python支持多继承,因此此处使用元组指定它的多戈弗雷,及时只有一个父类,也需要使用元组语法(单个元素的元组必须多一个逗号);3.该字典对象为该类绑定的类变量和方法。其中字典的key就是类变量或方法名,如果字典的value是普通值,那就代表类变量,如果是函数,就代表方法。

def fn(self):
    print('fn 函数')
#使用type()定义Dog类
Dog = type('Dog', (object,), dict(walk = fn, age = 6))
#创建Dog对象
d2 = Dog()
print(type(d2))
print(type(Dog))
d2.walk()
print(Dog.age)

10. 多态

对于弱类型的语言来说,变量并没有声明类型,因此同一个变量完全可以在不同的时间引用不同的对象;当一个变量在调用同一个方法时,完全可能呈现出多种行为。请看下面的程序

    >Python多态的优势在于极大的灵活性,即不论传入的对象是什么类型,只要该对象具有指定的方法,至于该方法呈现出怎样的行为特征,完全取决于传入对象的本身。这就好比一块画布上有多支画笔来画不同的形状,颜色,只要将它们放到画布中,就会呈现出不同的效果。

class Bird:
    def move(self,field):
        print('鸟在%s上自由地飞翔' %(field))
class Dog:
    def move(self, field):
        print('狗在%s上自由地奔跑' %(field))
wsx = Bird()
wsx.move('天空')
wsx = Dog()
wsx.move('草地')

11.检查类型

Python提供了如下两个函数来检查类型:

    >issubclass(cls, class_or_tuple) :检查cls是否为后一个类或元组包含的多个类中任意类的自子类

    >isinstance(obj, class_or_tuple) :检查obj是否为后一个类或元组包含的多个类中任意类的实例对象。

12.枚举类:

程序有两种方式来定义枚举类:

    > 直接使用Enum列出多个枚举值来创建枚举类

 

 

Enum()函数用于创建枚举类,该方法是Enum的构造方法,第一个参数是枚举类的类名,第二个参数是一个元组,用于列出所有的枚举值。

定义了上述的Season枚举类之后,就可以直接通过枚举值进行访问。这些枚举值都是该枚举的成员,每个成员右边name、value两个属性,

其中name属性为该枚举的变量名,value代表该枚举的序号,序号通常从1开始。

import enum
#定义季节枚举类
Season = enum.Enum('Season', ('SPRING', 'SUMMER', 'FALL', 'WINTER'))
'''
Enum()函数用于创建枚举类,该方法是Enum的构造方法,第一个参数是枚举类的类名,第二个参数是一个元组,用于列出所有的枚举值。
定义了上述的Season枚举类之后,就可以直接通过枚举值进行访问。这些枚举值都是该枚举的成员,每个成员右边name、value两个属性,
其中name属性为该枚举的变量名,value代表该枚举的序号,序号通常从1开始。
'''
print(Season.SPRING) #直接访问指定的枚举
print(Season.SPRING.name)
print(Season.WINTER.value)

#根据枚举变量名访问枚举对象
print(Season['SUMMER'])    //输出Season.SUMMER
 
#根据枚举值访问枚举对象       //输出Season.FALL
print(Season(3))

 

Python 还为枚举提供了一个 __members__ 属性,该属性返回一个 dict 字典,字典包含了该枚举的所有枚举实例。程序可通过遍历 __members__ 属性来访问枚举的所有实例。例如如下代码:

#遍历所有的成员
for name, member in Season.__members__.items():
    print(member, '--->', name,'--->', member.value)
 
 
输出如下:
Season.SPRING ---> SPRING ---> 1
Season.SUMMER ---> SUMMER ---> 2
Season.FALL ---> FALL ---> 3
Season.WINTER ---> WINTER ---> 4

    > 通过继承Enum基类来派生枚举类

如果要定义更复杂的枚举,则可通过继承 Enum 来派生枚举类,在这种方式下程序就可以为枚举额外定义方法了。例如如下程序:

import enum
class Orientation(enum.Enum):
    #为序列指定值
    EAST = '东'
    SOUTH = '南'
    WEST = '西'
    NORTH = '北'
 
    def info(self):
        print('这是一个定义方向为【%s】的枚举'% self.value)
print(Orientation.SOUTH)
print(Orientation.SOUTH.value)
#通过枚举变量访问枚举
print(Orientation['WEST'])
#通过枚举值访问枚举
print(Orientation('北'))
#调用枚举的info()方法
Orientation.EAST.info()
#循环遍历Orientation枚举的所有成员
for name, member in Orientation.__members__.items():
    print(member, '--->', name, '--->', member.value)
输出如下:
Orientation.SOUTH
南
Orientation.WEST
Orientation.NORTH
这是一个定义方向【东】的枚举
Orientation.EAST ---> EAST ---> 东
Orientation.SOUTH ---> SOUTH ---> 南
Orientation.WEST ---> WEST ---> 西
Orientation.NORTH ---> NORTH ---> 北

 上面程序通过继承 Enum 派生了 Orientation 枚举类,通过这种方式派生的枚举类既可额外定义方法,如上面的 info() 方法所示,也可为枚举指定 value(value 的值默认是 1、2、3、…)。

虽然此时 Orientation 枚举的 value 是由类型,但该枚举同样可通过 value 来访问特定枚举,如上面程序中的 Orientation('南'),这是完全允许的。

>枚举构造器

枚举也是类,因此枚举也可以定义构造器。为枚举定义构造器之后,在定义枚举实例时必须为构造器参数设置值。例如如下程序:

class Gender(enum.Enum):
    MALE = '男', '阳刚之力'
    FEMALE = '女', '柔顺之美'
 
    def __init__(self, cn_name, desc):
        self._cn_name = cn_name
        self._desc = desc
 
    @property
    def desc(self):
        return self._desc
    @property
    def cn_name(self):
        return self._cn_name
#访问FEMALE的name
print('FEMALE的name:', Gender.FEMALE.name)
#访问FEMALE的value
print('FEMALE的value:', Gender.FEMALE.value)
#访问自定义的cn_name属性
print('FEMALE的cn_name:', Gender.FEMALE.cn_name)
# 访问自定义的desc属性
print('FEMALE的desc:', Gender.FEMALE.desc)

上面程序定义了 Gender 枚举类,并为它定义了一个构造器,调用该构造器需要传入 cn_name 和 desc 两个参数,因此程序使用如下代码来定义 Gender 的枚举值。

MALE = '男', '阳刚之力'
FEMALE = '女', '柔顺之美

上面代码为 MALE 枚举指定的 value 是‘男’和‘阳刚之力’这两个字符串,其实它们会被自动封装成元组后传给 MALE 的 value 属性;而且此处传入的‘男’和‘阳刚之力’ 这两个参数值正好分别传给 cnname 和 desc 两个参数。简单来说,枚举的构造器需要几个参数,此处就必须指定几个值。

输出

FEMALE的name: FEMALE
FEMALE的value: ('女', '柔顺之美')
FEMALE的cn_name: 女
FEMALE的desc: 柔顺之美