面向对象编程进阶版

Posted kuxingseng95

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面向对象编程进阶版相关的知识,希望对你有一定的参考价值。

isinstance(obj, cls)和issubclass(sub, super)

isinstance(obj,cls)检查obj是否是类 cls 的实例化对象

issubclass(sub, super)检查sub类是否是 super 类的派生类

技术分享图片
# isinstance(obj,cls)检查obj是否是类 cls 的实例化对象

class Foo(object):
    pass

class Foo1(Foo):
    pass

obj = Foo1()
print(isinstance(obj, Foo1))    # True
print(isinstance(obj, Foo))     # True


# issubclass(sub, super)检查sub类是否是 super 类的派生类
class Foo(object):
    pass

class Bar(Foo):
    pass

print(issubclass(Bar, Foo))     # True
View Code

反射

反射的概念

反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。

python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)

四个可以实现自省的函数

下面有四个可以实现自省的函数

技术分享图片
# 1.hasattr(object, name)
#   判断object中有没有一个name字符串对应的方法或属性
# 2.getattr(object, name[, default])
#   通过object.name获取参数,如果设置第三个参数,获取的属性不存在时,显示第三个参数中的字符串,否则报错
# 3.setattr(x, y, v)
#   设置参数,三个参数都需要有,否则报错,
#   x为对象名,y为新加入的方法或者属性的一个名字,x为一个函数或者一个符合python基本数据类型的值。
# 4.delattr(x, y)
#   删除属性,不存在则报错。

# 综合例子:
class Student(object):
    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex

    def show_name(self):
        print(self.name)

    def show_age(self):
        print(self.age)

laowang = Student("老王", "18", "")

# 判断实例化对象中是否有某属性
print(hasattr(laowang, "name"))         # True
print(hasattr(laowang, "show_name"))    # True
print(hasattr(laowang, "aaaaa"))        # False

# 获取属性
print(getattr(laowang, "name"))     # 老王
print(getattr(laowang, "show_name"))        # <bound method Student.show_name of <__main__.Student object at 0x0000020D1E0E7358>>
print(getattr(laowang, "aaaaaa", "并没有"))   # 并没有

# 设置属性
setattr(laowang, "name", "老李")
print(getattr(laowang, "name"))     # 老李
laowang.show_name()         # 老李
setattr(laowang, "show_sex", lambda self: print(self.sex))
laowang.show_sex(laowang)       #
print(laowang.__dict__)         # {‘age‘: ‘18‘, ‘show_sex‘: <function <lambda> at 0x0000016DE9A302F0>, ‘sex‘: ‘男‘, ‘name‘: ‘老李‘}

# 删除属性
delattr(laowang, "age")
print(getattr(laowang, "age"))  # 报错,AttributeError: ‘Student‘ object has no attribute ‘age‘

# 试试类行不行。
print(hasattr(Student, "name"))     # False
print(hasattr(Student, "show_name"))    # True
delattr(Student, "show_name")
laoli = Student("laoli", 20 , "")
print(hasattr(laoli, "show_name"))      # False
laoli.show_name()       # 报错, AttributeError: ‘Student‘ object has no attribute ‘show_name‘

# 那么就可以给出结果了,这四种自省方式对类也有效。说明类也是对象。
View Code

反射的用法

用法一:实现可插拔机制

可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能

比如:

技术分享图片
class FtpClient:
    ftp客户端,但是还么有实现具体的功能
    def __init__(self,addr):
        print(正在连接服务器[%s] %addr)
        self.addr=addr
程序员A还没有完成的详细的功能
技术分享图片
from module import FtpClient

f1 = FtpClient(192.168.1.1)
if hasattr(f1, get):
    func_get = getattr(f1, get)
    func_get()
else:
    print(---->不存在此方法)
    print(处理其他的逻辑)
程序员B需要程序员A的相应功能,使用反射而不受影响

 用法二:动态导入模块

通常我们导入模块的时候用的是import。现在有了一个需求就是我动态的输入一个模块名,可以随时访问到导入模块中的方法或者变量。

技术分享图片
# 当使用import导入Python模块的时候,默认调用的是__import__()函数。直接使用该函数的情况很少见,一般用于动态加载模块。
# __import__的语法:__import__(name[, globals[, locals[, fromlist[, level]]]])
# 我们只需要知道:
# __import__("module")相当于import module
# __import__("package.module")相当于from package import name

# 坑点:
package = __import__("package")
print(package)   # <module ‘package‘ from ‘D:\\workspace\\modue\\package\\__init__.py‘>
A1 = __import__("package.A")
print(A1)        # <module ‘package‘ from ‘D:\\workspace\\modue\\package\\__init__.py‘>
A2 = __import__("package.A", fromlist=True)
print(A2)        # <module ‘package.A‘ from ‘D:\\workspace\\modue\\package\\A.py‘>
A3 = __import__("package.A", fromlist=[])
print(A3)        # <module ‘package‘ from ‘D:\\workspace\\modue\\package\\__init__.py‘>
A4 = __import__("package.A", fromlist=["A", "B"])
print(A4)        # <module ‘package.A‘ from ‘D:\\workspace\\modue\\package\\A.py‘>
B1 = __import__("package.B", fromlist=["A"])
print(B1)        # <module ‘package.B‘ from ‘D:\\workspace\\modue\\package\\B.py‘>

# 关于__import__的坑很多,网上也有很多错误的例子。
# 上面用五个例子来测试了一下,下一个小结论吧:当你用__import__导一个子模块,也就是有"."的时候,最好加上参数"fromlist=True"
关于__import__
技术分享图片
imp = input("请输入模块:")
dd = __import__(imp)
# 等价于import imp
inp_func = input("请输入要执行的函数:")

f = getattr(dd,inp_func,None)#作用:从导入模块中找到你需要调用的函数inp_func,然后返回一个该函数的引用.没有找到就返回None

f() # 执行该函数
动态导入例子

__getattr__,__setattr__,__delattr__

这三个内置函数是类的实例化对象才可以触发的。__getattr__:找不到对应属性的时候触发;__setattr__:设置属性的时候触发;__delattr__删除属性时触发。

技术分享图片
class Foo:
    x = 1

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

    def __getattr__(self, item):
        print(----> from getattr:你找的属性不存在)

    def __setattr__(self, key, value):
        print(----> from setattr)

    def __delattr__(self, item):
        print(----> from delattr)


f1 = Foo(10)    # ----> from setattr
f1.z = 10       # ----> from setattr
f1.aaa          # ----> from getattr:你找的属性不存在
del f1.x        # ----> from delattr
例子

 我们现在知道这几个内置函数的作用了,但是有这样一个问题。我们重写了这个几个内置的函数,也就是说,当我们设置,删除属性的时候并没有进行实际的操作,按照例子中的做法,就是显示了几个打印语句,并没有真正的执行设置和删除的操作。所以接下来进行修改。

技术分享图片
class Foo:
    x = 1

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

    def __getattr__(self, item):
        print(----> from getattr:你找的属性不存在)

    def __setattr__(self, key, value):
        print(----> from setattr)
        # self.key=value # 这里需要说明一下,如果我们使用"."这样的方式来设置属性,就会无限的调用这个__setattr__方法。所以我们要使用下面的方法。
        self.__dict__[key] = value

    def __delattr__(self, item):
        print(----> from delattr)
        # del self.item # 这个和设置的时候一样,也会陷入无限递归中,正确做法是下面的方式。
        self.__dict__.pop(item)


f1 = Foo(10)
f1.z = 10
print(f1.z)
del f1.z
print(f1.z)

"""
输出结果:
----> from setattr
----> from setattr
10
----> from delattr
----> from getattr:你找的属性不存在
None
"""
修改后的例子

 说明:__setattr__()方法中最好不要对__init__()方法中的属性进行操作,因为那个时候init还没有加载完成,属性是不存在的。

__getattribute__

这个函数和上面说的__getattr__很像,是不是有些关系啊?答案是肯定的。在说这个之前,我们需要搞懂python实例化对象查找属性的顺序。

  1. 首先访问 __getattribute__() 魔法方法(隐含默认调用,无论何种情况,均会调用此方法)
  2. 去实例对象,比如:t中查找是否具备该属性: t.__dict__ 中查找,每个类和实例对象都有一个 __dict__ 的属性
  3. 若在 t.__dict__ 中找不到对应的属性, 则去该实例的类中寻找,即 t.__class__.__dict__
  4. 若在实例的类中也招不到该属性,则去父类中寻找,即 t.__class__.__bases__.__dict__中寻找
  5. 若以上均无法找到,则会调用 __getattr__ 方法,执行内部的命令(若未重载 __getattr__ 方法,则直接报错:AttributeError)

以上几个流程,即完成了属性的寻找,这个是默认的查找顺序,所以,当我们重写了__getattribute__()方法的时候,当找不到属性的时候,需要手动加入进入第五步的操作。

技术分享图片
class Foo:
    def __init__(self, x):
        self.x = x

    def __getattr__(self, item):
        print(执行的是我)
        # return self.__dict__[item]

    def __getattribute__(self, item):
        print("都得执行我")
        return super(Foo, self).__getattribute__(item)


f1 = Foo(10)
print(f1.x)
f1.xxxxxx

"""
输出结果:
都得执行我
10
都得执行我
执行的是我
"""

# 当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError则会执行__getattr__
# 如果想看看的话,可以用try..except,把AttributeError来拦截处理一下,看看会不会不调用__getattr__方法
例子

二次加工标准类型

包装

包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)

技术分享图片
class List(list):  # 继承list所有的属性,也可以派生出自己新的,比如append和mid
    def append(self, p_object):
         派生自己的append:加上类型检查
        if not isinstance(p_object, int):
            raise TypeError(must be int)
        super().append(p_object)

    @property
    def mid(self):
        新增自己的属性
        index = len(self) // 2
        return self[index]


l = List([1, 2, 3, 4])
print(l)  # [1, 2, 3, 4]
l.append(5)
print(l)  # [1, 2, 3, 4, 5]
# l.append(‘1111111‘) #报错,必须为int类型

print(l.mid)  # 3

# 其余的方法都继承list的
l.insert(0, -123)
print(l)  # # [-123, 1, 2, 3, 4, 5]
l.clear()
print(l)  # [-123, 1, 2, 3, 4, 5]
基于继承实现二次加工

授权

授权:授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。

实现授权的关键点就是覆盖__getattr__方法

技术分享图片
import time


class FileHandle:
    def __init__(self, filename, mode=r, encoding=utf-8):
        if b in mode:
            self.file = open(filename, mode)
        else:
            self.file = open(filename, mode, encoding=encoding)
        self.filename = filename
        self.mode = mode
        self.encoding = encoding

    def write(self, line):
        if b in self.mode:
            if not isinstance(line, bytes):
                raise TypeError(must be bytes)
        self.file.write(line)

    def __getattr__(self, item):
        return getattr(self.file, item)
        # 当对象调用FileHandler类不存在的方法时,会返回open()函数的item字符串对应的方法;

    def __str__(self):
        if b in self.mode:
            res = "<_io.BufferedReader name=‘%s‘>" % self.filename
        else:
            res = "<_io.TextIOWrapper name=‘%s‘ mode=‘%s‘ encoding=‘%s‘>" % (self.filename, self.mode, self.encoding)
        return res


f1 = FileHandle(b.txt, "wb")
# f1.write(‘你好啊啊啊啊啊‘) #自定制的write,不用在进行encode转成二进制去写了,简单,大气
f1.write(你好啊.encode(utf-8))
print(f1)
f1.close()
例子1
技术分享图片
class List:
    def __init__(self,seq):
        self.seq=seq

    def append(self, p_object):
         派生自己的append加上类型检查,覆盖原有的append
        if not isinstance(p_object,int):
            raise TypeError(must be int)
        self.seq.append(p_object)

    @property
    def mid(self):
        新增自己的方法
        index=len(self.seq)//2
        return self.seq[index]

    def __getattr__(self, item):
        return getattr(self.seq,item)

    def __str__(self):
        return str(self.seq)

l=List([1,2,3])
print(l)
l.append(4)
print(l)
# l.append(‘3333333‘) #报错,必须为int类型

print(l.mid)

#基于授权,获得insert方法
l.insert(0,-123)
print(l)
例子2

 描述符

描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议
__get__():调用一个属性时触发;__set__():为一个属性赋值时触发;__delete__():采用del删除属性时触发

 定义一个描述符

技术分享图片
class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符
    def __get__(self, instance, owner):
        pass
    def __set__(self, instance, value):
        pass
    def __delete__(self, instance):
        pass
定义一个描述符

描述符简单说明

技术分享图片
class Foo:
    def __get__(self, instance, owner):
        print(触发get)

    def __set__(self, instance, value):
        print(触发set)

    def __delete__(self, instance):
        print(触发delete)


# 包含这三个方法的新式类称为描述符,由这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法
f1 = Foo()
f1.name = 老王
print(f1.name)
del f1.name
引子

从上面的例子中,我们应该有一个疑问,定义这三个东西有什么用,怎样才能触发他们。

技术分享图片
# 描述符Str
class Str:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        print(Str调用...)

    def __set__(self, instance, value):
        print(Str设置...)

    def __delete__(self, instance):
        print(Str删除...)


class People:
    name = Str("name")


laowang = People()
laowang.name
laowang.name = "老李"
print(laowang.__dict__)
print(People.__dict__)
del laowang.name

print(type(laowang) == People)  # True,type(obj)其实是查看obj是由哪个类实例化来的
print(type(laowang).__dict__ == People.__dict__)  # True

"""
运行结果:

Str调用...
Str设置...
{}
{‘__dict__‘: <attribute ‘__dict__‘ of ‘People‘ objects>, ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘People‘ objects>, ‘__doc__‘: None, ‘name‘: <__main__.Str object at 0x000001DD86427358>, ‘__module__‘: ‘__main__‘}
Str删除...
True
True
"""
关于描述符的简单例子

到这里就可以给出一个说明了:描述符就是用来代理另一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)

描述符的分类

实现__get__方法的对象是非数据描述符,意味着在初始化之后它们只能被读取。

同时实现__get__和__set__的对象是数据描述符,意味着这种属性是可写的。

关于描述符的注意事项

  •  描述符本身应该定义成新式类,被代理的类也应该是新式类
  •  必须把描述符定义成这个类的类属性,不能为定义到构造函数中
  • 类中定义描述符它就是一个类属性,存在于类的属性字典中,而不是实例的属性字典
  •  要严格遵循该优先级,优先级由高到底分别是
    1. 类属性
    2. 数据描述属性
    3. 实例属性
    4. 非数据描述符
    5. 找不到的属性触发__getattr__()

关于这个优先级问题的例子说明

技术分享图片
class Str:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        print(Str调用...)

    def __set__(self, instance, value):
        print(Str设置...)

    def __delete__(self, instance):
        print(Str删除...)


class People:
    name = Str("name")

People.name
print(People.__dict__)
People.name = "老王"
print(People.__dict__)
del People

"""
运行结果:
Str调用...
{‘name‘: <__main__.Str object at 0x00000213478E72E8>, ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘People‘ objects>, ‘__module__‘: ‘__main__‘, ‘__doc__‘: None, ‘__dict__‘: <attribute ‘__dict__‘ of ‘People‘ objects>}
{‘name‘: ‘老王‘, ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘People‘ objects>, ‘__module__‘: ‘__main__‘, ‘__doc__‘: None, ‘__dict__‘: <attribute ‘__dict__‘ of ‘People‘ objects>}
"""

"""
说明:从上面的代码可以看出来,只有在People.name的时候触发了描述符的__get__()方法
这是因为类属性比描述符更高的优先级,当调用People.name的时候,先找类中的属性,类中的属性并没有,所以去描述符中查找了。
而赋值的时候类的优先级比描述符高,所以,相当于是覆盖了描述符的赋值,也就不会触发描述符的__set__()方法, del同理。
"""
类属性>数据描述符
技术分享图片
class Str:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        print(Str调用...)

    def __set__(self, instance, value):
        print(Str设置...)

    def __delete__(self, instance):
        print(Str删除...)


class People:
    name = Str("name")


laowang = People()
laowang.name = "老王"
laowang.name
del laowang.name

"""
运行结果:
Str设置...
Str调用...
Str删除...
"""

"""
说明:如果描述符是一个数据描述符(即有__get__又有__set__),那么p1.name的调用与赋值都是触发描述符的操作,
于p1本身无关了,相当于覆盖了实例的属性
"""
数据描述符>实例属性
技术分享图片
class Foo:
    def func(self):
        print(我胡汉三又回来了)


f1 = Foo()
f1.func()  # 调用类的方法,也可以说是调用非数据描述符
# 函数是一个非数据描述符对象(一切皆对象么)
print(dir(Foo.func))
print(hasattr(Foo.func, __set__))
print(hasattr(Foo.func, __get__))
print(hasattr(Foo.func, __delete__))

"""
运行结果:
我胡汉三又回来了
[‘__annotations__‘, ‘__call__‘, ‘__class__‘, ‘__closure__‘, ‘__code__‘, ‘__defaults__‘, ‘__delattr__‘, ‘__dict__‘, ‘__dir__‘, ‘__doc__‘, ‘__eq__‘, ‘__format__‘, ‘__ge__‘, ‘__get__‘, ‘__getattribute__‘, ‘__globals__‘, ‘__gt__‘, ‘__hash__‘, ‘__init__‘, ‘__kwdefaults__‘, ‘__le__‘, ‘__lt__‘, ‘__module__‘, ‘__name__‘, ‘__ne__‘, ‘__new__‘, ‘__qualname__‘, ‘__reduce__‘, ‘__reduce_ex__‘, ‘__repr__‘, ‘__setattr__‘, ‘__sizeof__‘, ‘__str__‘, ‘__subclasshook__‘]
False
True
False
"""
"""
说明:
有人可能会问,描述符不都是类么,函数怎么算也应该是一个对象啊,怎么就是描述符了
描述符是类没问题,描述符在应用的时候不都是实例化成一个类属性么
函数就是一个由非描述符类实例化得到的对象
没错,字符串也一样
"""
实例属性>非数据描述符
技术分享图片
class Str:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        print(Str调用...)



class People:
    name = Str("name")

laowang = People()
laowang.name = "老王"
print(laowang.name)

"""
结果:
老王
"""
"""
说明:name是一个非数据描述符,因为描述符没用实现set方法,是一个非数据描述符
所以对实例属性的操作,触发的都是实例自己的。

"""
对实例属性>非数据描述符的再次说明

描述符的使用

python是个弱类型语言,对参数的赋值没有类型的限制,所以我们就可以通过描述符机制来实现类型的限制。

在一开始给出了描述符简单例子,我们接下来就是对这个简单的例子进行丰富。

技术分享图片
class Typed:
    def __init__(self, name, expected_type):
        self.name = name
        self.expected_type = expected_type

    def __get__(self, instance, owner):
        print(get--->, instance, owner)
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print(set--->, instance, value)
        if not isinstance(value, self.expected_type):
            raise TypeError(Expected %s % str(self.expected_type))
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        print(delete--->, instance)
        instance.__dict__.pop(self.name)


class People:
    name = Typed(name, str)
    age = Typed(name, int)
    salary = Typed(name, float)

    def __init__(self, name, age, salary):
        self.name = name
        self.age = age
        self.salary = salary


# p1=People(123,18,3333.3)
# p1=People(‘egon‘,‘18‘,3333.3)
p1 = People(egon, 18, 3333.3)
简单例子修改版

关于这个类的属性,如果我们不想在定义的时候写一大堆,可以用装饰器。

技术分享图片
class Typed:
    def __init__(self, name, expected_type):
        self.name = name
        self.expected_type = expected_type

    def __get__(self, instance, owner):
        print(get--->, instance, owner)
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print(set--->, instance, value)
        if not isinstance(value, self.expected_type):
            raise TypeError(Expected %s % str(self.expected_type))
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        print(delete--->, instance)
        instance.__dict__.pop(self.name)


def typeassert(**kwargs):
    def decorate(cls):
        print(类的装饰器开始运行啦------>, kwargs)
        for name, expected_type in kwargs.items():
            setattr(cls, name, Typed(name, expected_type))
        return cls

    return decorate


@typeassert(name=str, age=int,
            salary=float)  # 有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People)
class People:
    def __init__(self, name, age, salary):
        self.name = name
        self.age = age
        self.salary = salary


print(People.__dict__)
p1 = People(老王, 18, 3333.3)
简单例子修饰器修改版

关于给类装饰的说明会放在单独的修饰器进阶用法中

总结:

描述符是可以实现大部分python类特性中底层函数,包括@classmethod,@staticmethd,@property甚至是__slots__属性

描述父是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件。

修饰器进阶用法

在平时我们习惯于给函数加上装饰器,其实装饰器不仅可以装饰类,装饰器本身也可以是类。要明白这些,首先要了解两个概念

  1. python中一切皆对象
  2. def Out(func):
        def wrapper(*args, **kwargs):
            res = func(*args, **kwargs)
            return res
    
        return wrapper
    
    
    @Out  # 相当于A = Out(A)
    def A():
        pass 

修饰类

技术分享图片
def Out(**kwargs):
    def wrapper(obj):
        for key, val in kwargs.items():
            # obj.key = val    # 错误写法,这样A.__dict__结果为:{‘__doc__‘: None, ‘key‘: 2, ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘A‘ objects>, ‘__dict__‘: <attribute ‘__dict__‘ of ‘A‘ objects>, ‘__module__‘: ‘__main__‘}
            # obj.__dict__[key] = val     # 报错
            setattr(obj, key, val)   # {‘__weakref__‘: <attribute ‘__weakref__‘ of ‘A‘ objects>, ‘y‘: 2, ‘__module__‘: ‘__main__‘, ‘__doc__‘: None, ‘__dict__‘: <attribute ‘__dict__‘ of ‘A‘ objects>, ‘z‘: 3, ‘x‘: 1}
        return obj

    return wrapper


@Out(x=1, y=2, z=3)  # 相当于A = Out(A)
class A:
    pass


print(A.__dict__)  # {‘x‘: 1, ‘y‘: 2, ‘z‘: 3}
装饰器修饰类

类作为修饰器

关于类作为修饰符,直接放到例子里来说明。

技术分享图片
class Myproperty:
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner): # instance如果是实例调用它就是实例化对象,如果是类调用,是None,owner始终是不变的,就是调用的那个类。
        print(这是我们自己定制的静态属性,r1.area实际是要执行r1.area())
        if instance is None:        
            return self
        else:
            print(--->)
            value = self.func(instance)
            setattr(instance, self.func.__name__, value)  # 计算一次就缓存到实例的属性字典中
            return value


class Room:
    def __init__(self, name, width, length):
        self.name = name
        self.width = width
        self.length = length

    @Myproperty  # area=Myproperty(area) 相当于‘定义了一个类属性,即描述符‘
    def area(self):
        return self.width * self.length


r1 = Room(老王, 1, 1)
print(r1.area)  # 先从自己的属性字典找,没有再去类的中找,然后出发了area的__get__方法
print(r1.area)  # 先从自己的属性字典找,找到了,是上次计算的结果,这样就不用每执行一次都去计算

"""
说明:
在自定义的装饰类中,要实现自定义property的话,不能加__set__, 因为加了的话这个类就相当于是一个数据描述符,
这样我们的缓存就起不到作用了,因为数据描述符的优先级大于实例的数据优先级,所以它就不会到自己的__dict__中查找,
而是到数据描述符中查找。

"""
装饰器配合描述符做出自定义property

拓展:

技术分享图片
class ClassMethod:
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner):  # 类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身,
        def feedback():
            print(在这里可以加功能啊...)
            return self.func(owner)

        return feedback


class People:
    name = 老王

    @ClassMethod  # say_hi=ClassMethod(say_hi)
    def say_hi(cls):
        print(你好啊,帅哥 %s % cls.name)


People.say_hi()

p1 = People()
p1.say_hi()

# 疑问,类方法如果有参数呢,好说,好说

# -----------------------------------修改版---------------------------------------------------------------------
class ClassMethod:
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner):  # 类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身,
        def feedback(*args, **kwargs):
            print(在这里可以加功能啊...)
            return self.func(owner, *args, **kwargs)

        return feedback


class People:
    name = 老王

    @ClassMethod  # say_hi=ClassMethod(say_hi)
    def say_hi(cls, msg):
        print(你好啊,帅哥 %s %s % (cls.name, msg))


People.say_hi(你是那偷心的贼)

p1 = People()
p1.say_hi(你是那偷心的贼)
自制@classmethod
技术分享图片
class StaticMethod:
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner):  # 类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身,
        def feedback(*args, **kwargs):
            print(在这里可以加功能啊...)
            return self.func(*args, **kwargs)

        return feedback


class People:
    @StaticMethod  # say_hi=StaticMethod(say_hi)
    def say_hi(x, y, z):
        print(------>, x, y, z)


People.say_hi(1, 2, 3)

p1 = People()
p1.say_hi(4, 5, 6)
自制@staticmethod

__setitem__,__getitem__,__delitem__

这三个很类似__setattr__,__delattr__,__getattr__这三个,其实触发条件没啥区别,他们之间最大的区别简单说就是:

__setitem__,__getitem__,__delitem__在用"[]"操作的时候触发,

__setattr__,__delattr__,__getattr__在用"."操作的时候触发。

技术分享图片
class Foo:
    def __init__(self, name):
        self.name = name

    def __getitem__(self, item):
        print(self.__dict__[item])

    def __setitem__(self, key, value):
        self.__dict__[key] = value

    def __delitem__(self, key):
        print(del obj[key]时,我执行)
        self.__dict__.pop(key)

    def __delattr__(self, item):
        print(del obj.key时,我执行)
        self.__dict__.pop(item)


f1 = Foo(老王)
f1[age] = 18
f1[age1] = 19
del f1.age1
del f1[age]
f1[name] = 老李
print(f1.__dict__)

"""
运行结果:
del obj.key时,我执行
del obj[key]时,我执行
{‘name‘: ‘老李‘}
"""
例子

__str__,__repr__,__format__

__str__和__repr__是改变对象的字符串显示

技术分享图片
>>> class Foo:
...     def __str__(self):
...         return "我是函数中的Foo"
...     def __repr__(self):
...         return "我是交互解释器中的Foo"
...
>>> f1 = Foo()
>>> print(f1)
我是函数中的Foo
>>> f1
我是交互解释器中的Foo

"""
str函数或者print函数--->obj.__str__()
repr或者交互式解释器--->obj.__repr__()
如果__str__没有被定义,那么就会使用__repr__来代替输出
注意:这俩方法的返回值必须是字符串,否则抛出异常
"""
__str__和__repr__

__format__是用来自定制格式化字符串的

技术分享图片
date_dic={
    ymd:{0.year}:{0.month}:{0.day},
    dmy:{0.day}/{0.month}/{0.year},
    mdy:{0.month}-{0.day}-{0.year},
}
class Date:
    def __init__(self,year,month,day):
        self.year=year
        self.month=month
        self.day=day

    def __format__(self, format_spec):
        if not format_spec or format_spec not in date_dic:
            format_spec=ymd
        fmt=date_dic[format_spec]
        return fmt.format(self)

d1=Date(2018,01,01)
print(format(d1))
print({:mdy}.format(d1))
__format__

__slots__

技术分享图片
‘‘‘
1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)
3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__
当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个
字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给
实例添加新的属性了,只能使用在__slots__中定义的那些属性名。
4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该
只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。
关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。           更多的是用来作为一个内存优化工具。

‘‘‘


class Foo:
    __slots__ = x


f1 = Foo()
f1.x = 1
# f1.y = 2  # 报错
print(f1.__slots__)  # f1不再有__dict__


class Bar:
    __slots__ = [x, y]


n = Bar()
n.x, n.y = 1, 2
# n.z = 3  # 报错

# __slots__的作用是限制创建。还有就是省内存,他的副作用更明显就是用它,所有关于它的__dict__的都搞不来了
__slots__

__next__和__iter__

这两个是迭代器协议中可以看到的两个,实现迭代器协议,对象必须有一个__next__()方法,执行该方法,要么返回迭代器中的下一项,要么就引起一个Stoplteration异常,用来终止迭代

迭代对象:实现了迭代器协议的对象,对象的内部定义了一个__iter__()方法。

技术分享图片
class Foo:
    def __init__(self, x):
        self.x = x

    def __iter__(self):
        return self

    def __next__(self):
        n = self.x
        self.x += 1
        return self.x


f = Foo(3)
for i in f:
    print(i)
简单例子
技术分享图片
class Fib:
    def __init__(self):
        self._a = 0
        self._b = 1

    def __iter__(self):
        return self

    def __next__(self):
        self._a, self._b = self._b, self._a + self._b
        return self._a


f1 = Fib()

print(f1.__next__())
print(next(f1))
print(next(f1))

for i in f1:
    if i > 100:
        break
    print(%s  % i, end=‘‘)
斐波那契数列

__doc__

类的描述信息

class Foo:
    我是描述信息
    pass

print(Foo.__doc__)

__module__和__class__

__module__ 表示当前操作的对象在那个模块

__class__     表示当前操作的对象的类是什么

from lib.aa import C

obj = C()
print obj.__module__  # 输出 lib.aa,即:输出模块
print obj.__class__      # 输出 lib.aa.C,即:输出类

__del__

析构方法,当对象在内存中被释放时,自动触发执行

如果产生的对象仅仅只是python程序级别的(用户级),那么无需定义__del__,如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了__del__

技术分享图片
# ---------------------例子1--------------------------------------
class Foo:

    def __del__(self):
        print(执行我啦)

f1=Foo()
del f1
print(------->)

"""
输出结果:
执行我啦
------->
"""
# ---------------------例子2--------------------------------------
class Foo:

    def __del__(self):
        print(执行我啦)

f1=Foo()
# del f1
print(------->)

"""
输出结果:
------->
执行我啦
"""
例子

典型应用场景:

创建数据库类,用该类实例化出数据库链接对象,对象本身是存放于用户空间内存中,而链接则是由操作系统管理的,存放于内核空间内存中

当程序结束时,python只会回收自己的内存空间,即用户态内存,而操作系统的资源则没有被回收,这就需要我们定制__del__,在对象被删除前向操作系统发起关闭数据库链接的系统调用,回收资源,这与文件处理是一个道理。

__enter__和__exit__

 引子:我们在操作文件的时候,可以这样写:

with open(a.txt) as f:
    代码块

这个叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象中声明__enter__和__exit__方法

技术分享图片
class Open:
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print(出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量)
        # return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(with中代码块执行完毕时执行我啊)


with Open(a.txt) as f:
    print(=====>执行代码块)
例如

__exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行

技术分享图片
class Open:
    def __init__(self,name):
        self.name=name

    def __enter__(self):
        print(出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量)

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(with中代码块执行完毕时执行我啊)
        print(exc_type)
        print(exc_val)
        print(exc_tb)



with Open(a.txt) as f:
    print(=====>执行代码块)
    raise AttributeError(***抛个错***)
print("**********************************")

"""
运行结果:
出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
=====>执行代码块
with中代码块执行完毕时执行我啊
<class ‘AttributeError‘>
***抛个错***
<traceback object at 0x000001EC64F58D48>
Traceback (most recent call last):
  File "D:/workspace/modue/demo.py", line 18, in <module>
    raise AttributeError(‘***抛个错***‘)
AttributeError: ***抛个错***
"""
比如

如果__exit__()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行

技术分享图片
class Open:
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print(出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量)

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(with中代码块执行完毕时执行我啊)
        print(exc_type)
        print(exc_val)
        print(exc_tb)
        return True

with Open(a.txt) as f:
    print(=====>执行代码块)
    raise AttributeError(***抛个错***)
print("**********************************")

"""
运行结果:
出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
=====>执行代码块
with中代码块执行完毕时执行我啊
<class ‘AttributeError‘>
***抛个错***
<traceback object at 0x00000202D33B8D48>
**********************************
"""
比如

例子:

技术分享图片
class Open:
    def __init__(self, filepath, mode=r, encoding=utf-8):
        self.filepath = filepath
        self.mode = mode
        self.encoding = encoding

    def __enter__(self):
        # print(‘enter‘)
        self.f = open(self.filepath, mode=self.mode, encoding=self.encoding)
        return self.f

    def __exit__(self, exc_type, exc_val, exc_tb):
        # print(‘exit‘)
        self.f.close()
        return True

    def __getattr__(self, item):
        return getattr(self.f, item)


with Open(a.txt, w) as f:
    print(f)            # <_io.TextIOWrapper name=‘a.txt‘ mode=‘w‘ encoding=‘utf-8‘>
    f.write(aaaaaa)
    f.wasdf  # 抛出异常,交给__exit__处理
模仿open打开文件

用处:

使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预。在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题。

__call__

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

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

技术分享图片
class Foo:
    def __init__(self):
        pass

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


obj = Foo()  # 执行 __init__
obj()  # 执行 __call__
Foo()()  # 执行 __call__
"""
运行结果:
__call__
__call__
"""
例如

metaclass

metaclass又被称为元类。要理解元类,还是要记住重复了很多次的那句,python中一切皆对象。

先看个例子:

class Foo(object):
    pass

f1 = Foo()
print(type(f1))     # <class ‘__main__.Foo‘>
print(type(Foo))    # <class ‘type‘>

可以看到查看Foo的类型的时候,它返回的时一个type类,证明调用了type这个元类产生的Foo。即默认的元类就是type。

这一系列关系可以理解为:

技术分享图片

所有我们可以认为,我们用的class关键字定义出来的类本身也是一个对象,负责生成这个对象的类称之为元类,内置的类为type。

我们创建实例化对象的时候是,比如:f1 = Foo(...)。那么用元类创建类的时候应该也差不多,比如说是:Foo = type(...)。那么现在的关键点来了,这个参数我们应该传什么。细想的话,我们创建一个实例化对象的时候,应该又这样三个部分:类名,基类,类的命名空间。

技术分享图片
class MyType(type):
    def __init__(self, a, b, c):
        print(第一步:元类的构造函数执行)
        print(a)
        print(b)
        print(c)

    def __call__(self, *args, **kwargs):
        print(第二步:当用()开始执行实例化对象时调用)
        print(self)
        print(args, kwargs)
        obj = object.__new__(self)  # object.__new__(Foo)-->f1,用object的__new__方法创建一个空的Foo的实例化对象f1
        self.__init__(obj, *args, **kwargs)  # Foo.__init__(f1,*arg,**kwargs)   # 调用Foo的初始化方法。
        return obj


class Foo(object,
          metaclass=MyType):  # Foo=MyType(Foo,‘Foo‘,(object,),{..})---》__init__,这里指定了元类,所以执行到这里会跳到自定义元类中的__init__的类初始化。
    def __init__(self, name):
        print("第三步:给新建实例化对象初始化")
        self.name = name  # f1.name=name


print(Foo)
f1 = Foo(老王)  # f1获得自定义元类中__call__返回的初始化完的对象
print("第四步:查看实例化好的对象")
print(f1)

"""
运行结果:
第一步:元类的构造函数执行
Foo
(<class ‘object‘>,)
{‘__module__‘: ‘__main__‘, ‘__init__‘: <function Foo.__init__ at 0x0000020309F7F598>, ‘__qualname__‘: ‘Foo‘}
<class ‘__main__.Foo‘>
第二步:当用()开始执行实例化对象时调用
<class ‘__main__.Foo‘>
(‘老王‘,) {}
第三步:给新建实例化对象初始化
第四步:查看实例化好的对象
<__main__.Foo object at 0x0000020309F84160>
"""
自定义元类实现过程及原理

注意:

  • 只有继承了type类才能称为元类,否则就是一个普通的自定义类
  • 要记住__call__方法不能缺,要不无法实例化。
  • 现在了解了元类,那么属性查找就需要分为两层了,一个是对象层(基于c3算法的MRO的查找),一个是元类层。
  • object类中默认有一个__new__,而我们在元类中也可以用object.__new__(self)去造对象。
    • 推荐的话在__call__中使用self.__new__(self)去创造空对象。这样会根据属性查找顺序去找__new__,而object.__new__则直接跳过了他们的子类。

关于元类的例子

技术分享图片
class Mymetaclass(type):
    def __new__(cls, name, bases, attrs):
        update_attrs = {}
        for k, v in attrs.items():
            if not callable(v) and not k.startswith(__):
                update_attrs[k.upper()] = v
            else:
                update_attrs[k] = v
        return type.__new__(cls, name, bases, update_attrs)


class Chinese(metaclass=Mymetaclass):
    country = China
    tag = Legend of the Dragon  # 龙的传人

    def walk(self):
        print(%s is walking % self.name)


print(Chinese.__dict__)
‘‘‘
{‘__module__‘: ‘__main__‘,
 ‘COUNTRY‘: ‘China‘, 
 ‘TAG‘: ‘Legend of the Dragon‘,
 ‘walk‘: <function Chinese.walk at 0x0000000001E7B950>,
 ‘__dict__‘: <attribute ‘__dict__‘ of ‘Chinese‘ objects>,                                         
 ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘Chinese‘ objects>,
 ‘__doc__‘: None}
‘‘‘
在元类中控制把自定义类的数据属性都变成大写
技术分享图片
class Mymetaclass(type):
    # def __new__(cls,name,bases,attrs):
    #     update_attrs={}
    #     for k,v in attrs.items():
    #         if not callable(v) and not k.startswith(‘__‘):
    #             update_attrs[k.upper()]=v
    #         else:
    #             update_attrs[k]=v
    #     return type.__new__(cls,name,bases,update_attrs)

    def __call__(self, *args, **kwargs):
        if args:
            raise TypeError(must use keyword argument for key function)
        obj = object.__new__(self)  # 创建对象,self为类Foo

        for k, v in kwargs.items():
            obj.__dict__[k.upper()] = v
        return obj


class Chinese(metaclass=Mymetaclass):
    country = China
    tag = Legend of the Dragon  # 龙的传人

    def walk(self):
        print(%s is walking % self.name)


p = Chinese(name=老王, age=18, sex=male)
print(p.__dict__)  # {‘SEX‘: ‘male‘, ‘NAME‘: ‘老王‘, ‘AGE‘: 18}
在元类中控制自定义的类无需__init__方法
技术分享图片
# 单例:即单个实例,指的是同一个类实例化多次的结果指向同一个对象,用于节省内存空间
# 如果我们从配置文件中读取配置来进行实例化,在配置相同的情况下,就没必要重复产生对象浪费内存了
# settings.py文件内容如下
HOST = 1.1.1.1
PORT = 3306

# 方式一:定义一个类方法实现单例模式
import settings


class mysql:
    __instance = None

    def __init__(self, host, port):
        self.host = host
        self.port = port

    @classmethod
    def singleton(cls):
        if not cls.__instance:
            cls.__instance = cls(settings.HOST, settings.PORT)
        return cls.__instance


obj1 = Mysql(1.1.1.2, 3306)
obj2 = Mysql(1.1.1.3, 3307)
print(obj1 is obj2)  # False

obj3 = Mysql.singleton()
obj4 = Mysql.singleton()
print(obj3 is obj4)  # True

# 方式二:定制元类实现单例模式
import settings


class Mymeta(type):
    def __init__(self, name, bases, dic):  # 定义类Mysql时就触发

        # 事先先从配置文件中取配置来造一个Mysql的实例出来
        self.__instance = object.__new__(self)  # 产生对象
        self.__init__(self.__instance, settings.HOST, settings.PORT)  # 初始化对象
        # 上述两步可以合成下面一步
        # self.__instance=super().__call__(*args,**kwargs)


        super().__init__(name, bases, dic)

    def __call__(self, *args, **kwargs):  # Mysql(...)时触发
        if args or kwargs:  # args或kwargs内有值
            obj = object.__new__(self)
            self.__init__(obj, *args, **kwargs)
            return obj

        return self.__instance


class Mysql(metaclass=Mymeta):
    def __init__(self, host, port):
        self.host = host
        self.port = port


obj1 = Mysql()  # 没有传值则默认从配置文件中读配置来实例化,所有的实例应该指向一个内存地址
obj2 = Mysql()
obj3 = Mysql()

print(obj1 is obj2 is obj3)

obj4 = Mysql(1.1.1.4, 3307)

# 方式三:定义一个装饰器实现单例模式
import settings


def singleton(cls):  # cls=Mysql
    _instance = cls(settings.HOST, settings.PORT)

    def wrapper(*args, **kwargs):
        if args or kwargs:
            obj = cls(*args, **kwargs)
            return obj
        return _instance

    return wrapper


@singleton  # Mysql=singleton(Mysql)
class Mysql:
    def __init__(self, host, port):
        self.host = host
        self.port = port


obj1 = Mysql()
obj2 = Mysql()
obj3 = Mysql()
print(obj1 is obj2 is obj3)  # True

obj4 = Mysql(1.1.1.3, 3307)
obj5 = Mysql(1.1.1.4, 3308)
print(obj3 is obj4)  # False
基于元类实现单例模式

 


以上是关于面向对象编程进阶版的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段——JS中的面向对象编程

VSCode自定义代码片段9——JS中的面向对象编程

Python 进阶指南(编程轻松进阶):十六面向对象编程和继承

Python基础-week06 面向对象编程进阶

Python7 - 面向对象编程进阶

进阶学Python:Python面向对象基础!