今日作业
编写元类规定对象的所有数据值转大写
eg:
obj.name = 'jason'
print(obj.name) # JASON
class MyTypeClass(type):
def __call__(self, *args, **kwargs):
args = [i.upper() for i in args]
if kwargs:
for i in kwargs:
kwargs[i] = kwargs[i].upper()
return super().__call__(*args, **kwargs)
class MyClass(metaclass=MyTypeClass):
def __init__(self, name):
self.name = name
def __setattr__(self, key, value):
self.__dict__[key] = value.upper()
my = MyClass(name='aaa')
my.age = 'abc'
print(my.name,my.age)
面向对象的双下方法
1.__ str__, __ repr__
__str__(***) 里边必须有return "字符串"类型 (优先级高于__repr__)
1.打印对象的时候,会触发__str__方法
2.直接str转化也可以触发
class A:
def __init__(self,name,age):
self.name = name
self.age =age
def __str__(self):
print(666)
return f'姓名: {self.name} 年龄: {self.age}'
a = A('二狗',35)
b = A('大狗',56)
c = A('老狗',18)
打印对象触发__str__方法
print(f'{a.name} {a.age}') # 二狗 35
print(f'{b.name} {b.age}')
print(f'{c.name} {c.age}')
print(a) # 666
姓名: 二狗 年龄: 35
print(b)
print(c)
直接str转化也可以触发.
print(str(a)) # 666
姓名: 二狗 年龄: 35
__repr__ 里边必须有return "字符串"类型
打印对象的时候,会触发__repr__方法
class A:
def __init__(self,name,age):
self.name = name
self.age =age
def __repr__(self):
print(666)
return f'姓名: {self.name} 年龄: {self.age}'
a = A('二狗',35)
b = A('大狗',56)
c = A('老狗',18)
print(a)
print(repr(a))
__str__和__repr__ 一起使用时,只执行__str__的内容
class A:
def __init__(self,name,age):
self.name = name
self.age =age
def __str__(self):
return '777'
def __repr__(self):
return '666'
a = A('二狗',35)
b = A('大狗',56)
c = A('老狗',18)
# print(a)
print(a) # 777
print(b) # 777
'''
str()方法定义当被print()方法或者str()方法调用时的行为,repr()方法定义当被print()方法或者
repr()方法调用时的行为,一般来说,它们的功能都是实现类到字符串的转化,实现方式上也没有特别的差
异。 但实际上,str()方法的返回结果可读性应该要更强一些,repr()方法的返回结果要更加准确,也就是
说,str()方法的意义是得到便于人们阅读的信息,而__repr__()方法的目的在于开发者便于调试。
注意:当进行print()的时候,首先被执行的是__str__()方法,如果没定义__str__()方法,repr()
方法才会被执行。
'''
2.__call__
__call__(***) 对象()触发对象从属类(父类)的__call__方法
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo()
obj()
当调用obj()时,call()方法会被调用,call()方法也可以接受参数
'''
call()方法可以让类的实例像函数一样被调用
'''
3. __del__
__del__当一个对象的生命周期结束被彻底销毁的时候被调,__del__方法会被调用,不常用,一般不需要
进行重写。
class A:
def __del__(self):
print(666)
obj = A()
del obj
4.__getattr__,__getattribute__,__setattr__
__getattr__表示的是当一个对象访问一个属性时,没有从它定义的属性当中查找到就会调用这个方法。
class A(object):
def __init__(self, value):
self.value = value
def __getattr__(self, item):
print("into __getattr__")
return "can not find"
a = A(10)
print(a.value)
# 10
print(a.name)
# into __getattr__
# can not find
__getattribute__只要对象查找名字无论名字是否存在都会执行该方法,如果类中有__getattribute__方
法 那么就不会去执行__getattr__方法
class A(object):
def __init__(self, value):
self.value = value
def __getattribute__(self, item):
print("into __getattribute__")
return "__getattribute__"
a = A(10)
print(a.value)
into __getattribute__
__getattribute__
print(a.name)
into __getattribute__
__getattribute__
class A(object):
def __init__(self, value):
self.value = value
def __getattribute__(self, item):
print("into __getattribute__")
return "__getattribute__"
def __getattr__(self, item):
print('into __getattr__')
return '__getattr__'
a = A(10)
print(a.value)
# into __getattribute__
# __getattribute__
print(a.name)
# into __getattribute__
# __getattribute__
'''
使用归纳:
1.__getattribute__方法优先级比__getattr__高
2.当同时定义__getattribute__和__getattr__时,__getattr__方法不会再被调用,除非显示调用
__getattr__方法或引发AttributeError异常。
3.如果是对不存在的属性做处理,尽量把逻辑写在__getattr__方法中
4.如果非得重写__getattribute__方法,需要注意两点:第一是避免.操作带来的死循环;第二是不要遗
忘父类的__getattribute__方法在子类中起的作用
'''
__setattr__对象在执行添加属性操作的时候自动触发 >>> obj.变量名=变量值
class A(object):
def __init__(self, value):
print("into __init__")
self.value = value
def __setattr__(self, name, value):
print("into __setattr__")
if value == 10:
print("from __init__")
object.__setattr__(self, name, value)
a = A(10)
# into __init__
# into __setattr__
# from __init__
print(a.value)
# 10
a.value = 100
# into __setattr__
print(a.value)
# 100
5.__enter__,__exit__
我们知道在操作文件对象的时候可以这么写
with open('a.txt') as f:
'代码块'
上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法
__exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行
如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行
# coding=utf-8
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中代码块执行完毕时执行我啊')
print(exc_type)
print(exc_val)
print(exc_tb)
return True
with Open('aa.txt') as f:
print(f)
print(f.name)
print(sdsd) # 触发 __exit__ ,之后的代码就不会运行
print('-------')
print('End....')
'''
运行结果:
出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
<__main__.Open object at 0x0000016D7FA95BE0>
aa.txt
with中代码块执行完毕时执行我啊
<class 'NameError'>
name 'sdsd' is not defined
<traceback object at 0x0000016D7FA925C8>
End....
'''
'''
总结:
__enter__在with语句后自动调用,可以给as后的变量赋值
__exit__用于捕获异常,返回boolean对象,如果为True异常被忽略,如果为False异常被抛出
三个参数分别是:
1.exc_type 异常类型
2.exc_value异常值
3.traceback
with obj as f:
代码块
1. with obj ----> 触发obj.__enter__(),拿到返回值
2. as f --> f = 返回值
3. with obj as f 等同于 f = obj.__enter__()
4. 执行代码块
1> 没有异常,整个代码块运行完毕后去触发__exit__(),它的三个参数都返回None
2> 有异常的情况下,从异常出现的位置直接触发__exit))
a:如果__exit__返回值为True,代表吞掉了异常(不报异常)
b:如果__exit__返回值不为True,代表吐出了异常(异常报错)
c:__exit__运行完毕,就代表整个with语句运行完毕
'''
针对双下方法的笔试题
1.让字典具备句点符查找值的功能
# 1.定义一个类继承字典
class MyDict(dict):
def __getattr__(self, item):
return self.get(item)
def __setattr__(self, key, value):
self[key] = value
'''要区别是名称空间的名字还是数据k:v键值对'''
obj = MyDict({'name':'jason','age':18})
# 1.具备句点符取v
# print(obj.name)
# print(obj.age)
# 2.具备句点符设k:v
# obj['gender'] = 'male'
obj.pwd = 123 # 给字典名称空间添加名字 不是数据k:v
print(obj)
2.补全下列代码 使其运行不报错
"""
class Context:
pass
with Context() as ctx:
ctx.do_something()
"""
class Context:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass
def do_something(self):
pass
with Context() as ctx:
ctx.do_something()
元类
元类就是用于创建类的“东西”。
在理解元类之前,我们必须先掌握Python中的类(class)。
和大多数语言一样,Python中的类是用来描述如何“生成一个对象”:
class A(object):
pass
a = A()
print(a)
# <__main__.A object at 0x01DFFDD8>
但是,在Python中,类不仅能用来描述如何生成一个对象,类本身也是对象。
在你使用关键词 class 的时候,Python就会执行它,并创建一个对象。
class A(object):
pass
当我们运行这两行代码时在内存中创建了一个名为“A”的对象。
这个对象(类)本身具有创建对象(实例)的能力,因此它也是一个类。你可以对它做以下操作:
1.将其分配给变量
2.复制它
3.为其添加属性
4.将其作为函数参数传递
我们可以对A添加属性等等,还可以对A使用反射
class A(object):
pass
A.name = 1
print(A) # <class '__main__.A'>
print(hasattr(A,'name')) # True
A实际也是一个对象,但它又可以实例出对象。
使用class关键字时,Python会帮你自动创建此对象,但是,Python同样也提供了一种手动创建的方法,那就
是type函数。
type函数最经典的用法是返回对象的类型。但是很少人知道,它还能接受参数并手动创建类。
type(name, bases, attrs)
name: 类名
bases: 元组,父类名
attrs: 字典,类属性值
A = type('A', (), {})
print(A) # <class '__main__.A'>
class Foo(object):
bar = True
等同于Foo = type('Foo', (), {'bar':True})
继承
FooChild = type('FooChild', (Foo,), {})
print(FooChild)
# <class '__main__.FooChild'>
print(FooChild.bar)
# True
可见通过 type() 函数创建的类和直接写class是完全一样的。
因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用 type() 函数创建出class。
产生类的两种表现形式(本质是一种)
1.class关键字
class C1(object):
pass
print(C1) # <class '__main__.C1'>
2.type元类
type(类名,父类,类的名称空间)
res = type('C1', (), {})
print(res) # <class '__main__.C1'>
"""
学习元类的目的
元类能够控制类的创建 也就意味着我们可以高度定制类的行为
eg:掌握了物品的生产过程 就可以在过程中做任何的额外操作
比如:要求类的名字必须首字母大写
思考在哪里编写定制化代码
类的产生过程目前还比较懵 元类里面的__init__方法
对象的产生过程呢 类里面的__init__方法
方法:由已知推未知
"""
元类一般用于创建类。在执行类定义时,解释器必须要知道这个类的正确的元类。解释器会先寻找类属性
__metaclass__,如果此属性存在,就将这个属性赋值给此类作为它的元类。如果此属性没有定义,它会向上查
找父类中的__metaclass__.如果还没有发现__metaclass__属性,解释器会检查名字为
__metaclass__的全局变量,如果它存在,就使用它作为元类。否则, 这个类就是一个传统类,并用
types.ClassType 作为此类的元类。
注意:Python3中不再有__metaclass__属性以及模块级别的__metaclass__属性。
在执行类定义的时候,将检查此类正确的(一般是默认的)元类,元类(通常)传递三个参数(到构造器): 类名,
从基类继承数据的元组,和(类的)属性字典。
class MyTypeClass(type):
def __init__(cls, cls_name, cls_bases, cls_dict):
# print(cls, cls_name, cls_bases, cls_dict)
if not cls_name.istitle():
raise Exception("类名的首字母必须大写 你个SD")
super().__init__(cls_name, cls_bases, cls_dict)
class C1(metaclass=MyTypeClass):
school = '清华大学'
class a(metaclass=MyTypeClass):
school = '清华大学'
元类进阶操作
1.回想__call__方法
对象加括号会自动执行产生该对象的类里面的__call__,并且该方法返回什么对象加括号就会得到什么
推导:类加括号会执行元类的里面的__call__该方法返回什么其实类加括号就会得到什么
"""类里面的__init__方法和元类里面的__call__方法执行的先后顺序"""
class MyTypeClass(type):
def __call__(self, *args, **kwargs):
print('__call__ run')
super().__call__(*args, **kwargs)
class MyClass(metaclass=MyTypeClass):
def __init__(self, name):
print('__init__ run')
self.name = name
obj = MyClass('jason')
# 定制对象的产生过程
class MyTypeClass(type):
def __call__(self, *args, **kwargs):
# print('__call__ run')
# print(args,kwargs)
if args:
raise Exception('必须全部采用关键字参数')
super().__call__(*args, **kwargs)
class MyClass(metaclass=MyTypeClass):
def __init__(self, name):
# print('__init__ run')
self.name = name
"""强制规定:类在实例化产生对象的时候 对象的独有数据必须采用关键字参数"""
# obj1 = MyClass('jason')
obj2 = MyClass(name='jason')
"""
如果你想高度定制类的产生过程
那么编写元类里面的__init__方法
如果你想高度定制对象的产生过程
那么编写元类里面的__call__方法
"""
__new__
new()方法是在新式类(新式类和经典类的区别)中的方法。object为所有新式类的基类,在object中,
new()方法被定义为静态方法,并且至少需要传递一个参数cls,cls表示需要实例化的类。
在创建一个类对象实例的过程中,new()方法作用在构造方法__init__()之前。new()函数执行后会返回实
例对象(self),然后将self作为第一个参数传给该类的初始化方法__init__()方法。
可以这么理解,如果设计一个A类,A类中__new__()方法负责返回一个实例对象(不一定是A类对象),而
__init__()方法负责将类对象初始化(设置各种属性之类)。在init()调用之前,new()可以决定是否要使
用init()方法,因为new()可以调用其他类的构造方法或者直接返回别的对象来作为本类(A类)的实例。
#类A
class A(object):
def __init__(self):
self.name = 'A' #设置name属性
def __new__(cls, *args, **kwargs):
# 执行object的__new__()函数,传入cls说明返回当前类(A类)的实例对象
return object.__new__(cls)
#类B
class B(object):
def __init__(self):
self.name = 'B' #设置name属性
def __new__(cls, *args, **kwargs):
#执行object的__new__()函数,传入参数A说明返回(A类)的实例对象
return object.__new__(A)
a = A() #创建A类实例, 相当于隐式执行了A类的__new__()和__init__()
b = B() #创建B类实例, 只执行了B类的__new__()
print(a.name) # A
#print(b.name) # 出错:AttributeError: 'A' object has no attribute 'name'
b.__init__( ) #显示调用__init(),完成b的初始化
print(b.name) # A 正常输出
print(type(a)) # <class '__main__.A'>
print(type(b)) # <class '__main__.A'>
#第一个print(b.name)报错的原因是,想要创建B类实例的时候,new方法却直接返回了A类实例对象
#因此,没有执行B类的init方法,也没有执行A类的init方法,也就导致b对象没有name属性
#在显示调用init方法之后,b才有了name属性,所以第二个的print(b.name)正常输出
"""
注意:并不是所有的地方都可以直接调用__new__ 该方法过于底层
如果是在元类的__new__里面 可以直接调用
class Meta(type):
def __new__(cls, *args, **kwargs):
obj = type.__new__(cls,*args,**kwargs)
return obj
如果是在元类的__call__里面 需要间接调用
class Mate(type):
def __call__(self, *args, **kwargs):
obj = object.__new__(self) # 创建一个空对象
self.__init__(obj,*args,**kwargs) # 让对象去初始化
return obj
"""