面向对象高级

286 阅读15分钟

1、__new__方法

这是我参与更文挑战的第10天,活动详情查看: 更文挑战

1.1 __new__的作用

总结

  • __new__ 是一种负责创建类实例的静态方法,它无需使用 staticmethod 装饰器修饰,且该方法会优先 __init__ 初始化方法被调用。
  • __new__必须要有返回值,返回实例化出来的实例,可以return父类__new__出来的实例,或者直接是object的__new__出来的实例
  • __init__有一个参数self,就是这个__new__返回的实例,__init____new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值
  • __new__方法是新式类出现的方法。
  • __new__方法和__init__方法一样,都是在要实例化对象时调用,但是__new__方法先被调用
class GirlFriend(object):
    def __new__(cls):
        print("我先被执行")
        # new方法中必须返回实例化出来的对象,该对象要通过object类或者父类中的__new__方法实现返回当前类的对象
        return object.__new__(cls)

    def __init__(self):
        print("我后被执行")

GirlFriend()
# 运行结果为:
"""
我先被执行
我后被执行

"""

2、单例模式

2.1 单例是什么

举个常见的单例模式例子,一个优秀的男孩子可能会被很多女生喜欢,但是这个男孩子他只能有一个女朋友(对象),这个唯一的女朋友就是单例(唯一的对象)。

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,单例模式是一种对象创建型模式。

2.2 创建单例-保证只有1个对象

class GirlFriend(object):
    # 定义该变量用来保存第一个实例化出来的对象的内存地址
    __isinstance = None

    def __new__(cls):
        # 第一次创建对象时,条件成立会给类属性__isinstance赋值为第一次的实例出的对象地址,等以后在创建条件永远不会成立,就修改不了改地址值,因此只会返回__isinstance保存下的对象
        if not cls.__isinstance:
            cls.__isinstance = super().__new__(cls)
        return cls.__isinstance


a = GirlFriend()
b = GirlFriend()
print("a的内存地址为:", id(a))
print("b的内存地址为:", id(b))

运行结果:

a的内存地址为: 2096279946016
b的内存地址为: 2096279946016

2.3 创建单例时,只执行1次__init__方法

class GirlFriend(object):
    # 定义该变量用来保存第一个实例化出来的对象的内存地址
    __isinstance = None
    # 该变量表示__init__方法的运行状态
    __first = True

    def __new__(cls, *args, **kwargs):
        # 第一次创建对象时,条件成立会给类属性__isinstance赋值为第一次的实例出的对象地址,等以后在创建条件永远不会成立,就修改不了改地址值,因此只会返回__isinstance保存下的对象
        if not cls.__isinstance:
            cls.__isinstance = super().__new__(cls)
        return cls.__isinstance

    def __init__(self, name):
        # 当第一次实例化时,对象第一次运行init方法会将类属性_first的状态修改为False,这样等以后在实例化对象,就不会在对初始化的值进行修改了,确保了单例的唯一性
        if self.__first:
            self.__first = False
            self.name = name


a = GirlFriend("小迪")
b = GirlFriend("小薇")
print("a的内存地址为:", id(a))
print(a.name)
print("b的内存地址为:", id(b))
print(b.name)


运行结果:

a的内存地址为: 2002801034464
小迪
b的内存地址为: 2002801034464
小迪

3、python是动态语言

3.1 动态语言的定义

动态编程语言高级程序设计语言 的一个类别,在计算机科学领域已被广泛应用。它是一类 在运行时可以改变其结构的语言 :例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。动态语言目前非常具有活力。例如JavaScript便是一个动态语言,除此之外如 PHP 、 Ruby 、 Python 等也都属于动态语言,而 C 、 C++ 等语言则不属于动态语言。----来自 维基百科

class Hero(object):
    def __init__(self, name, hobby):
        # 在init方法中只初始化了两个实例对象属性
        self.name = name
        self.hobby = hobby

    def __str__(self):
        return "我是{},爱好{}".format(self.name, self.hobby)


if __name__ == '__main__':
    # 实例化一个Hero对象
    yu_da_ye = Hero("于谦", "抽烟喝酒烫头")
    print("对象的姓名属性:", yu_da_ye.name)
    print("对象的爱好属性:", yu_da_ye.hobby)
    # 对于name和hobby是对象在类中定义好的属性,都可以通过对象名.属性的形式调用出来
    # 先通过 对象名.job获取job的属性,但是对于类中并没有定义这个属性,我们可以通过动态语言的特性给对象动态的添加属性
    yu_da_ye.job = "说相声"
    print("对象的工作属性为:", yu_da_ye.job)

运行效果

对象的姓名属性: 于谦
对象的爱好属性: 抽烟喝酒烫头
对象的工作属性为: 说相声

4、__slots__的使用

4.1 使用`__slots__`限制对象的属性

通过上一个章节的代码的演示,可以看出,python程序是可以在运行的时候给对象增加新的属性,为了限制这项操作我们需要借助__slots__属性来完成。代码案例

class Hero(object):
    # 定义__slots__属性来限制对象只能具备哪些属性,__slots__属性需要赋予一个对象的属性名字的字符串的容器,该容器可以为list,tuple或者set
    __slots__ = ('name', 'hobby')

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


if __name__ == '__main__':
    # 实例化一个Hero对象
    yu_da_ye = Hero("于谦", "抽烟喝酒烫头")
    print("对象的姓名属性:", yu_da_ye.name)
    print("对象的爱好属性:", yu_da_ye.hobby)
    # 对于job属性来说,__slots_中没有该属性的字符串数据所以解释器报错
    yu_da_ye.job = "说相声"
    print("对象的工作属性为:", yu_da_ye.job)

运行结果

对象的姓名属性: 于谦
对象的爱好属性: 抽烟喝酒烫头
AttributeError: 'Hero' object has no attribute 'job'

4.2 `__slots__`只对自己所在类中进行限制,子类无法继承

class Hero(object):
    __slots__ = ('name', 'hobby')

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

class USAHero(Hero):
    pass

if __name__ == '__main__':
    # 实例化一个Hero对象
    spy = USAHero("川建国", "发推特")
    print("对象的姓名属性:", spy.name)
    print("对象的爱好属性:", spy.hobby)
    # 对于job属性来说,__slots_中没有该属性的字符串数据所以解释器报错
    spy.job = "总统"
    print("对象的工作属性为:", spy.job)

运行结果

对象的姓名属性: 川建国
对象的爱好属性: 发推特
对象的工作属性为: 总统

property属性

1.可以改变类中方法调用的形式

可以是对象的方法调用时和属性调用一样

class Hero(object):

    def eat(self):
        print("英雄喜欢吃")

    @property
    def sleep(self):
        print("英雄喜欢睡觉")

if __name__ == '__main__':
    a = Hero()
    a.eat()
    a.sleep
property属性的定义和调用要注意一下几点:
  • 定义时,在实例方法的基础上添加 @property 装饰器;并且仅有一个self参数
  • 调用时,无需括号

2. property属性的完整使用

三种@property装饰器

#coding=utf-8
# ############### 定义 ###############
class Goods:
    """定义一个商品类
       第一种装饰器:@property
       第二种装饰器:@property方法名.setter
       第三种装饰器:@property方法名.deleter
    """
    @property
    def price(self):
        print('@property')

    @price.setter
    def price(self, value):
        print('@price.setter')

    @price.deleter
    def price(self):
        print('@price.deleter')

# ############### 调用 ###############
obj = Goods()
obj.price          # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
obj.price = 123    # 自动执行 @price.setter 修饰的 price 方法,并将  123 赋值给方法的参数value
del obj.price      # 自动执行 @price.deleter 修饰的 price 方法

三种装饰器的应用

class Goods(object):

    def __init__(self):
        # 原价
        self.original_price = 100
        # 折扣
        self.discount = 0.8

    @property
    def price(self):
        # 实际价格 = 原价 * 折扣
        new_price = self.original_price * self.discount
        return new_price

    @price.setter
    def price(self, value):
        self.original_price = value

    @price.deleter
    def price(self):
        del self.original_price

obj = Goods()
obj.price         # 获取商品价格
obj.price = 200   # 修改商品原价
del obj.price     # 删除商品原价

3.property属性的第二种使用方式:类属性方式

# property属性的第二种定义方式:类属性定义方式
class Goods(object):

    def get_price(self):
        print("get price...")
        return 100

    def set_price(self, value):
        """必须两个参数"""
        print("set price...")
        print(value)

    def del_price(self):
        print("del price")

    price = property(get_price, set_price, del_price, "相关描述...")

obj = Goods()

obj.price  # 自动调用第一个参数中定义的方法:get_price
obj.price = "价格"  # 自动调用第二个参数中定义的方法:set_price方法,并将“价格”当作参数传入
desc = Goods.price.__doc__  # 自动获取第四个参数中设置的值:"相关描述..."
print(desc)
del obj.price  # 自动调用第三个参数中定义的方法:del_price方法

property方法中有个四个参数

  • 第一个参数是方法名,调用 对象.属性 时自动触发执行方法
  • 第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法
  • 第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法
  • 第四个参数是字符串,调用 对象.属性.doc ,此参数是该属性的描述信息

类属性定义方式的案例

class Goods(object):

    def __init__(self):
        # 原价
        self.original_price = 100
        # 折扣
        self.discount = 0.8

    def get_price(self):
        # 实际价格 = 原价 * 折扣
        new_price = self.original_price * self.discount
        return new_price

    def set_price(self, value):
        self.original_price = value

    def del_price(self):
        del self.original_price

    PRICE = property(get_price, set_price, del_price, '价格属性描述...')

obj = Goods()
obj.PRICE          # 获取商品价格
obj.PRICE = 200    # 修改商品原价
del obj.PRICE      # 删除商品原价
Goods.PRICE.__doc__# 获取价格的描述信息

5、property属性-应用

5.1 私有属性添加getter和setter方法

class Money(object):
    def __init__(self):
        self.__money = 0

    def getMoney(self):
        return self.__money

    def setMoney(self, value):
        if value >= 0:
          self.__money = value
        else:
            print("error:请存放正确的数值")

5.2 使用property升级getter和setter方法

class Money(object):
    def __init__(self):
        self.__money = 0

    def getMoney(self):
        return self.__money

    def setMoney(self, value):
        if value >=0:
            self.__money = value
        else:
            print("error:数值不正确")

    # 定义一个属性,当对这个money设置值时调用setMoney,当获取值时调用getMoney
    money = property(getMoney, setMoney)  

a = Money()
a.money = 100  # 调用setMoney方法
print(a.money)  # 调用getMoney方法
#100

5.3 使用property取代getter和setter方法

  • 重新实现一个属性的设置和读取方法,可做边界判定
class Money(object):
    def __init__(self):
        self.__money = 0

    # 使用装饰器对money进行装饰,那么会自动添加一个叫money的属性,当调用获取money的值时,调用装饰的方法
    @property
    def money(self):
        return self.__money

    # 使用装饰器对money进行装饰,当对money设置值时,调用装饰的方法
    @money.setter
    def money(self, value):
        if value >= 0:
            self.__money = value
        else:
            print("error:不是整型数字")

a = Money()
a.money = 100
print(a.money)

6、魔法属性

无论人或事物往往都有不按套路出牌的情况,Python的类属性也是如此,存在着一些具有特殊含义的属性,详情如下:

6.1 __doc__

  • 表示类的描述信息
class Foo:
    """ 描述类信息,这是用于看片的神奇 """
    def func(self):
        pass

print(Foo.__doc__)
#输出:类的描述信息

6.2 __module____class__

  • module 表示当前操作的对象在那个模块
  • class 表示当前操作的对象的类是什么
test.py
# -*- coding:utf-8 -*-

class Person(object):
    def __init__(self):
        self.name = 'laowang'
main.py
from test import Person

obj = Person()
print(obj.__module__)  # 输出 test 即:输出模块
print(obj.__class__)  # 输出 test.Person 即:输出类

6.3 __dict__

  • 类或对象中的所有属性

类的实例属性属于对象;类中的类属性和方法等属于类,即:

class Province(object):

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

    def func(self, *args, **kwargs):
        print('func')

# 获取类的属性,即:类属性、方法、
print(Province.__dict__)
# 输出:{'__dict__': <attribute '__dict__' of 'Province' objects>, '__module__': '__main__', 'country': 'China', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Province' objects>, 'func': <function Province.func at 0x101897950>, '__init__': <function Province.__init__ at 0x1018978c8>}

obj1 = Province('山东', 10000)
print(obj1.__dict__)
# 获取 对象obj1 的属性
# 输出:{'count': 10000, 'name': '山东'}

obj2 = Province('山西', 20000)
print(obj2.__dict__)
# 获取 对象obj2 的属性
# 输出:{'count': 20000, 'name': '山西'}

6.4 __init__

  • 初始化方法,通过类创建对象时,自动触发执行
class Person:
    def __init__(self, name):
        self.name = name
        self.age = 18


obj = Person('laowang')  # 自动执行类中的 __init__ 方法

6.5 __del__

  • 当对象在内存中被释放时,自动触发执行。

注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,__del__的调用是由解释器在进行垃圾回收时自动触发执行的。

class Foo:
    def __del__(self):
        pass

6.6 __str__

  • 如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值。
class Foo:
    def __str__(self):
        return 'laowang'


obj = Foo()
print(obj)
# 输出:laowang

6.7 __call__(了解)

  • 对象后面加括号,触发执行。

注:__init__方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 call 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

class Foo:
    def __init__(self):
        pass

    def __call__(self, *args, **kwargs):
        print('__call__')


obj = Foo()  # 执行 __init__
obj()  # 执行 __call__

6.8__getitem____setitem____delitem__(了解)

  • 用于索引操作,如字典。以上分别表示获取、设置、删除数据
# -*- coding:utf-8 -*-

class Foo(object):

    def __getitem__(self, key):
        print('__getitem__', key)

    def __setitem__(self, key, value):
        print('__setitem__', key, value)

    def __delitem__(self, key):
        print('__delitem__', key)


obj = Foo()

result = obj['k1']      # 自动触发执行 __getitem__
obj['k2'] = 'laowang'   # 自动触发执行 __setitem__
del obj['k1']           # 自动触发执行 __delitem__

6.9__getslice____setslice____delslice__(了解)

  • 该三个方法用于分片操作,如:列表
# -*- coding:utf-8 -*-

class Foo(object):

    def __getslice__(self, i, j):
        print('__getslice__', i, j)

    def __setslice__(self, i, j, sequence):
        print('__setslice__', i, j)

    def __delslice__(self, i, j):
        print('__delslice__', i, j)

obj = Foo()

obj[-1:1]                   # 自动触发执行 __getslice__
obj[0:1] = [11,22,33,44]    # 自动触发执行 __setslice__
del obj[0:2]                # 自动触发执行 __delslice__

7、with关键字和上下文管理器

7.1with关键字的使用

​ 在我们日常使用场景中,经常会操作一些资源,比如文件对象、数据库连接、Socket连接等,资源操作完了之后,不管操作的成功与否,最重要的事情就是关闭该资源,否则资源打开太多而没有关闭,程序会报错,以文件操作为例,通常我们会这样写:

f = open('file.txt', 'w')
try:
    f.write("Hello")
finally:
    f.close()

但既然close方法是必须的操作,那就没必要显式地调用,所以Python给我们提供了一种更优雅的方式,使用with语句:

with open('file.txt', 'w') as f:
    f.write("Hello")

在退出with语句下的代码块之后,f 对象会自动执行自己的close方法,实现资源的释放,简洁优雅。

事实上,上面一段代码就用到了上下文管理器的知识。

某种程度上,上下文管理器可以理解成try/finally的优化,使得代码更加易读,在通常情况下,我们读取文件的时候,如果不适用with语句,为了防止出错,可以采用try/finally的语句来进行读取,使得文件可以正常执行close()方法。

f = open('file.text', 'w'):
    try:
        f.write('hello')
    finally:
        f.close()

很明显,with语句比try/finally更易读,更友好。

7.2上下文管理器

上下文管理器协议,是指要实现对象的 enter() 和 exit() 方法。

上下文管理器也就是支持上下文管理器协议的对象,也就是实现了 enter() 和 exit() 方法。

上下文管理器 是一个对象,它定义了在执行 with 语句时要建立的运行时上下文。 上下文管理器处理进入和退出所需运行时上下文以执行代码块。 通常使用 with 语句来使用,但是也可以通过直接调用它们的方法来使用。

简单来说,我们定义一个上下文管理器,需要在一个类里面一个实现__enter__(self)__exit__(self, exc_type, exc_value, traceback) 方法。

通常情况下,我们会使用with语句来使用上下文管理器:

with context_expr [as var]:    
    with_body

配合with语句使用的时候,上下文管理器会自动调用__enter__方法,然后进入运行时上下文环境,如果有as 从句,返回自身或另一个与运行时上下文相关的对象,值赋值给var。当with_body执行完毕退出with语句块或者with_body代码块出现异常,则会自动执行__exit__方法,并且会把对于的异常参数传递进来。如果__exit__函数返回True。则with语句代码块不会显示的抛出异常,终止程序,如果返回None或者False,异常会被主动raise,并终止程序。三 上下文管理器的运用场景

上下文管理器的典型用法包括保存和恢复各种全局状态,锁定和解锁资源,关闭打开的文件等。

7.3enter和exit方法说明

  1. enter方法说明 上下文管理器的enter方法是可以带返回值的,默认返回None,这个返回值通过with...as...中的 as 赋给它后面的那个变量

当然with...as...并非固定组合,单独使用with...也是可以的,上下文管理器的enter方法还是正常执行,只是这个返回值并没有赋给一个变量,with下面的代码块也不能使用这个返回值。

  1. exit方法说明 上下文管理器的exit方法接收3个参数exc_type、exc_val、exc_tb,如果代码块BLOCK发生了异常e并退出,这3个参数分别为type(e)、str(e)、e.traceback,否则都为None。

同样exit方法也是可以带返回值的,这个返回值应该是一个布尔类型True或False,默认为None(即False)。如果为False,异常会被抛出,用户需要进行异常处理。如果为True,则表示忽略该异常。

class abc(object):
    def __enter__(self):
        # 进入with 调用
        pass
    def __exit__(self, exc_type, exc_val, exc_tb):
        # 离开with 调用
        # 异常类型,异常值,异常追踪
        print(exc_type)
        print(exc_val)
        print(exc_tb)

with abc() as a:
    print("enter")
    b = 1 / 0
    print("exit")

7.4资源的创建和释放场景

​ 上下文管理器的典型用法包括保存和恢复各种全局状态,锁定和解锁资源,关闭打开的文件等。

​ 模拟一下Python中的open函数的实现过程

class File():

    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode

    def __enter__(self):
        print("entering")
        self.f = open(self.filename, self.mode)
        return self.f

    def __exit__(self, *args):
        print("will exit")
        self.f.close()
    
    
with File('out.txt', 'w') as f:  # f用来接收enter方法返回的对象
    print("writing")
    f.write('hello, python')

结语

文章篇幅较长,给看到这里的小伙伴点个大大的赞!由于作者水平有限,文章中难免会有错误之处,欢迎小伙伴们反馈指正。

如果觉得文章对你有帮助,麻烦 点赞、评论、收藏

你的支持是我最大的动力!!!