进入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.单例模式
类加括号无论执行多少次永远只会产生一个对象
目的:
当类中有很多非常强大的方法 我们在程序中很多地方都需要使用
如果不做单例 会产生很多无用的对象浪费存储空间
我们想着使用单例模式 整个程序就用一个对象

浙公网安备 33010602011771号