双下划线方法(__)

面向对象中的双下方法也有一些人称之为是魔法方法,有些双下方法不需要刻意调用,到达某个条件会自动触发,就比如我们在对象实例化中学的__init__方法。

__str__:对象被执行打印(print、前端展示)操作的时候自动触发,该方法必须返回字符串类型的数据。

例子:原本应该是打印对象的,但定义了该方法后,打印的值变成了该方法的返回值。

# 没有__str__的情况:
class MyClass1(object):
pass
obj1 = MyClass1()
print(obj1) # 输出:<__main__.MyClass object at 0x000001D5B350F208> # 定义了__str__的情况:
class MyClass2(object):
def __str__(self):
"""这里可以自定义其他操作"""
return '这里是返回的内容'
obj2 = MyClass2()
print(obj2) # 输出:这里是返回的内容

__del__:对象被执行(被动、主动)删除操作之后自动执行。

例子:程序运行结束python会自动删除(回收)定义的名称,所以程序结束MyClass被删除了,就执行了该方法。

class MyClass(object):
def __del__(self):
"""这里可以自定义其他操作"""
print("MyClass被删除了")
obj = MyClass()
"""
执行结果:
MyClass被删除了
"""

__call__:对象被加括号调用的时候自动触发。

例子:

class MyClass(object):
def __call__(self, *args, **kwargs):
"""自定义操作"""
print(args, kwargs)
return '执行的返回值'
obj = MyClass()
res = obj('aaa', a=1) # 输出:('aaa',) {'a': 1}
print(res) # 输出:执行的返回值

__enter__:对象被执行with上下文管理语法开始自动触发,该方法返回什么as后面的变量名就会得到什么。

__exit__:对象被执行with上下文管理语法结束之后自动触发。

例子:

class MyClass(object):
def __enter__(self):
"""自定义操作"""
print('__enter__方法')
return '这是给as后面的变量的' def __exit__(self, exc_type, exc_val, exc_tb):
"""对象被执行with上下文管理语法结束之后自动触发"""
print('__exit__方法') obj = MyClass()
with obj as o:
print(o)
"""
执行结果:
__enter__方法
这是给as后面的变量的
__exit__方法
"""

__setattr__:对象在执行添加属性操作的时候自动触发。

例子:因为执行了:对象.变量名=变量值的赋值操作,触发了该方法。

class MyClass(object):
def __setattr__(self, key, value):
"""这里可以自定义其他操作"""
print('添加键为%s, 值为%s' % (key, value))
obj = MyClass()
obj.name = 'tom' # 输出:添加键为name, 值为tom

__getattr__:对象查找不存在名字的时候自动触发。

例子:查找到名字时按正常流程走,找不到名字执行该方法,并把该方法返回值替换成需要查找的值。

class MyClass(object):
name = 'tom'
def __getattr__(self, item):
"""这里可以自定义其他操作"""
return '不好意思 没有%s这个名字' % item obj = MyClass()
print(obj.name) # 输出:tom
print(obj.age) # 输出:不好意思,没有age这个名字

__getattribute__:只要对象查找名字无论名字是否存在都会执行该方法,如果类中有__getattribute__方法,那么就不会去执行__getattr__方法

class MyClass(object):
def __getattribute__(self, item):
print('不管有没有,我都执行')
return '这是执行的返回值' def __getattr__(self, item):
return '不好意思 没有%s这个名字' % item obj = MyClass()
res = obj.name # 输出:不管有没有,我都执行
print(res) # 输出:这是执行的返回值

笔试题

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})
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()

元类简介

元类,即产生类的类。

在学类之前,我们都说type()是查看数据类型的方法,但在学了类之后,可以推导出type()查看的其实是当前对象所属的类名称。比如123属于int类、'asd'属于str类、[1,2,3]属于list类等等。

我们来看一段代码:

class MyClass(object):
pass
obj = MyClass()
print(type(obj)) # 输出:<class '__main__.MyClass'>
print(type(MyClass)) # 输出:<class 'type'>

在上述例子中,用type()方法可以看到obj属于MyClass类,MyClass类属于的是type类,而且obj是MyClass类产生的,所以可以推导出MyClass类是type类产生的。

总结:type就是元类,而且是所有类默认的元类。

学习元类的目的:

元类能够控制类的创建,也就意味着我们可以高度定制类的行为,就好比掌握了物品的生产过程,就可以在过程中做任何的额外操作。

产生类的两种表现形式

产生类有两种方法,但其实本质上都是同一种。

产生类有两种方法:

  • class关键字产生类。

  • 用type元类产生类。

第一种我们已经熟知了,重点是第二种方法。

用type元类产生类:

书写方式:type(类名, 父类, 类的名称空间)

比如:

MyClass = type('MyClass', (), {'name': 'tom'})
obj = MyClass()
print(obj.name) # 输出:tom

元类的基本使用

我们都知道,自定义列表的类需要创建一个类去继承list类,自定义元类也是一样,只需要创建一个类去继承type类,但是如果要让一个类去使用自定义元类就需要用关键字赋值的方式。比如:

# 必须是继承了type的类才是自定义元类
class Mymeta(type):
pass # 通过metaclass可以指定类的元类
class MyClass(metaclass=Mymeta):
pass print(type(MyClass)) # 输出:<class '__main__.Mymeta'>

问题:如何让一个类必须要大写字母开头。

解决:

class MyTypeClass(type):
# 重写type元类中的方法
def __init__(cls, cls_name, cls_bases, cls_dict):
# 如果创建的类是大写,则报错
if not cls_name.istitle():
raise Exception("类名的首字母必须大写")
# 如果创建的类是大写字母开头,则执行元类type中的方法
super().__init__(cls_name, cls_bases, cls_dict) class C1(metaclass=MyTypeClass): # 正常执行
school = '清华大学' class a(metaclass=MyTypeClass): # 在这里报错
school = '清华大学'

程序执行到在定义a类的时候就会报错。

元类进阶操作

回想:对象加括号会自动执行产生该对象的类里面的__call__,并且该方法返回什么对象加括号就会得到什么。

推导:类加括号会自动执行元类的里面的__call__,并且该方法返回什么类加括号就会得到什么。

证明:

# 自定义元类
class Mymeta(type):
def __call__(self, *args, **kwargs):
print('类被调用了') # 使用自定义元类创建类
class MyClass(metaclass=Mymeta):
pass MyClass() # 输出:类被调用了

进阶:在创建对象的时候需要用类加括号的方式实例化,但是调用类也是用类加括号的方式,那么类里面的__init__方法和元类里面的__call__方法执行的先后顺序是怎么样的?。

# 自定义元类
class Mymeta(type):
def __call__(self, *args, **kwargs):
print('类被调用了') # 使用自定义元类创建类
class MyClass(metaclass=Mymeta):
def __init__(self):
print('类被初始化了') res = MyClass() # 输出:类被调用了

由上述结果得知:如果同时存在类里面的__init__方法和元类里面的__call__方法,那么它们的执行优先级为:元类里面的__call__优先级更高。

那么该如何用MyClass类创建对象呢?其实只要返回type元类里面的__call__即可

# 自定义元类
class Mymeta(type):
def __call__(self, *args, **kwargs):
print('类被调用了')
return super().__call__(*args, **kwargs) # 使用自定义元类创建类
class MyClass(metaclass=Mymeta):
def __init__(self):
print('类被初始化了') res = MyClass()
print(res) # 输出:<__main__.MyClass object at 0x0000019FC14CC198>

由这个我们可以实现类在实例化产生对象的时候,对象的独有数据必须采用关键字参数。

# 自定义元类
class Mymeta(type):
def __call__(self, *args, **kwargs):
if args:
raise Exception('必须全部采用关键字参数')
return super().__call__(*args, **kwargs) # 使用自定义元类创建类
class MyClass(metaclass=Mymeta):
def __init__(self, name):
self.name = name obj1 = MyClass(name='tom') # 正常执行
obj2 = MyClass('tom') # 程序报错

元类总结:

  • 如果你想高度定制类的产生过程,那么编写元类里面的__init__方法
  • 如果你想高度定制对象的产生过程,那么编写元类里面的__call__方法

__new__方法

__new__是用于产生空对象用的,它会返回一个空对象。

事实上,__init__和__call__执行的时候都会先产生一个空对象,而这个空对象就是由__new__产生的。

我们来看一个例子:

# 自定义元类
class Mymeta(type):
def __new__(cls, *args, **kwargs):
print('__new__ run')
return super().__new__(cls, *args, **kwargs)
def __call__(self, *args, **kwargs):
print('__call__ run')
return super().__call__(*args, **kwargs) # 使用自定义元类创建类
class MyClass(metaclass=Mymeta):
def __init__(self):
print('__init__ run') obj1 = MyClass()
"""
执行结果:
__new__ run
__call__ run
__init__ run
"""

我们可以看出__new__方法是最优先的,并且如果__new__方法不返回空对象,程序就会报错,无法执行__init__和__call__。

所以__init__和__call__方法中的self形参其实是从__new__方法获得的。

python面向对象双下划线方法与元类的更多相关文章

  1. python_面向对象——双下划线方法

    1.__str__和__repe__ class Person(object): def __init__(self,name,age): self.name = name self.age = ag ...

  2. Python - 面向对象编程 - 魔术方法(双下划线方法)

    什么是魔术方法 在Python中,所有以 __ 双下划线包起来的方法,都统称为 Magic Method 魔术方法,也叫双下划线方法 有哪些重要的魔术方法? __new__ https://www.c ...

  3. Python面向对象 | 双下方法

    定义:双下方法是特殊方法,他是解释器提供的.由双下划线+方法名+双下划线 .它具有特殊意义的方法,双下方法主要是python源码程序员使用的,我们在开发中尽量不要使用双下方法,但是深入研究双下方法,更 ...

  4. 面向对象相关概念与在python中的面向对象知识(魔法方法+反射+元类+鸭子类型)

    面向对象知识 封装 封装的原理是,其成员变量代表对象的属性,方法代表这个对象的动作真正的封装是,经过深入的思考,做出良好的抽象(设计属性时用到),给出“完整且最小”的接口,并使得内部细节可以对外透明( ...

  5. python 面向对象专题(六):元类type、反射、函数与类的区别、特殊的双下方法

    目录 Python面向对象06 /元类type.反射.函数与类的区别.特殊的双下方法 1. 元类type 2. 反射 3. 函数与类的区别 4. 特殊的双下方法 1. 元类type type:获取对象 ...

  6. python类中的双下划线方法

    __getitem__,__setitem__和__delitem__ 实现了对象属性的字典化操作. class Person: def __init__(self, name, age, hobby ...

  7. 理解Python的双下划线命名(转)

    add by zhj:今天在学习SimpleHTTPServer的源代码时,看到了Python标准库SocketServer模块中有个BaseServer类,该类的__init__方法定义如下 def ...

  8. 理解Python的双下划线命名

    引子 我热情地邀请大家猜测下面这段程序的输出: class A(object):        def __init__(self):               self.__private()   ...

  9. python单/双下划线使用

    在Python编程中经常会遇到函数(function),方法(method)及属性(attribute)以下划线'_'作为前缀,这里做个总结. 主要存在四种情形: 1. object # public ...

随机推荐

  1. SQL之总结(四)---null问题的处理

    概述:如果表中的某个列是可选的,那么我们可以在不向该列添加值的情况下插入新记录或更新已有的记录.这意味着该字段将以 NULL 值保存. NULL 值的处理方式与其他值不同. NULL 用作未知的或不适 ...

  2. 一个好用的swagger第三方ui-xiaoymin

    swagger自带的ui界面实在是看的难受 配置完默认访问地址:ip:port/swagger-ui.html 推荐一个好用的第三方ui,界面如图: 使用方法: 1.添加依赖 <dependen ...

  3. Linux环境下Eclipse中快捷键不起作用

    在window->Preferences->general->keys中, 找到 content asist 修改下边值 Binding 改成 Alt+/ When 改为 Editi ...

  4. Mybatis实现简单增删改查

    Mybatis的简单应用 学习内容: 需求 环境准备 代码 总结: 学习内容: 需求 使用Mybatis实现简单增删改查(以下是在IDEA中实现的,其他开发工具中,代码一样) jar 包下载:http ...

  5. rem,px,em最大的区别;

    px:px像素(Pixel).相对长度单位.像素px是相对于显示器屏幕分辨率而言的.移动端的分辨率很多.所以px不适用移动端em:em的值不固定:其长度继承父级元素的字体大小rem:相对于根元素htm ...

  6. Uncaught TypeError: document.getElementsById is not a function

    今天博主终于开始攻关javascript(俗称js)了,不过要注意了,它和java可是一丁点关系都没有,就像老婆饼和老婆一样. 下面就让我们来讨论一下博主这次犯下的低级错误吧 一.背景(解决方法在文末 ...

  7. 9. Lab: file system

    https://pdos.csail.mit.edu/6.S081/2021/labs/fs.html 1. Large files (moderate) 1.1 要求 Modify bmap() s ...

  8. 01 | 堆、栈、RAII:C++里该如何管理资源?(极客时间笔记)

    基本概念 堆,英文是 heap,在内存管理的语境下,指的是动态分配内存的区域.这个堆跟数据结构里的堆不是一回事.这里的内存,被分配之后需要手工释放,否则,就会造成内存泄漏. C++ 标准里一个相关概念 ...

  9. Filebeat和logstash 使用过程中遇到的一些小问题记录

    一.filebeat 收集软链文件日志 1.1.场景 由于我们新部署的Nginx 日志都是采用的软链的形式. lrwxrwxrwx 1 root root 72 Apr 6 00:00 jy.baid ...

  10. vim 下几种比较省劲的方式(vi结合着用)

    Vim的几种模式 正常模式:可以使用快捷键命令,或按:输入命令行. 插入模式:可以输入文本,在正常模式下,按i.a.o等都可以进入插入模式. 可视模式:正常模式下按v可以进入可视模式, 在可视模式下, ...