14--Python 面向对象进阶

2019-06-11  本文已影响0人  Roger田翔
@Author : Roger TX (425144880@qq.com)
@Link : https://github.com/paotong999

一、封装

封装是从业务逻辑中抽象对象时,要赋予对象相关数据与操作,将一些数据和操作打包在一起的过程。
封装对类形成了一种“黑盒”状态,我们不需要知道类内部是什么样的,只要对对象进行操作就可以。
1.含义

封装是对全局作用域中其它区域隐藏多余信息的原则。

2.实例

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线 __,在Python中,实例的变量名如果以 __ 开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。

二、继承

继承概念

  1. 继承实现了代码的重用,相同的代码不需要重复的编写。
  2. 在Python中子类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类
  3. 父类的私有属性和方法不会被子类继承

继承顺序

  1. Python3中如果不指定继承哪个类,默认就会继承Object类,继承了Object类的类就叫做新式类。
  2. Python2中如果不指定继承哪个类,不会默认去继承Object类,没有继承Object类的类就叫做经典类。
  3. 经典类和新式类的不同就在于对方法的搜索顺序不同,经典类是深度优先;而新式类是广度优先。
继承类型
继承顺序

单继承与多继承

  1. 父类中没有的属性在子类中出现叫做派生属性,父类中没有的方法在子类中出现叫做派生方法
  2. 只要是子类的对象调用,子类中有的名字一定用子类的,子类中没有才找父类的,如果父类也没有报错
  3. 如果父类、子类都有则用子类的,如果还想用父类的,单独调用父类的
    -- 父类名.方法名 需要自己传self参数
    -- super().方法名 不需要自己传self
  4. 可以用 Foo.__mro__ 方法查看继承顺序
class A:
    def __init__(self,a=None):
        self.a=a
    def test(self):
        print("A...test")

class B:
    def __init__(self,b=None):
        self.b=b
    def test(self):
        print("B...test")

class C(B,A):
    def __init__(self,a):
        A.__init__(self,a)
    def t(self):
        A.test(self)    # 调用A的test()
        super().test()    # 这个调用的也是B的test
        print("C....t")

c=C("aa")
#默认调用的是父类B的test方法,因为在class C(B,A),B在A前面
c.test()
c.t()

二、super() 函数

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

子类的构造方法调用父类的构造方法有两种方式:

  1. 使用未绑定方法,这种方式很容易理解。因为构造方法也是实例方法,当然可以通过这种方式来调用。
  2. 使用 super() 函数调用父类的构造方法。

调用 super() 的本质就是调用 super 类的构造方法来创建 super 对象

  1. 使用 super() 构造方法最常用的做法就是不传入任何参数,然后通过 super 对象的方法调用父类方法。
  2. 在调用父类的实例方法时,程序会完成第一个参数 self 的自动绑定。
  3. 在调用类方法时,程序会完成第一个参数 cls 的自动绑定。
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))

class Manager(Employee, Customer):
    # 重写父类的构造方法
    def __init__(self, salary, favorite, address):
        print('--Manager的构造方法--')
        # 通过super()函数调用父类的构造方法
        super().__init__(salary)
        # 与上一行代码的效果相同
        #super(Manager, self).__init__(salary)
        # 使用未绑定方法调用父类的构造方法
        Customer.__init__(self, favorite, address)
# 创建Manager对象
m = Manager(25000, 'IT产品', '广州')
m.work()
m.info()

三、抽象类&接口类

@abstractmethod:抽象方法
含abstractmethod方法的类不能实例化,继承的子类必须实现 @abstractmethod 装饰的方法,未被装饰的可以不重写

from abc import abstractclassmethod,ABCMeta

class Payment(metaclass=ABCMeta):
    @abstractclassmethod
    def pay(self):
        print('支付ing....')

class Wechatpay(Payment):
    def pay(self):
        print('微信支付ing...')

@ property:方法伪装属性
方法返回值及属性值,被装饰方法不能有参数,必须实例化后调用,类不能调用
将一个方法伪装成属性,被修饰的特性方法,内部可以实现处理逻辑,但对外提供统一的调用方式
这个装饰器还有和其配套的setter、deleter

class Data:
    def __init__(self):
        self.number = 123

    @property
    def operation(self):
        return self.number

    @operation.setter
    def operation_set(self, number):
        self.number = number

    @operation.deleter
    def operation_del(self):
        del self.number

d = Data()
print(d.operation)
d.operation_set = 222
del d.operation_del

四、多态

不同的子类对象调用相同的父类方法,产生不同的执行效果,可以增加代码的外部调用灵活度。
父类变量能够引用子类对象,当子类有重写父类方法,调用的将是子类方法。

  1. 定义一个父类
  2. 定义多个子类,并重写父类的方法
  3. 传递子类对象给调用者,不同子类对象产生不同的执行效果

五、枚举

在某些情况下,一个类的对象是有限且固定的,比如季节类,它只有 4 个对象;再比如行星类,目前只有 8 个对象。这种实例有限且固定的类,在 Python 中被称为枚举类。

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

  1. 直接使用 Enum 列出多个枚举值来创建枚举类。
  2. 通过继承 Enum 基类来派生枚举类。

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

import enum
# 定义Season枚举类
Season = enum.Enum('Season', ('SPRING', 'SUMMER', 'FALL', 'WINTER'))

# 直接访问指定枚举
print(Season.SPRING)    # Season.SPRING
# 访问枚举成员的变量名
print(Season.SPRING.name)    # SPRING
# 访问枚举成员的值
print(Season.SPRING.value)    # 1
# 根据枚举变量名访问枚举对象
print(Season['SUMMER'])    # Season.SUMMER
# 根据枚举值访问枚举对象
print(Season(3))    # Season.FALL

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

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

# 遍历Season枚举的所有成员
for name, member in Season.__members__.items():
    print(name, '=>', member, ',', member.value)

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

from enum import Enum, unique, IntEnum 

@unique
class Orientation(Enum):
    # 为序列值指定value值
    EAST = '东'
    SOUTH = '南'
    WEST = '西'
    NORTH = '北'
    def info(self):
        print('这是一个代表方向【%s】的枚举' % self.value)

# 通过枚举变量名访问枚举
print(Orientation['WEST'])    # Orientation.WEST
# 通过枚举值来访问枚举
print(Orientation('南'))    # Orientation.SOUTH
# 调用枚举的info()方法
Orientation.EAST.info()    # 这是一个代表方向【东】的枚举

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

  1. @unique 装饰器防止value值相同,如果没有这个标签,当有两个值相同时,第二个name相当于第一个name的别名
  2. Enum 允许 value 为非整型,IntEnum 只允许 value 为整型
  3. 枚举类是单例模式,不能实例化

六、metaclass元类

如果希望创建某一批类全部具有某种特征,则可通过 metaclass 来实现。使用 metaclass 可以在创建类时动态修改类定义

为了使用 metaclass 动态修改类定义,程序需要先定义 metaclass, metaclass 应该继承 type 类,并重写 __new__() 方法。

metaclass,直译为元类,可以理解为类的元数据
先定义metaclass,就可以创建类,最后创建实例。你可以把类看成是metaclass创建出来的“实例”。

定义ListMetaclass,按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass:

# metaclass是类的模板,所以必须从`type`类型派生:
class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

__new__() 方法接收到的参数依次是:

有了ListMetaclass,我们在定义类的时候还要指示使用ListMetaclass来定制类,传入关键字参数metaclass:

class MyList(list, metaclass=ListMetaclass):

当我们传入关键字参数metaclass时,Python解释器在创建MyList时,要通过 ListMetaclass.__new__() 来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。

现在 MyList 就可以调用add()方法

L = MyList()
L.add(1)
L

下面看一个元类使用的实例

# 定义ItemMetaClass,继承type
class ItemMetaClass(type):
    # cls代表动态修改的类
    # name代表动态修改的类名
    # bases代表被动态修改的类的所有父类
    # attr代表被动态修改的类的所有属性、方法组成的字典
    def __new__(cls, name, bases, attrs):
        # 动态为该类添加一个cal_price方法
        attrs['cal_price'] = lambda self: self.price * self.discount
        return type.__new__(cls, name, bases, attrs)

# 定义Book类
class Book(metaclass=ItemMetaClass):
    __slots__ = ('name', 'price', '_discount')

    def __init__(self, name, price):
        self.name = name
        self.price = price

    @property
    def discount(self):
        return self._discount

    @discount.setter
    def discount(self, discount):
        self._discount = discount
        
# 定义cellPhone类
class CellPhone(metaclass=ItemMetaClass):
    __slots__ = ('price', '_discount' )

    def __init__(self, price):
        self.price = price

    @property
    def discount(self):
        return self._discount

    @discount.setter
    def discount(self, discount):
        self._discount = discount

b = Book("Python基础教程", 89)
b.discount = 0.76
# 创建Book对象的cal_price()方法
print(b.cal_price())
cp = CellPhone(2399)
cp.discount = 0.85
# 创建CellPhone对象的cal_price()方法
print(cp.cal_price())

通过使用 metaclass 可以动态修改程序中的一批类,对它们集中进行某种修改。这个功能在开发一些基础性框架时非常有用,程序可以通过使用 metaclass 为某一批需要具有通用功能的类添加方法。

上一篇下一篇

猜你喜欢

热点阅读