进入python的世界_day29_python基础—— 类的内置方法(魔法方法)、元类

一、类的内置方法——也称魔法方法

1.介绍

​ 什么是内置方法?

#定义在类的内部,以__开头而且以__结尾的方法,其实也就是双下
#不同的内置方法,在不同的场景下,无需人为调用会自动触发执行

2.为什么要用内置方法

  • 避免报错
  • 定制化类或者对象

3.几个常用的内置方法

1.__init__()  # 这个不必多说,类的构造方法,会在对象初始化的时候调用,除了self参数外还可以自定义一些参数
2.__ str__()  # 当对象被执行打印操作时自动触发,即被print调用,如果__str__()中有返回值,就会打印其中的返回值,返回值必须是字符串,不能是对象本身(会递归查找报错)
class Pig:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f'名字是:{self.name} 年龄是:{self.age}'

tom = Pig('佩奇', 8)
print(tom)  >>># 名字是:佩奇 年龄是:8
————————————————————————————————————————
3.__call__()  # 对象加括号调用的时候自动触发,如果类体内没写__call__()会报错
class A:
    def __init__(self):
        self.name = "你好"
        self.time = "下午好"
    def say(self):
        print("我在学Python")
    # def __call__(self, *args, **kwargs):
        # print('你好啊')
a = A()
a()     >>># 'A' object is not callable
~~
class A:
    def __init__(self):
        self.name = "你好"
        self.time = "下午好"
    def say(self):
        print("我在学Python")
    def __call__(self, *args, **kwargs):
        print('你好啊')
a = A()
a()    >>> # 你好啊
___________________________________________
4.__getattr__()  # 当对象在找不存在的名字时由这个方法抛出异常,所以可以提前写好规避报错
class A:
    def __init__(self):
        self.name = "你好"
        self.time = "下午好"
    def say(self):
        print("我在学Python")
a = A()
print(a.age)  >>>AttributeError: 'A' object has no attribute 'age'
~~    
class A:
    def __init__(self):
        self.name = "你好"
        self.time = "下午好"
    def say(self):
        print("我在学Python")
    def __getattr__(self, item):
        print('我是__getattr__')
        return '没有找到你点的名字'
a = A()
print(a.age) >>> # 我是__getattr__
                 # 没有找到你点的名字
____________________________________________
5. __ getattribute__()  # 不是很常用,对象只要查找名字就会触发,会把查找的名字的值作为实参传进该方法中,相当于属性访问拦截器
class A(object):
    def __init__(self, name):
        self.name = name
        self.city = "北京"

    def __getattribute__(self, obj):
        if obj.endswith("e"):
            return object.__getattribute__(self, obj)
        elif obj.endswith("y"):
            return object.__getattribute__(self, obj)
        else:
            return '名字找不到啦'

a = A("华为")

print(a.name) 
print(a.test)
print(a.city)              
>>>
#华为
#名字找不到啦
#北京
_______________________________________________
6.__ setattr__()  # 在类中对属性进行赋值操作时,__setattr__()函数会自动触发,注意防止无限递归(用super()解决)
class A:
    def __init__(self):
        self.name = "jack"
        self.age = 29
        self.male = True

    def __setattr__(self, key, value):
        # self.__dict__[key] = value 
        pass
    
a = A()
print(a.name)  >>># 报错

class A:
    def __init__(self):
        self.name = "jack"
        self.age = 29
        self.male = True

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


a = A()
print(a.name)  >>> # jack

a.name = 'BT'
print(a.name)   >>> # BT
_______________________________________
7. __enter__ #  当对象被当做with上下文管理操作的开始自动触发 并且该方法返回什么 as后面的变量名就会接收到什么 一般返回自己

8.__exit__  # with上下文管理语法运行完毕之后自动触发(子代码结束)
class A:
    def __init__(self):
        self.name = "jack"
        self.age = 29
        self.male = True

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

    def __enter__(self):
        return self.name

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('with子代码结束啦,我可以出来了!')


a = A()

with a as f:
    print(f)
    
>>>
# jack
# with子代码结束啦,我可以出来了!

9. __del__() # 在清理对象时触发,会先执行该方法

4.双下new方法——一个静态方法

#   先去看一下元类控制对象产生的那一节,移步至2.3
#元类里面的__call__其实经过了三个步骤
#1.产生一个空对象 2.调用__init__添加数据 3.返回创建好的对象
# 上面说的第一步,其实是先造一个空对象,调用元类内的__new__方法
obj = self.__new__(self) ==> 就是产生一个空对象
__new__() #  new方法是在__init__()构造函数之前执行。负责执行__init__ ,进行一些实例初始化之前的工作。重写__new__()之后init不会自己调用,必须return才行

二、元类(metaclass)

​ 记住python中,万物皆对象

1.前戏(元类推导过程)

class A:
    pass


a = A()
print(type(a))   >>> # <class '__main__.A'> 

l1 = [1, 2, 3]
print(type(l1))  >>> # <class 'list'>
# 可以看到,type是查看对象所归属的类(产生对象的类的名字)
————————————————————————————————————————————————
# 那如果我们查看类A又是产生自谁呢?
print(type(A))  >>> # <class 'type'>
# 不管怎么创类,都可以看到是产生自type
# 可以得到结论,我们创造的类,源头都是type产生的——类被当成对象来看了
  • 总结:产生类的类,就叫做元类,默认是type,type已经是面向对象的尽头

    继承type的类,也可以称为元类

2.元类定制类的行为

对定义的类做出一些干涉

比如说,想规定创类时,类名必须规范,必须首字母大写,这个需求

# 首先想,创类,都源自type,但是呢,我们又不能直接去改type,那哪改的哦,可以自己创一个类,继承type,然后在自己的类中规范条件

# 补充,对象是由类名+括号产生的 __init__
 #      类是由元类+括号产生的  __init__  
1.定义一个基本元类,继承type
class BaseMetaClass(type): # 元类的传参可以ctrl+鼠标查看
    def __init__(self, what, bases=None, dict=None):
        	# what是要创建的类的名字 bases是父类的名称
      		# dict是要创建的类的名称空间
        if not what.istitle():
            #Python istitle() 方法检测字符串中所有的单词拼写首字母是否为大写,且其他字母为小写。
             raise TypeError('类名首字母应该大写!')
                # 手动抛异常!
        super().__init__(what, bases, dict)   
        
class A(metaclass=BaseMetaClass) #这个注意下,本来默认metaclass=type的,现在创类的元类不是type了所以要改一下

# 现在就发现,功能实现了,不过创的时候不要忘记指定元类哈
  • 总结:元类可以拦截产生类的行为,通过改写元类里面的双下init实现控制

3.元类定制对象的产生行为

记住python中,万物皆对象

# 我们知道,对象+括号,会执行产生对象的类的 __call__方法
# 那类也是对象,类+括号,会执行产生类的类的 __call__方法
# 这个很好推导但是有何意义呢?

点进去type果然找到一个双下call方法,不过发现type的双下call确实存在,但是啥都没,不干涉产生类

# 在Python中,创建对象时,先执行元类的__call__,把创建对象的传参也传进__call__审查,然后再执行类的__init__,进行属性的赋值

下面就想一个需求,要求创对象时,必须按照关键字传参,都传给**kwargs,不然创不了对象

# 前面铺垫就是告诉我们,可以在元类的__call__里做修改了
class BaseMetaClass(type):
    def __call__(self, *args, **kwargs):
        # args 是位置传参
        # kwargs 是关键字传参,别忘啦
        if args: # 如果位置被传了东西过来
            raise TypeError("只接收关键字传参!")
        else:
            return super().__call__(*args, **kwargs)
# 这样就定义好了,我们试试

class A(metaclass=BaseMetaClass):
    def __init__(self, name, city):
        self.name = name
        self.city = city

a = A('jack', '长沙')  >>>>>># 报错,主动抛出了异常
a = A(name='jack', city='长沙')  # 可以创建完成
  • 总结:call才是产生对象的关键

    元类可以拦截产生对象的行为,通过改写元类里面的双下call实现控制,高度定制

  • 对象()>类内的双下__call__
    类()->自定义元类内的双下__call__
    自定义元类()->内置元类双下__call__

三、浅识设计模式

1.设计模式
	前人通过大量的验证创建出来解决一些问题的固定高效方法
2.IT行业
	23种
        创建型
        结构型
        行为型
详细博客:      
https://blog.csdn.net/qq_35669659/article/details/123145226
3.单例模式
	类加括号无论执行多少次永远只会产生一个对象
 	目的:
        当类中有很多非常强大的方法 我们在程序中很多地方都需要使用
        如果不做单例 会产生很多无用的对象浪费存储空间
        我们想着使用单例模式 整个程序就用一个对象
posted @ 2022-11-08 21:10  yiwufish  阅读(301)  评论(0)    收藏  举报