面向对象的魔法方法,元类

一、魔法方法

魔法方法:类中定义的双下方法都称为魔法方法。
不需要人为调用,在特定的条件下会自动触发运行

常见的魔法方法:

1. __init__方法:对象添加独有数据的时候自动触发

class C(object):
    def __init__(self,name):
        self.name = name    # 给对象添加独有数据时自动触发
        print('__init__')    # __init__
obj = C(123) # 类名加括号

 

2. __str__方法:对象被执行打印操作的时候自动触发

类中没有__str__方法,打印对象得到的是内存地址:
class C(object):
    def __init__(self,name):
        self.name = name
        print('__init__')    # __init__


obj = C(123)
print(obj)    # <__main__.C object at 0x00000149D9903A30>

类中有__str__方法,不执行打印操作不会触发:

class C(object):
    def __init__(self,name):
        self.name = name
        print('__init__')   # __init__
  def __str__(self): 
     return '哈哈哈'

obj
= C(123)

类中有__str__方法,执行打印操作时才会触发:

class C(object):
    def __init__(self,name):
        self.name = name
        print('__init__')

    def __str__(self):
        return '哈哈哈'  # 返回值必须为字符串类型,不然会报错

obj = C(123)
print(obj)    # 哈哈哈

注意__str__的返回值,只能是字符串类型,且不能返回对象本身: 

class C(object):
    def __init__(self,name):
        self.name = name
        print('__init__')

    def __str__(self):
        return 123    # TypeError: __str__ returned non-string (type int)

obj = C(123)
print(obj)


class C(object):
    def __init__(self,name):
        self.name = name
        print('__init__')

    def __str__(self):
        return f'{self}说:哈哈哈'    # RecursionError: maximum recursion depth exceeded while calling a Python object

obj = C(123)
print(obj)

 

3.__call__方法:对象加括号调用的时候自动触发

class C(object):

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

obj = C()
obj()   # 对象加括号执行__call__

可以添加返回值,返回什么,则对象加括号就能接收到什么:

class C(object):

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

obj = C()
res = obj()   # 对象加括号执行__call__
print(res)    # 123

我们给对象传入几个参数:

class C(object):

    def __call__(self,*args, **kwargs):
        print('__call__')    # __call__
        print(args,kwargs)    # (123, 'abc') {'name': 'alex'}

obj = C()
res = obj(123,'abc', name = 'alex')    

说明:*args用来接收多余的位置参数组成元组,**kwargs用来接受多余的关键字参数组成字典

 

4.__getattr__方法:对象在查找不存在的名字的时候会自动触发

class C(object):

    def __getattr__(self, item):
        print('__getattr__')    # __getattr__      

obj = C()
obj.age   # 对象查找不存在的名字自动触发

返回什么,就会接收到什么:

class C(object):

    def __getattr__(self, item):
        print('__getattr__')    # __getattr__
        return f'抱歉,您所要的{item}不存在'

obj = C()
print(obj.age)    # 抱歉,您所要的age不存在

 

5.__getattribute__方法

   对象查找名字就会自动触发,有它的存在就不会执行上面的__getattr__,很少用。

 

6.__setattr__方法:给对象添加或者修改数据的时候自动触发

     对象.名字 = 值

class C(object):

    def __setattr__(self, key, value):
        print('__setattr__')    # __setattr__
        print(key,value)   # key与value分别接收变量名和数据值  name alex

obj = C()
obj.name = 'alex'    # 给对象添加数据值自动触发

 

7.__enter__方法:当对象被当做with上下文管理操作的开始自动触发

  该方法返回什么,as后面的变量名就会接收到什么

8. __exit__方法:with上下文管理语法运行完毕之后自动触发(子代码结束)

class C(object):

    """当对象被当做with上下文管理操作的开始自动触发"""
    def __enter__(self):
        return 123    # 该方法返回什么,as后面的变量名就会接收到什么

    """with上下文管理语法运行完毕之后自动触发(子代码结束)"""
    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

obj = C()
with obj as f:
    print(f)   # 123

 

魔法方法笔试题

笔试题1.补全下列代码使得运行不报错即可

class Context:
        pass
    with Context() as f:
        f.do_something()

代码补全:

class Context:
    
    def do_something(self):
        pass
    """with上下文开始"""
    def __enter__(self):
        return self


    """with上下文结束"""
    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

with Context() as f:
    f.do_something()

思路:

1. with上下文管理,要有__enter__与__exit__方法

2. __enter__返回什么,as后面的变量名就会接收到什么,所以__enter__要返回self,来什么对象就返回什么对象

3. f.do_something()对象查找方法,所以在对象里再添加一个do_somethint的方法

 

笔试题2:自定义字典类型,并让字典能够通过句点符的方式操作键值对

class MyDict(dict):
    def __setattr__(self, key, value):    # 2. 添加__setattr__方法
        self[key] = value    # 3. self指代的是字典,添加新的键值对

    def __getattr__(self, item):    # 6. 添加__getatter__方法
        return self.get(item)    # 7. 对象从键值对里取值

obj = MyDict()
print(obj)    # {}

obj.name = 'alex'   # 1:想给字典的数据添加一个键值对
print(obj)    # 4. 添加的新的键值对可以正常执行 {'name': 'alex'}

"""5.想让对象通过句点符的方式取值"""
print(obj.name)    # 8. 对象可以正常取值 alex

obj.age = 18
print(obj.age)    # 18

print(obj)    # {'name': 'alex', 'age': 18}

思路:

1. 自定义字典类,需要给字典添加新的键值对,联想到__setattr__给对象添加或者修改数据时自动触发,所以添加一个__setattr__方法

2. 通过self[key] = value添加键值对,这样就可以把新的键值对成功添加到字典里

3. 这时候通过对象句点符的方式取值,还是会报错,联想到__getattr__方法是对象在查找不存在的名字的时候自动触发,所以添加一个__getattr__方法

4. 返回self.get(item),这样对象点名字,就可以获取名字对应的数据值

 

二、元类简介 

元类的推导流程

 """推导步骤1: 如何查看数据的数据类型"""

s1 = 'hello world'  # str()
l1 = [11, 22, 33, 44]  # list()
d1 = {'name': 'jason', 'pwd': 123}  # dict()
t1 = (11, 22, 33, 44)  # tuple()
print(type(s1))  # <class 'str'>
print(type(l1))  # <class 'list'>
print(type(d1))  # <class 'dict'>
print(type(t1))  # <class 'tuple'>
"""推导步骤2:其实type方法是用来查看对象的类名"""
class Student:
    pass
obj = Student()
print(type(obj))  # <class '__main__.Student'>
"""推导步骤3:python中一切皆对象,我们好奇type查看类显示的是什么"""
class Student:
    pass
obj = Student()
print(type(obj))  # <class '__main__.Student'>
print(type(Student))  # <class 'type'>
class A:pass
class B:pass
print(type(A), type(B))    # <class 'type'> <class 'type'>

结论:我们定义的类其实都是由type类产生的>>>:元类(产生类的类)

 

创建类的两种方式

方式1:使用关键字class

class Teacher:
    school_name = '女子学院'
    def func1(self):pass
print(Teacher)    # <class '__main__.Teacher'>

方式2:利用元类type(了解)

  type(类名,类的父类,类的名称空间)

cls = type('Student', (object,), {'name':'jason'})
print(cls)    # <class '__main__.Teacher'>

补充:

了解知识:名称空间的产生
1.手动写键值对
针对绑定方法不好定义
2.内置方法exec
能够运行字符串类型的代码并产生名称空间
#exec:三个参数
 
#参数一:包含一系列python代码的字符串
 
#参数二:全局作用域(字典形式),如果不指定,默认为globals()
 
#参数三:局部作用域(字典形式),如果不指定,默认为locals()
 
#可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中

g={
    'x':1,
    'y':2
}
l={}
 
exec('''
global x,z
x=100
z=200
 
m=300
''',g,l)
 
print(g)  #{'x': 100, 'y': 2,'z':200,......}
print(l)  #{'m': 300}

 

元类定制类的产生行为

我们现在知道了类是怎么产生的,就可以通过一些手段干预类和对象的产生行为。

推导:

  • 对象是由类名加括号产生的,会执行类里的__init__
  • 类是由元类加括号产生的,会执行元类里的__init__
  • 所以元类可以用来拦截类的一些创建形为。

案例:

需求:所有的类必须首字母大写,否则无法产生

 

# 1.自定义元类:继承type的类称之为元类
class MyMetaClass(type):
    def __init__(self, what, bases=None, dict=None):    # 形参是通过查看type的源码里的__init__方法得到的
        """通过打印三个参数,发现what是类名,bases是父类,dict是名称空间"""
        # print('what',what)
        # print('bases',bases)
        # print('dict',dict)
        if not what.istitle():
            raise TypeError('你不是不python程序员?懂不懂规矩?类名首字母应该大写')       # 如果类名的首字母没有大写,抛出异常
        super().__init__(what, bases, dict)     # 调用父类方法

# 2. 指定类的元类:利用关键字metaclass指定类的元类
class myclass(metaclass= MyMetaClass):
    desc = '元类其实很有趣 就是有点绕'
print(myclass)   # 类的首字母没有大写,会报错

class Student(metaclass=MyMetaClass):
    info = '我是学生 我很听话'
print(Student)    # <class '__main__.Student'>
print(Student.__dict__)    # Student的名称空间

 

元类定制对象的产生行为

推导:

  • 对象加括号会执行产生该对象类里面的 __call__
  • 类加括号会执行产生该类的类里面的 __call__

案例:

需求:给对象添加独有数据的时候,必须采用关键字参数传参

第1步,按照位置传参

class MyMetaClass(type):
    def __call__(self, *args, **kwargs):
        print(args)  # ('alex',18, 'famale')
        print(kwargs)  # {}
        return super().__call__(*args, **kwargs)

class Student(metaclass=MyMetaClass):
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

obj = Student('alex',18, 'famale')

第2步:如果按照位置传参,则主动报错,只能按照关键字参数传参

class MyMetaClass(type):
    def __call__(self, *args, **kwargs):
        print(args)  # ()
        print(kwargs)  # {'name': 'alex', 'age': 18, 'gender': 'famale'}
        if args:
            raise TypeError("只能按照关键字传参")
        return super().__call__(*args, **kwargs)

class Student(metaclass=MyMetaClass):
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender


# obj = Student('alex',18, 'famale')   # TypeError: 只能按照关键字传参
obj1 = Student(name='alex',age=18,gender='famale')

 

魔法方法之__new__

__new__ : 产生一个空对象

class MyMetaClass(type):
    def __call__(self, *args, **kwargs):

        # 1.先产生一个空对象(骨架)
        obj = self.__new__(self)

        # 2.调用__init__给对象添加独有数据(血肉)
        self.__init__(obj,*args,**kwargs)

        # 3.返回创建好的对象
        return obj

class Student(metaclass = MyMetaClass):
        def __init__(self, name):
            self.name = name

obj = Student('alex')
print(obj.name)    # alex

 

三、设计模式简介

1. 设计模式:前人通过大量的验证创建出来解决一些问题的固定高效方法

2. IT行业

    23种

      创建型、结构型、行为型

 详细博客: https://blog.csdn.net/qq_35669659/article/details/123145226

3. 单例模式:类加括号无论执行多少次永远只会产生一个对象

    目的:

  当类中有很多非常强大的方法 我们在程序中很多地方都需要使用

        如果不做单例,会产生很多无用的对象浪费存储空间

        我们想着使用单例模式,整个程序就用一个对象

 

单例模式实现的多种方式

方式1:用绑定给类的方法产生

class C1:
    __instance = None

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

    @classmethod
    def singleton(cls):
        if not cls.__instance:
            cls.__instance = cls('alex', 18)
        return cls.__instance


obj1 = C1.singleton()
obj2 = C1.singleton()
obj3 = C1.singleton()   # 用绑定给类的方法产生单例
print(id(obj1))    # 1764532833008
print(id(obj2))    # 1764532833008
print(id(obj3))    # 1764532833008


obj4 = C1('lucy',19)   # 方法并没有写死,如果想产生新的对象,则直接用类加括号产生
obj5 = C1('jsaon',26)
print(id(obj4))    # 1764532879616
print(id(obj5))    # 2377895117152

方式2:

class Mymeta(type):
    def __init__(self, name, bases, dict):  # 定义类Mysql时就触发
        # 事先从配置文件中取配置来造一个Mysql的实例出来
        self.__instance = object.__new__(self)  # 产生对象
        self.__init__(self.__instance,'alex', 18)    # 初始化对象
        # 上述两步可以合成下面一步
        # self.__instance = super().call__(*args, **kwargs)
        super().__init__(name, bases, dict)

    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,name,age):
        self.name = name
        self.age = age

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

obj3 = Mysql('alex',18)
obj4 = Mysql('lucy',19)    # 根据所传参数的不同,产生不同的对象
print(id(obj3), id(obj4))    # 1865674785504 1865674785456

方式3:基于模块的单例模式

# 在模块里把所有的功能和方法都写好,要用的时候直接从模块里调

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

    def func1(self):
        pass

    def func2(self):
        pass

    def func3(self):
        pass

    def func4(self):
        pass

obj = C1('jason')

方式4: 定义一个装饰器实现单例模式

def outer(cls):  # cls=Mysql
    _instance = cls('alex', 18)

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

    return inner


@outer  # Mysql=singleton(Mysql)
class Mysql:
    def __init__(self, name, age):
        self.name = name
        self.age = age


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

obj4 = Mysql('alex', 18)
obj5 = Mysql('lucy', 19)
print(obj3 is obj4)  # False

方式5:

class MyMetaClass(type):
    def __call__(self, *args, **kwargs):
        if not self.instance:
            obj = self.__new__(self)
            self.__init__(obj)
            self.instance = obj
        return self.instance

class C1(metaclass=MyMetaClass):
    instance = None


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

 

四、pickle序列化模块

优势: 能够序列化python中所有的类型
缺陷: 只能够在python中使用,无法跨语言传输

案例:

需求: 产生一个对象并保存到文件中,取出来还是一个对象

class C1:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def func1(self):
        print('from func1')

    def func2(self):
        print('from fucn2')

obj = C1('alex', 18)

import pickle
with open(r'a.txt', 'wb') as f:
    data = pickle.dump(obj, f)

with open(r'a.txt','rb') as f:
    data = pickle.load(f)
print(data)    # <__main__.C1 object at 0x00000222A6F34BB0>
data.func1()    # from func1
data.func2()    # from fucn2
print(data.name)    # alex

 

posted @ 2022-11-08 22:26  莫~慌  阅读(279)  评论(0编辑  收藏  举报