面向对象

一、封装

  • 将数据和行为进行封装,形成类
    1、类的定义
class TestClass:
    # 公有类变量,可以用类名直接调用
    cls_var_1 = 'pulic class variable'
    # 私有类变量,只能在类内部调用
    # 定义私有变量或函数,都以双下划线开始,且不以双下划线结尾
    __cls_var_2 = 'private class variable'
    
    # 构造函数(初始化函数),初始化类的时候被调用
    # 以双下划线开始,且以双下划线结尾的方法,都有特殊意义
    def __init__(self, x, y):
        # 定义实例变量(公有实例变量),只有实例化之后才能调用
        self.x = x
        # 定义私有实例变量,初始化之后,也只能通过内部方法调用
        self.__y = y
    
    # 公有实例方法(第一个参数必须要有,且约定为“self”,代表类本身)
    # 实例方法只是实例化之后才能被实例调用
    def method_1(self, *args, **kwargs):
        pass
    
    # 私有实例方法
    def __method_2(self, *args, **kwargs):
        pass
    
    # 公有类方法(第一个参数必须要有,且约定为“cls”,代表类本身)
    # 公有类方法可以直接用类名调用(无需实例化)
    # 类方法的的定义跟实例方法类似,只需要加上classmethod的装饰器即可
    @classmethod
    def method_11(cls, *args, **kwargs):
        pass
    
    # 私有类方法
    @classmethod
    def __method_12(cls, *args, **kwargs):
        pass
    
    # 静态方法,使用频率很低,不要传递self或cls变量
    # 静态方定义类似类方法,只需要加上staticmethod的装饰器即可
    # 法效果上:只是给方法加了个命名空间
    @staticmethod
    def method_111(*args, **kwargs):
        pass

2、python中的getter、setter函数

# python中的getter、setter函数
class A:
    def __init__(self):
        self.__val = 0
        
    # 当实例函数的参数只有self的时候,可以加property装饰器
    # 可以将函数定义成属性,实例化之后,是需要实例名.调用即可,不用加括号
    # 被property装饰的属性是只读的,不能动态修改
    # 实际上,跟java中的getter函数一样,只读且不可修改私有属性
    @property
    def val(self):
        if self.__val < 0:
            return 0
        return self.__val
    
    # 被装饰成setter函数的前提是,函数名必须要跟被装饰property的函数名一样
    # setter函数接收一些参数,并且在内部做一些类型和数值判断
    @val.setter
    def val(self, value):
        if isinstance(value, (int, float)) and value >= 0:
            self.__val = value
        else:
            self.__val = 0

二、继承

1、单继承

# 定义一个具有门牌号、开关门状态的类“Door”
class Door:
    # 初始化的时候,定义了门牌号、开关门状态
    def __init__(self, number, status):
        self.__number = number
        self.__status = status
        
    def open(self):
        self.__status = 'is_oppened'
        print("open the door")
        
    def close(self):
        self.__status = 'is_closed'
        print("close the door")
# 定义一个具有锁的门(继承自类“Door”)
class LockableDoor(Door):
    # 重写构造方法
    def __init__(self, number, status, is_locked):
        # 初始化父类的实例变量
        super(LockableDoor, self).__init__(number, status)
        self.__is_locked = is_locked
    
    # 重写父类的open方法,因为子类的open跟父类的不完全一样,且有相似的地方
    def open(self):
        if not self.__is_locked:
            # 如果门没有上锁,开门动作跟Door一样,执行父类的open方法
            super(LockableDoor, self).open()
        else:
            # 如果门上锁了,执行子类自己的方法
            print('is_locked, can not open.')
    
    # 关门的行为跟Door一样,不需要在子类中再定义,可以省略
    
    # 定义子类中的新方法,锁门动作是子类独有方法,父类中没有
    def lock(self):
        if self.__status == 'is_opened':
            print('is openning, can not lock.')
        else:
            self.__is_locked = True
  • 单继承的规则
    1、私有的方法和变量不可以继承(包含类级和实例级的方法和变量)
    2、公有的方法和变量是可继承的(类级和实例级的)
    3、父类公有方法(实例级和类级)可以访问父类的私有变量(即使子类中重新定义了同名的私有变量,调用父类的公有方法,访问的依然是父类的私有变量)(父类的私有变量不会被子类覆盖)(父类的公有变量会被子类覆盖,包含类级的和实例级的)
    上面一条的出现,主要是私有变量改名导致的

2、 多继承规则

# 定义文档类,初始化文档的内容
class Document:
    def __init__(self, content):
        self.content = content

# 定义Word类型的文档,继承自Document类   
class Word(Document):
    # Word文档具有自己的格式化行为
    def formater(self):
        self.content = 'Word document: {0}'.format(self.content)
        
# 定义Excel类型的文档,继承自Document类
class Excel(Document):
    def formater(self):
        self.content = 'Excel document: {0}'.format(self.content)

# 定义打印机类Printer
class Printer:
    def display(self):
        print('{0} printed on Printer.'.format(self.content))
        
# 定义显示器类Monitor
class Monitor:
    def display(self):
        print('{0} printed on Monitor.'.format(self.content))
        
# 将多个功能单一的类组合成打印各种文档的类(MIXIN混入模式,实现组合)

# 1、拼凑成在打印机上打印Word文档的类
class WordOnPrinter(Word, Printer):
    pass

# 2、拼凑成在显示器上显示word文档的类
class WordOnMonitor(Word, Monitor):
    pass

# 3、拼凑成在打印机上打印excel文档的类
class ExcelOnPrinter(Word, Printer):
    pass

# 4、拼凑成在显示器上显示excel文档的类
class ExcelOnMonitor(Word, Monitor):
    pass

MIXIN实现了数据跟方法的分离,被组合的多个类,不能独立运行,必须通过组合才能发挥作用,此时,不用考虑继承顺序及MRO(通过C3算法计算而来)。

  • 多继承规则;:
    1、不能相互继承,防止A继承B,B继承C,C又继承A,要符合MRO规则
    2、符合MIXIN的类可以随意组合,无需考虑MRO
    3、多继承如果遇到同名函数,会按照继承列表从左至右,先遇到哪个就执行哪个;
    所以,MIXIN类要写在前面,避免MIXIN类里的方法得不到执行。

三、魔术方法

以双下滑下开始,且以双下划线结尾的方法,都有特殊含义,也叫魔术方法。

  • 对象的创建与销毁
    1、' __ new__ ' 创建对象
    2、'__ init__' 初始化对象
    3、'__ del__' 当对象销毁(垃圾回收)的时候调用

  • 可视化对象

# 定义一个类Test
class Test:
    def __init__(self, name):
        self.name = name
        
    # 当print该类的实例对象的时候,会调用此方法
    # 如果不手动更改返回值,默认会返回实例对象的机器码数值(如对戏类型,内存地址)
    # 如果定义此方法的同时,还定义了__str__魔术方法,则返回__str__方法定义的字符串
    def __repr__(self):
        return self.name
    
    # 当使用str()函数查看该类的字符串时,调用此方法
    def __str__(self):
        return 'call__str__name is {0}'.format(self.name)
    
    # 当使用bytes()函数查看该类的字符串时,调用此方法
    def __bytes__(self):
        return 'call__str__name is {0}'.format(self.name).encode('utf-8')

用的比较多的就是__ str__可视化方法

  • 比较运算符重载
# 定义一个Person类,实现根据人的年龄比较大小
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    # 当该对象实现了以下魔术方法的时候,可以实现根据年龄比较大小
    def __lt__(self, other):
        return self.age < other.age
    
    def __le__(self, other):
        return self.age <= other.age
    
    def __eg__(self, other):
        return self.age == other.age
        
    def __ne__(self, other):
        return self.age != other.age
        
    def __ge__(self, other):
        return self.age >= other.age
        
        
    def __gt__(self, other):
        return self.age > other.age

比较运算符的运算会调用相应的比较魔术方法

  • 布尔函数bool

list默认没有__ bool__()函数,会调用__ len__()方法,判断其长度;
如果明确定义了__ bool__()函数,就不会调用__ len__()方法。

  • 哈希函数

__ hash__() 返回一串数字

  • 可调用对象

实现了__ call__() 方法的类,就是可调用对象。
判断一个对象是否可调用,可以使用callable()函数进行判断。

  • 使用可调用对象给类做装饰器
# 装饰器不仅可以装饰方法,也可以装饰类
# 使用类装饰器实现单例模式
class Single:
    # 定义一个私有类变量,存储被装饰的类的实例
    __instance = None
    # 初始化函数接收一个类作为参数
    def __init__(self, cls):
        self.cls = cls
        
    # 定义__call__()方法,被调用的时候执行
    def __call__(self, *args, **kwargs):
        # 判断是否已经存在cls的实例
        if not self.__instance:
            # 如果还没有cls类的实例出现,则初始化一个cls的实例
            # 并将初始化的cls实例赋值给self.__instance变量
            self.__instance = self.cls(*args, **kwargs)
        # 返回初始化的实例
        return self.__instance

@Single
class Test:
    pass

test = Test()    <==等价于==>    Single(Test)()

当保存一些全局性的变量,一般使用类做装饰器。
可调用对象通常用来写类的装饰器。

  • 反射

dir()就是一个标准的反射,
执行dir()的时候,会调用对象的__ dict__属性。

调用__ dict__属性的时候,会以字典的形式打印出所有的实例属性,
类属性以及各种方法都不会显示。

getattr()函数,返回类属性和类方法,不会返回实例属性。
例如:getattr(类名,类属性/类方法的字符串形式)

setattr()函数,动态的给某个对象增加实例属性
delattr()函数,动态的删除某个属性

# 定义一个获取属性的类
class GetAttr:
    def __init__(self):
        self.__dic = {'x':1, 'y':2}
        # 此处定义了一个字典里面不存在的属性,在获取的时候就不会调用__ getattr__方法
        self.a = 11
        # 当定义了实例属性x, 就不会调用__ getattr__方法
        self.x = 22
        # 当获取y属性的时候,会先在初始化阶段查找是否有y属性
        # 如果没有,就调用__ getattr__方法继续查找
        # 如果__ getattr__方法也没找到,才会抛出异常
        
    def __getattr__(self, name):
        print('get {0}'.format(name))
        return self.__dic.get(name)

# 实例化一个GetAttr对象
g1 = GetAttr()

# 调用g1的x属性
# 这里,在字典和初始化阶段都有x属性
# 会直接调用初始化阶段的x属性
# 不会检查__getattr__方法
g1.x
返回  22

__ getattr__ 调用顺序:先查找类本身的属性,没找到再调用此方法
__ getattrute__调用顺序:直接调用此方法,不管找到与否都不去查找类本身的属性

  • with语句与"__ enter__" 和 "__ exit__"

当打开一种资源,进行一系列操作之后,再关闭连接,
这个时候需要用到with语句(必入数据库的连接操作)

# 比如,进行文件读取操作的时候,使用with语句
with open('./text.log') as f:
    f.readline()
    ......

只要一个类同时实现了"__ enter__" 和 "__ exit__"两个魔术方法,
就可以使用with语句。

通常情况下:将一些打开和初始化的工作在__ enter__方法里完成;
结束后的清场工作,都在__ exit__方法里完成。

# 定义一个资源类
class Resource:
    def __init__(self):
        print('init 操作')
    
    # 定义__enter__方法,进入操作
    def __enter__(self):
        print('enter 打开资源、建立连接等操作')
    
    # 定义__exit__方法,离开操作
    def __exit__(self, *args, **kwargs):
        print('exit 关闭资源、关闭连接,清场操作')

# with语句块结束的时候,会自动调用Resource类的__exit__方法进行清场操作
with Resource() as rs:
    print('rs 业务逻辑操作')
# 执行结果如下
init 操作
enter 打开资源、建立连接等操作
rs 业务逻辑操作
exit 关闭资源、关闭连接,清场操作

可以有效的避免忘记关闭资源的后患。

四、描述器

实现了以下三个魔术方法的类,就是描述器
1、__ get__
2、__ set__
3、__ delete__

# 定义一个Number类,用于描述数字类型的变量
class Number:
    def __init__(self, name):
        self.name = name
        
    def __get__(self, instance, cls):
        print('get')
        if instance is not None:
            return instance.__dict__[self.name]
        return self
    
    def __set__(self, instance, value):
        print('set')
        if isinstance(value, (int, float)):
            instance.__dict__[self.name] = value
        else:
            raise TypeError('Excepted int or float type')
            
    def __delete__(self, instance):
        print('delete')
        del instance.__dict__[self.name]
# 定义一个坐标点类
class Point:
    x = Number('x')
    y = Number('y')
    
    def __init__(self, x, y):
        self.x = x
        self.y =y

# 实例化一个坐标点
point = Point(1, 2)

# 获取横坐标
point.x  # Point.x.__get__(point, Point)

描述器多用于类型检查

以下校验失败,不知道问题出在哪里,待解决。。。。。。

# 定义一个类型定义(用于类型检查)的描述器
class Typed:
    def __init__(self, name, excepetedType):
        self.name = name
        self.excepetedType = excepetedType
        
    def __get__(self, instance, cls):
        print('get')
        if instance is None:
            return self
        return instance.__dict__[self.name]
    
    def __set__(self, instance, value):
        print('set')
        if isinstance(value, self.excepetedType):
            instance.__dict__[self.name] = value
        else:
            raise TypeError('Excepted {0}, but given {1}'.format(self.excepetedType, type(value)))
            
    def __delete__(self, instance):
        del instance.__dict__[self.name]
from functools import wraps

# 定义一个类型检查的装饰器(带参数的装饰器)
def typeAssert(**kwargs):
    def inner(cls):
        @wraps(cls)
        def wrap(**kwargs):
            for k,v in kwargs.items():
                # 遍历关键字参数列表
                # 将每一项参数经过类型检查之后,以属性的方式添加到cls
                setattr(cls, k, Typed(k, v))
            return cls
        return wrap
    return inner
@typeAssert(x=int, y=float, z=str)
class Test:
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
# 以下语句没报错,说明出问题了,问题待解决。。。。。。
test = Test(x=1, y='123', z='qwe')

描述器是一个方法传递协议。

五、异常

1、异常处理

错误:通常指程序运行中不可恢复的问题。
异常:通常指可以在程序运行时恢复的错误。

# 定义一个异常语句块,基础语法如下
# 将可能出现异常的代码写在try语句内
try:
    print('begin')
    1 / 0
    print('end')
    
# 下面进行异常捕获,使用except语句
# except语句属于可选项,可出现多次
# 按照顺序依次匹配,执行最先匹配到的异常语句
# 所以,异常捕获应按照先子类,后父类的顺序书写except语句
except Exception as e:
    print('Exception - ',e)
    
# as语句将捕获到的异常的实例赋值给e变量,便于对异常对象的操作处理
except TypeError as e:
    print('TypeError - ', e)

# 如果不需要对异常对象做任何处理,也可以只写except关键字,后面什么都不加
# 如果只要except关键字,后面没有任何模式匹配的类型的时候,会匹配所以异常类型
except ZeroDivisionError as e:
    print('TypeError - ', e)

# 当处理完异常,即将推出异常处语句块的时候执行finally语句
# 即使上面出现过return语句,finally语句还是会执行
# 通常,会在finally语句块里面做一些资源清理或者释放资源的操作
# 如:关闭数据库连接,关闭正在读写的文件
finally:
    print('finally do something...')
# 输出结果
begin
Exception -  division by zero
finally do something...

2、抛出异常

手动抛出异常: 使用raise关键字
异常抛出,会抛给上层处理,如果上层没有处理,会继续上抛直到顶层

def fn(i):
    if i < 0:
        raise Exception('i<0')

BaseException

  • Exception
  • GeneratorExit
  • KeyboardInterrupt
  • SystemExit

3、自定义异常

定义一个类,继承Exception即可
编写库的时候,通常会自定义一些异常,方便调用者排查故障

六、模块化

Python中,模块、包和库的概念没有清晰的边界
一个文件就是一个模块,模块名就是文件名
一个目录,包含了__ init__.py就是一个包
通常,当一个或者若干个包,包含了一个setup.py就认为是一个库

导入一个模块,实际上就是执行该模块,所以不要在模块里编写全局性的操作语句。

  • 相对导入和绝对导入

同一包内的模块可以使用相对导入,也可以使用绝对导入。
1、相对引用:
引用同一级目录下的模块,使用.加模块名
引用载上一级的模块,使用..加模块名(再上一级使用...加模块名)
2、绝对引用:
from 完整的模块路径名 import 模块下的方法

如果是同一目录下的引用,一般使用相对引用,当顶层的包名修改的时候,引用不受影响。当相对引用的层次太深的时候,会导致引用者搞不清引用的是哪个包。

  • 循环导入

应避免循环引用(会报错)

  • 发布自己的库
# 在要发布的包内,创建一个setup.py的文件即可
# 文件内容,大致如下(引入标准库的方式)
from distutils.core import setup
# setup函数接收一个可变关键字参数
# 以字典的形式传递参数
setup(**{
  'name': '库名',
  'version': '1.0.0',
   # 目录名(不会级联子目录,需要单独指定)
  'packages': ['包列表', '包列表.子目录'] ,
  # 安装该包的时候,依赖的库或者包列表(可选项)
  'requires': ['依赖的库名']
})
# 也可用关键字参数的形式传递参数
setup(
  name = '库名',
  version = '1.0.0',
  packages = ['要发布的包列表']
)

# 创建好setup.py之后,在setup.py所在目录执行build命名
$ python setup.py build
# 引入第三方增强库的方式编写setup.py文件(通常使用这种方式)
from setuptools import setup, find_packages
setup(**{
  'name': '库名',
  'version': '1.0.0',
  # find_packages函数会自动查找目录
  'packages': find_packages(),
  'install_requires': ['依赖的库名==(>=)版本号']
})

发布出来之后,会生成一个目录文件。
安装的时候,只需要进入该目录,执行pip命令即可:
$ pip install .
也可以,使用python命令安装:
$ python setup.py install
如果放在而来GitHub上,也可以使用pip安装:
$ pip install git+URL

  • pip常用命令
    1、freeze:列出当前项目正在使用的库和包(包含库的具体版本)
# 将当前项目的依赖包到处到requirements.txt文件中
$ pip freeze > requirements.txt

2、install:安装命令

# 从该文件读取并安装
$ pip install -r requirements.txt
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • 1 面向对象No6 面向对象 OO Object Oriented 编程时以对象为单元,封装数据和逻辑,以此提...
    征程_Journey阅读 1,055评论 0 2
  • java继承 继承的概念 继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。 继承就是子类继...
    863cda997e42阅读 630评论 0 1
  • C++类和对象 C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心...
    863cda997e42阅读 581评论 0 4
  • 面向对象笔记 一、 对象在内存中的存放方法以及被调用过程 class文件首先被加载到方法区中的class文件内容区...
    VictorBXv阅读 427评论 0 2
  • 引用类型就是指针类型 构造函数不需要要返回值 扩充构造函数(不会)【也可以扩充字典转模型的构造函数】 函数既可以作...
    CoderZb阅读 261评论 0 0