python入门(三)
这部分内容承接 Linux入门、 python入门(一)、
Python入门(三)的内容,是我在python入门学习的第四篇笔记。
主要记录面向对象等方面的内容。
面向对象
面向对象技术简介
类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
方法:类中定义的函数。
类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
局部变量:定义在方法中的变量,只作用于当前实例的类。
实例变量:在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
实例化:创建一个类的实例,类的具体对象。
对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
和其它编程语言相比,Python 在尽可能不增加新的语法和语义的情况下加入了类机制。
Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法。
对象可以包含任意数量和类型的数据。
类是模板,对象是根据这个模板创建出来的。
类只能有一个,对象可以有多个,不同对象的属性可能不同。
类中定义了什么属性和方法,对象就有什么属性和方法,不可能多也不可能少。
类的设计·类的定义、属性、方法
类对象的内置操作函数
dir()内置函数
给该函数传入标识符/数据,就可以看到针对这一对象所有的属性和方法。
isinstance(obj, class)
用于判断对象是否属于某一类
hasattr(obj, attribute) getattr(obj, attribute) setattr(obj, attribute)
判断对象是否有某个属性,获取某个属性,设置某个属性
以__name__这种下划线作为开头和结尾的是python给对象提供的内置方法和属性。
类的定义、实例化、单继承、多继承、super函数
'''
类的定义
类的名使用大驼峰命名法,
即每个单词的首字母大写,单词之间没有下划线
'''
# 定义一个类
class ClassName(BaseClassName):
''' 括号内的基类名表示类继承的基类
子类(派生类 DerivedClassName)会继承父类
(基类 BaseClassName)的属性和方法。
BaseClassName(实例中的基类名)必须与派生类定义在一个作用域内。
除了类,还可以用表达式,基类定义在另一个模块中时这一点非常有用:
class DerivedClassName(modname.BaseClassName):
pythoN还有限地支持多继承形式,用逗号表达式给出多个基类名即可
需要注意圆括号中父类的顺序,若是父类中有相同的方法名,
而在子类使用时未指定,python从左至右搜索
即方法在子类中未找到时,从左到右查找父类中是否包含方法。
如果需要通过子类实例或子类调用父类方法就用super函数
super() 函数是用于调用父类(超类)的一个方法。
super() 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。
'''
pass
'''类名的确定:分析整个业务流程出现的名词通常就可以作为类名'''
def __init__(self, arg1, arg2, ...):
super().BaseClassName.__init__
pass
'''
类有一个名为 __init__() 的特殊方法(构造方法)
该方法在类实例化时会自动调用
可以不重写这一方法,也可以用它来初始化实例变量
类的方法定义和函数定义时的区别是:
其必须传入第一个参数是其类的实例本身,一般用self表示。
'''
# 实例化这个类
x = ClassName()
''' 实例化类是创建一个'''
类属性和方法
类的私有属性
__private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs。
类的方法
在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数,self 代表的是类的实例。self 的名字并不是规定死的,也可以使用 this,但是最好还是按照约定使用 self。
类的私有方法
__private_method:两个下划线开头,声明该方法为私有方法,只能在类的内部调用 ,不能在类的外部调用。self.__private_methods。
类的专有方法:
这些方法只是设置了接口,还是需要自己实现,用于定制类
__init__ : 构造函数,在生成对象时调用
__del__ : 析构函数,释放对象时使用
__str__ : 返回结果必须是字符串,重写该方法可以改变print(实例)的输出
__repr__ : 打印,转换
__setitem__ : 按照索引赋值
__getitem__: 按照索引获取值
__delitem__:按照索引删除值
__getattr__:在没有找到指定的属性时会到该方法下搜索
__len__: 获得长度
__cmp__: 比较运算
__call__: 函数调用
__add__: 加运算
__sub__: 减运算
__mul__: 乘运算
__truediv__: 除运算
__mod__: 求余运算
__pow__: 乘方
__iter__:返回一个可迭代对象
运算符重载
Python同样支持运算符重载,我们可以对类的专有方法进行重载
封装
封装是面向对象编程的一大特性。
面向对象编程的第一步就是将属性和方法封装到一个类中
外界使用类创建对象,用对象调用方法
对象方法的细节都被封装在类中
多态
面向对象的三大特性:封装、多态、继承
多态:不同的子类对象调用相同的父类方法,产生不同的结果。
- 多态可以增加代码的灵活度
- 以继承和重写父类方法为前提
- 是调用方法的技巧,不会影响类的内部设计
类属性和实例属性
类属性就是给类对象定义的属性
通常用来记录与这个类相关的特征
类属性不会用来记录具体对象的特征
类属性定义在类方法外部,能被类名和实例名访问。
实例属性如果和类属性同名,会遮蔽掉类属性,实例属性被删除后,遮蔽解除。
类方法和静态方法
在类方法内部可以访问类对象或其他类方法
类方法需要用修饰器 @classmethod 来标识,告诉解释器这是一个类方法,类方法的第一个参数应该是 cls
- 由哪一类调用的类方法,cls就是哪一类的引用。
- 这个参数和实例方法的self参数类似
- 也可以使用其他标识符,但一半用cls表示
通过 classname. 调用类方法时,不需要给出cls参数。
在类方法内部,可以通过cls.访问类属性和其他类方法。
静态方法
如果需要在类内封装一个方法,这个方法及不需要访问实例属性也不需要访问类属性,既不需要调用实例方法叶也不需要调用类方法,这个方法就可以成为静态方法。
用修饰器 **@staticmethod**修饰
通过 classname. 调用静态方法
杂项
身份运算符 is 和 is not
is是判断两个标识符是不是引用同一个对象
类似id(x) == id(y)
MRO 查看方法搜索顺序
在多继承时可以通过python给类内置的属性__mro__查看方法搜索顺序。
私有属性和私有方法
在属性和方法名前增加2个下划线。
私有属性在外界不能被直接访问
要在外界调用私有属性和方法可以用
_ClassName__AttributeName()
即在方法前添加下划线和类名
类是一个特殊的对象
python运行时类同样会被加载到内存中
类对象在内存中只有一份,一个类可以创建多个实例对象
除了封装实例的属性和方法外,类对象还可以拥有自己的属性和方法。
单例设计模式
设计模式是前人工作总结的经验和方法,通常是对某一类问题的成熟解决方案。
单例设计模式:让类创建的对象在系统中只有唯一的一个实例。
'''__new__方法
__new__方法是object基类提供的内置静态方法,
在使用类名创建对象时,解释器会调用该方法为对象分配内存空间
该方法的作用
1.为对象分配内存空间
2.返回对象的引用
解释器获得该引用后传递给__init__参数
重写__new__方法时一定要
return super().__new__(cls)
'''
面向对象高级编程
使用__slots__
python可以动态地给实例或类绑定属性和方法
def dynaticMethod(sefl, *arg):
pass
return
class A(baseClass):
pass
a = A()
from types import MethodType
# 给实例添加方法
a.dynaticMethod = MethodType(dynaticMethod, a)
# 然后就可以调用该方法
a.dynaticMethod()
# 但给实例绑定的方法只能在该实例使用
# 如果要在其他实例也使用,应该绑定到类方法上
A.dynaticMethod = dynaticMethod
但是如果我们需要限制实例的属性和方法添加呢?为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性
使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的
除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
使用@property
有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?
Python内置的@property装饰器就是负责把一个方法变成属性调用的
对一个属性名,常用一个getitem()和setitem()方法来访问和修改它
class Student(object):
def __init__(self, score):
self._score = score
def score_get(self):
return self._score
def score_set(self, score):
self._score = score
使用@property后可以简化为
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
其中装饰器@score.setter是由@property产生的,用于装饰修改属性的方法,如果不使用该装饰器,则该属性变为只读的
要注意,由于@property装饰的方法可以被当作属性直接访问,要避免它指向的属性与该方法同名,否则会造成递归错误
多重继承
通过多重继承,一个子类就可以同时获得多个父类的所有功能。
使用枚举类
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
# 这样我们就获得了Month类型的枚举类,可以直接使用Month.Jan来引用一个常量,或者枚举它的所有成员:
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)
# value属性则是自动赋给成员的int常量,默认从1开始计数。
# 如果需要更精确地控制枚举类型,可以从Enum派生出自定义类:
@unique #该装饰器检查是否有重复值
class Gender(Enum):
female = 0
male = 1
使用type和元类metaclass 动态创建类
type()函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过type()函数创建出Hello类,而无需通过class Hello(object)…的定义:
要创建一个class对象,type()函数依次传入3个参数:
- class的名称;
- 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
- class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。
通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。
正常情况下,我们都用class Xxx…来定义类,但是,type()函数也允许我们动态创建出类来,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会非常复杂。
>>> def fn(self, name='world'): # 先定义函数
... print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
metaclass,直译为元类,简单的解释就是:
当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。
但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。
元类的使用感觉不太常用,先不写
异常
异常的概念
程序运行时,解释器遇到一个错误,会停止程序并提示信息。
程序停止执行并给出信息这个动作,成为抛出(raise)异常
通过捕获异常可以对突发事件进行集中处理,保证程序的稳定性和健壮性。
Python的错误其实也是class,所有的错误类型都继承自BaseException,所以在使用except时需要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”。
常见的错误类型和继承关系
https://docs.python.org/3/library/exceptions.html#exception-hierarchy
捕获异常
- 简单的捕获异常语法
try:
尝试执行的代码
except:
出错处理的代码
''' 注意,出错处理后程序不会停止而是会继续执行 '''
- 错误类型捕获
try:
pass
except error1:
# 针对错误1的处理
pass
except (error2, error3):
# 针对错误2、错误3的处理
pass
except Exception as Result:
print("未知错误 %s " % Result)
- 异常捕获完整语法
try:
pass
except error1:
# 针对错误1的处理
pass
except (error2, error3):
# 针对错误2、错误3的处理
pass
except Exception as Result:
print("未知错误 %s " % Result)
else:
# 没有异常出现才会执行的代码
pass
finally:
# 无论是否有异常都会执行的代码
pass
异常的传递
当方法/函数调用出现异常,会将异常传递给调用该方法函数的一方。
并逐级回传,直到传递到主程序,如果主程序仍然没有异常处理,程序才会被终止。
这样,我们根据异常的传递性,只需要在主程序中增加异常捕获语句,可以保证代码的简洁性。
记录异常(错误) logging模块
Python内置的logging模块可以非常容易地记录错误信息:
程序打印完错误信息后会继续执行,并正常退出:
# err_logging.py
import logging
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
try:
bar('0')
except Exception as e:
logging.exception(e)
main()
print('END')
抛出异常
如果要抛出错误,首先根据需要,可以定义一个错误的class,选择好继承关系,然后,用raise语句抛出一个错误的实例
raise语句如果不带参数,就会把当前错误原样抛出。此外,在except中raise一个Error,还可以把一种类型的错误转化成另一种类型:
# err_raise.py
class FooError(ValueError):
pass
def foo(s):
n = int(s)
if n==0:
raise FooError('invalid value: %s' % s)
return 10 / n
foo('0')
调试
print查看
断言 assert
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10 / n
def main():
foo('0')
assert的意思是,表达式n != 0应该是True,否则,根据程序运行的逻辑,后面的代码肯定会出错。如果断言失败,assert语句本身就会抛出AssertionError
程序中如果到处充斥着assert,和print()相比也好不到哪去。不过,启动Python解释器时可以用-O参数来关闭assert:(断言的开关“-O”是英文大写字母O,不是数字0。)
logging记录
把print()替换为logging是第3种方式,和assert比,logging不会抛出错误,而且可以输出到文件:
import logging
logging.basicConfig(level=logging.INFO)
s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)
logging允许你指定记录信息的级别,有debug,info,warning,error等几个级别,当我们指定level=INFO时,logging.debug就不起作用了。同理,指定level=WARNING后,debug和info就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。
pdb模块调试
第4种方式是启动Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态。我们先准备好程序
在运行文件时带上-m pdb即可
python -m pdb test.py
l # 查看代码
n # 单步执行
p variable # 查看变量值
q # quit
c # continue untill trace point
也可以在代码中设置断点pdb.set_trace()来设置断点
程序会自动在pdb.set_trace()暂停并进入pdb调试环境,可以用命令p查看变量,或者用命令c继续运行:
import pdb
pdb.set_trace()
# 程序会自动在pdb.set_trace()暂停并进入pdb调试环境,可以用命令p查看变量,或者用命令c继续运行:
测试
单元测试模块 unittest
https://www.liaoxuefeng.com/wiki/1016959663602400/1017604210683936
感觉暂时用不上,先不写。
文档测试(单文件测试)
当我们编写注释时,如果写上这样的注释:
# mydict2.py
class Dict(dict):
'''
Simple dict but also support access as x.y style.
>>> d1 = Dict()
>>> d1['x'] = 100
>>> d1.x
100
>>> d1.y = 200
>>> d1['y']
200
>>> d2 = Dict(a=1, b=2, c='3')
>>> d2.c
'3'
>>> d2['empty']
Traceback (most recent call last):
...
KeyError: 'empty'
>>> d2.empty
Traceback (most recent call last):
...
AttributeError: 'Dict' object has no attribute 'empty'
'''
def __init__(self, **kw):
super(Dict, self).__init__(**kw)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
def __setattr__(self, key, value):
self[key] = value
if __name__=='__main__':
import doctest
doctest.testmod()
运行该文件时,会把注释中的测试代码提取出来运行一遍,如果正常运行就无影响,否则会报错。
模块
模块搜索顺序:
在当前目录下搜索指定名称的模块文件
如果在当前目录下没找到,会回到系统的工具包目录下找。
python中对每个模块都有一个内置属性__file__可以查看模块的完整路径
模块开发原则
每一个文件都应该是可以被导入的。
一个独立的py文件就是一个模块。
在导入文件时,所有没有任何缩进的代码都会被执行一遍
内置属性 name
__name__属性可以做到,测试模块的代码只在测试时执行,在被导入不会被执行
如果是被其他文件导入的,__name__就是模块名
如果是当前执行的文件,name__就是__main
# 导入模块
# 定义全局变量
# 定义类
# 定义函数
# 在代码的最下方
def main():
#...
pass
if __name__ == "__main__":
main()
发布模块
- 创建setup.py
from distutils.core import setup
setup(name = "包名",
version = "1.0",
description = "描述信息",
long_description = "",
author = "",
author_email = "",
url = "",
py_modules = ["包名.模块名1",
"包名.模块名2"]
)
- 构建发布模块
在终端执行指令
# 构建模块
python3 setup.py build
# 发布模块
python3 setup.py sdist
# 安装模块
tar -zxvf 包名.tar.gz
sudo python3 包名.py install
# 删除模块
cd usr/local/lib/python3.x/dist-packages/
sudo rm -r 包名*
使用 pip 安装第三方模块
# 如果想把包安装到Python3环境
sudo pip3 install package_name
sudo pip3 uninstall package_name
# 如果想把pip安装到python2环境
sudo pip install package_name
sudo pip uninstall package_name
包
包是一个包含多个模块的特殊目录
目录下有一个特殊的文件__init__.py
包名的命名方式和变量名命名方式一样,小写字母 + 下划线_
使用 import 包名 可以一次导入包中所有模块
__init.py
# 要在外界使用包中的模块,
# 需要在__init.py指定对外界提供的模块列表
from . import module1
from . import module2
...
文件操作(IO控制)
文件的概念
- 文件的概念和作用
- 文件的存储方式
二进制数据
文件的基本操作
文件操作流程
- 打开文件
- 读写文件
- 关闭文件
操作文件的函数/方法
函数/方法 | 说明 |
---|---|
open | 打开文件并返回文件操作对象 |
read | 将文件内容读取到内存 |
write | 将指定内容写入到文件 |
close | 关闭文件 |
以上是最基本的四个方法,还有许多的文件对象方法:
文件打开的方式
open 函数默认以只读方式打开文件,并返回文件对象
# 常用形式
f = open("文件名", "打开方式")
# 完整格式
open(file, mode='r', buffering=-1,
encoding=None, errors=None, newline=None,
closefd=True, opener=None)
'''
file: 必需,文件路径(相对或者绝对路径)。
mode: 可选,文件打开模式
buffering: 设置缓冲
encoding: 一般使用utf8
errors: 报错级别
newline: 区分换行符
closefd: 传入的file参数类型
opener: 设置自定义开启器,开启器的返回值必须是一个打开的文件描述符。
'''
打开方式mode的参数有
文件/目录常用管理操作
在终端/文件浏览器上可以执行常规的文件/目录管理操作,如创建、重命名、删除、改变路径等。
在python中可以通过导入os模块实现这些功能。
文件操作
方法名 | 说明 | 示例 |
---|---|---|
rename | 重命名 | os.rename(源文件名, 目标文件名) |
remove | 删除文件 | os.remove(目标文件) |
目录操作
方法名 | 说明 | 示例 |
---|---|---|
listdir | 目录列表 | os.listdir(目录名) |
mkdir | 创建目录 | os.mkdir(目录名) |
rmdir | 删除目录 | os.rmdir(目录名) |
getcwd | 获取当前目录 | os.getcwd() |
chdir | 修改工作目录 | os.chdir(目标目录) |
path.isdir | 判断是否是文件 | os.path.isdir(文件路径) |
文件路径支持相对路径和绝对路径 |
文本文件的编码格式
文本文件存储的是基于字符编码的数据,常见的有ascii编码和unicode编码等
python2.x默认使用ascii编码。
python3.x默认使用UTF-8编码
如果要 在python2.x中使用中文输出,需要把编码格式改成utf-8
具体地是在代码最开始加上一行注释。并且在字符串引号前加u
# *-* coding:utf8 *-*
u"这是一个中文字符串"
ASCII编码
有256个字符,给字符在内存中占据1个字节的空间。
UTF-8编码
使用1-6个字节来表示一个UTF-8字符,几乎涵盖世界上所有地区的文字
大多数汉字会用3个字节表示
UTF-8是unicode的一种编码格式
StringIO和BytesIO
很多时候,数据读写不一定是文件,也可以在内存中读写。
StringIO顾名思义就是在内存中读写str。
要把str写入StringIO,我们需要先创建一个StringIO,然后,像文件一样写入即可:
>>> from io import StringIO
>>> f = StringIO()
>>> f.write('hello')
5
>>> f.write(' ')
1
>>> f.write('world!')
6
>>> print(f.getvalue())
hello world!
>>> from io import StringIO
>>> f = StringIO('Hello!\nHi!\nGoodbye!')
>>> while True:
... s = f.readline()
... if s == '':
... break
... print(s.strip())
...
Hello!
Hi!
Goodbye!
StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO。
BytesIO实现了在内存中读写bytes,我们创建一个BytesIO,然后写入一些bytes:
序列化·pickle模块
我们把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等,都是一个意思。
序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。
反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。
Python提供了pickle模块来实现序列化
>>> import pickle
>>> d = dict(name='Bob', age=20, score=88)
>>> pickle.dumps(d)
b'\x80\x03}q\x00(X\x03\x00\x00\x00ageq\x01K\x14X\x05\x00\x00\x00scoreq\x02KXX\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Bobq\x04u.'
>>> f = open('dump.txt', 'wb')
>>> pickle.dump(d, f)
>>> f.close()
当我们要把对象从磁盘读到内存时,可以先把内容读到一个bytes,然后用pickle.loads()方法反序列化出对象,也可以直接用pickle.load()方法从一个file-like Object中直接反序列化出对象。我们打开另一个Python命令行来反序列化刚才保存的对象:
>>> f = open('dump.txt', 'rb')
>>> d = pickle.load(f)
>>> f.close()
>>> d
{'age': 20, 'score': 88, 'name': 'Bob'}
eval()函数
将字符串当成有效表达式来求值并返回结果
要注意的是,不要用eval函数直接转换终端输入,否则用户可以通过os模块的函数调用做一些非法操作。
比如
eval('__import__(os).system("rm -rf")')
会导致当前目录下文件被强制删除
#------------------------------------------------------------------------------#
更新于2021-6-2
#------------------------------------------------------------------------------#
更新于2023-1-4,更新了面向对象高级编程部分、异常部分、测试部分的内容,参考廖雪峰前辈的网站增加了许多内容。
说明:本文内容参考了一些视频和博客,虽然是我自己进行了整理和编辑后的内容,但如果您(原视频或博客作者)认为我的行为侵犯了您的版权,请私信联系我,核实身份后我会删除文章。