“人生苦短,我用Python”,在渗透测试领域Python是高效的工具开发利器,基于Python可以快速编写信息爬取、文件处理、网络发包等脚本,而且现在多数poc/exp都用Python开发,Python几乎成为渗透测试领域的硬核技能,现把Python基础语法总结如下,方便复习查阅。
文章目录
一、对象
(一) 基础概念
1.面向对象与面向过程区分
-
面向过程在主程序中顺序调用不同函数,函数调用子函数,这对开发复杂项目难度较大
-
面向对象是更大的封装,在一个对象内封装多个方法(函数),可通过继承、多态等简化开发流程
2.面向对象的三大特征
-
封装 :根据功能将属性和方法封装到一个抽象的类中
-
继承 :实现代码重用,相同代码不需要重复的编写
-
多态 :不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度
3.类和对象相关概念
-
类是一群具有相同特征和行为事物的一个统称,不能直接使用
-
类是模板,对象是根据模板创建出的具体,不同对象之间属性可能不同
-
对象是类的具体化,是由类创造的聚体存在,可以直接使用
-
对象的特征描述为属性,对象的行为描述为方法
-
在Python中对象无处不在,可以在ipython中
.
接在"变量"后,后边出现提示内容即说明它是对象,实际上Python中所有变量、数据甚至函数都是对象 -
用
dir()
内置函数可以查看对象的属性和方法,如对象的内置属性__doc__
为注释说明文档 -
__name__
是Python的内置属性,如果当前文件是程序运行的主程序,则__name__
的值为__main__
,否则值为文件的名称,可以利用这个特点在开发模块中以if __name__ == "__main__":
条件语句编写调试程序
4.类的定义
类是一个特殊的对象,定义的类属于类对象,用类创建的对象为实例对象,PEP8规范建议类的命名使用大驼峰命名法,类的定义如下:
class ClassName:
def function(self, param_list):
pass
var_name = ClassName()
-
定义方法时的第一个参数必须是
self
-
对象和变量一样,都是引用,
self
是对自身具体对象的引用 -
使用同一个类创建的多个对象不是同一个对象
-
利用赋值语句可以增加对象的属性,但是不推荐这样做,因为这种方式并没有针对类进行修改,仅仅是针对某一个对象,具有一定局限性
-
Python访问属性时首先在对象内部查找,如果有要查找的属性就返回,如果没有就在类属性中查找属性
(二) 实例方法与属性
1.初始化方法
实例(对象)属性一般通过初始化方法定义。当使用类名创建对象时,程序首先会自动为对象在内存中分配空间,然后为对象的属性设置初始值,即__init__
初始化方法,这也是对象的内置方法,是专门用来定义实例属性的方法。
在__init__
中定义属性时,要用self.属性名 = 初始值
定义,可以通过创建对象时传参增加程序灵活性:(PEP8规范建议定义类与类之间应该保持两个空行)
class ZooAnimal:
def __init__(self, new_name):
self.name = new_name
animal1 = ZooAnimal("monkey")
print(animal1.name)
定义类时其他常见的内置方法:
__del__
:当一个对象从内存中销毁前,会自动调用该方法__str__
:利用print函数打印对象时,默认会返回对象引用的地址,如果不想看到默认输出的内容,可以自定义要输出的字符串
3.类的属性和方法
类对象在内存中只有一个,而实例对象可以有很多个,类对象可以有自己独有的属性和方法,访问方式和对象相同,如下定义类:
class ZooAnimal(object):
count = 0
def __init__(self, name):
self.name = name
ZooAnimal.count += 1
animal1 = ZooAnimal("mokey")
animal2 = ZooAnimal("tiger")
print(ZooAnimal.count)
print(animal1.count)
访问对象属性和方法时遵循向上搜索原则,即优先查看对象中有无相应属性,如果没有则向上搜索类中有无相应属性,直到搜索到为止。
ZooAnimal.count
和animal1.count
的值都是2,对象本身没有count属性,所以访问对象的count属性就相当于访问类的count属性,但这样容易引起混淆,不推荐这样做,而且这时如果通过对象.属性 = 属性值
给属性赋值,此时不会遵循向上搜索原则,而会将属性临时添加到对象里,而不是类里。
3.私有属性和私有方法
如果对象的某些属性和方法,只希望在对象内部访问使用,而不希望外部调用,则可以使用私有属性和私有方法。
只需要在定义类时在属性或方法前加__
即可,这样外部就不能直接访问了,但实际上Python中并没有真正意义上的私有,Python只不过将私有属性和私有方法名称做了转换:__名称
–>_类名__名称
,访问_类名__名称
一样可以访问到私有属性和方法。
注:区分私有属性和方法 (如:__name
, __addr
)、内置属性和方法 (如:__init__
, __del__
)
(三) 类方法与静态方法
1.定义类方法
类方法需要用修饰器@classmethod
来标识,告诉解释器这时一个类方法,如:
@classmethod
def class_function(cls):
pass
类方法的第一个参数应该是cls
:
- 由哪一个类调用的方法,方法内的
cls
就是哪一个类的引用,与实例方法的self
相似 - 使用其他名称也可以,不过习惯上使用
cls
在方法外部调用时,通过类名调用类的属性或类方法,不需要传递cls
参数;在方法内部调用时,可以通过类名也可以通过cls
调用类的属性或类方法。
2.定义类静态方法
需要在类中封装一个方法,既不需要访问实例属性或调用实例方法,也不需要访问类属性或者调用类方法,这个时候可以封装成一个静态方法,静态方法无需传递任何参数,如:
@staticmethod
def static_function():
pass
需要@staticmethod
标识,告诉解释器这是一个静态方法,同类方法一样,需要通过类名调用静态方法,如下例:
-
属性:
定义一个类属性top_score记录游戏历史最高分
定义一个实例属性player_name记录游戏玩家姓名 -
方法:
静态方法:show_help显示游戏帮助信息
类方法:show_top_score显示历史最高分
实例方法:start_name开始当前玩家的游戏
class Player(object):
top_score = 0
@staticmethod
def show_help():
print("Help manual:")
print("Unfinished...")
@classmethod
def show_top_score(cls):
print("Max score is {}".format(cls.top_score))
def __init__(self, name):
self.player_name = name
self.start_flag = False
self.score = 0
def start_name(self):
print("Game begin!")
self.start_flag = True
def get_score(self):
if self.player_name:
self.score += 1
if self.score > Player.top_score:
Player.top_score = self.score
else:
print("{} has not started game!")
player1 = Player("Jim")
player1.start_name()
player1.get_score()
player2 = Player("Bob")
player2.start_name()
print("Highest score is {}".format(Player.top_score))
(四) 继承与多态
1.继承
子类继承父类,子类拥有父类所有的方法和属性,这样在开发时无需再次开发与父类相同的属性和方法,直接添加子类特有的属性和方法即可,简化了开发流程。
子类是父类的派生类,父类是子类的基类。继承可以分为单继承和多继承:
(1) 单继承
子类只拥有一个父类的情况为单继承:
class ClassName(SuperClass):
pass
-
继承具有传递性,子类具有父类以及父类的父类的属性和方法,层层传递
-
方法的重写:
① 当父类的方法不能满足子类的需求,当其完全不相同时,要重写父类的方法,重新定义方法
② 当子类的方法是父类原有功能的扩展时,仍需要重写父类的方法,在需要的位置用
super().父类方法
来调用父类方法的执行,其他位置再针对子类特殊的需求编写新的代码 -
子类对象不能访问父类的私有属性和私有方法,但是子类对象可以通过父类的公有方法间接访问父类的私有属性和私有方法
(2) 多继承
子类可以拥有多个父类的情况为多继承,这种情况子类会具有所有父类的属性和方法:
class ClassName(SuperClass1, SuperClass2...):
pass
-
注意不同父类之间不要存在同名的属性和方法,避免混淆。如果有,就不要用多继承
-
__mro__
即method resolution order
,类的内置方法,内容为方法解析优先级顺序,对象调用方法时会按照优类的先级顺序查找,在优先级高的类中找到方法后就不再向下寻找,可以发现最后一个类是object,它是所有对象的基类
(3) 新式类和经典类
-
新式类:以
object
为基类的类,Python3解释器中使用,在定义类时默认以object
为基类,这样创建的对象会继承有很多内置属性和方法 -
经典类:不以
object
为基类的类,Python2解释器中使用,如果定义类时括号里没有object
,则是一个经典类,此时由经典类创建的对象并不会有很多的内置属性和方法。考虑到代码兼容性,建议在定义类时统一加上object
2.多态
多态以继承和重写父类方法为前提,使得不同的对象调用同一个接口,表现出不同的状态,增加代码的灵活性,是调用方法的技巧,不会影响类的内部设计。
本质是因为子类会重写父类的方法,因此父类和子类创建的对象其属性和方法不完全相同,例如:
class Animal(object):
def __init__(self, name):
self.animal_name = name
def say(self):
print("I am %s." % self.animal_name)
class Duck(Animal):
def say(self):
print("I am %s,I love swimming." % self.animal_name)
class Dog(Animal):
def say(self):
print("I am %s,I love running." % self.animal_name)
def animal_say(object):
object.say()
duck = Duck("YaYa")
dog = Dog("Wang")
animal_say(duck)
animal_say(dog)
输出结果:
I am YaYa,I love swimming.
I am Wang,I love running.
可见同一个接口,因为传入参数的不同而返回了不同的结果。
(五) 单例设计模式
1.单例设计模式
让类创建的对象,在系统中只有唯一的一个实例,这样每一次执行类名()
返回的对象,内存地址是相同的,应用场景如音乐播放对象、回收站对象等。
2.__new__方法
使用类名()
创建对象时,Python解释器会调用__new__
方法为对象分配空间,__new__
是一个由object
基类提供的内置静态方法,作用有两个:
- 一是分配空间
- 二是返回对象的引用
Python解释器获得对象的引用后,将引用作为第一个参数,传递给__init__
方法继续进行初始化工作,单例设计需要通过重写__new__
方法,实现每次创建新对象时程序不会新开辟内存空间。
注意:__new__
方法重写时必须返回对象的引用,不然后续__init__
初始化工作不会正常进行。
3.Python中的单例
实现思路:定义一个类属性,初始值None,创建对象时,如果类属性为None,则调用 __new__
方法,不是None时,直接返回类属性中记录的对象引用,例如:
class MusicPlayer(object):
instance = None
@staticmethod
def __new__(cls, *args, **kwargs):
if cls.instance == None:
cls.instance = super().__new__(cls)
return cls.instance
else:
return cls.instance
player1 = MusicPlayer()
player2 = MusicPlayer()
print(player1, player2)
通过输出结果可以发现player1和player2对象的引用地址是相同的。
如果希望初始化工作只执行一次,则可以定义一个类属性init_flag用来标记是否执行过初始化工作,如果执行过则return,没执行过则执行初始化工作,如下:
class MusicPlayer(object):
instance = None
init_flag = False
@staticmethod
def __new__(cls, *args, **kwargs):
if instance == None:
cls.instance = super().__new__(cls)
return cls.instance
else:
return cls.instance
def __init__(self):
if init_flag == False:
return
MusicPlayer.init_flag = True
初始化工作...
二、模块
(一) 基础概念
模块是Python程序架构一个核心概念:
-
每一个以py结尾的Python源码都可以是一个模块
-
在模块中定义的全局变量、函数、类都是提供给外界直接使用的工具
-
模块好比工具包,要想使用,首先要导入
(二) 导入方式
1.import 导入
import complex_module1
import complex_module2 as CModule
complex_module1.func1()
CModule.func2()
在导入时,每个导入独占一行,导入之后,通过module.func()
调用模块提供的工具 (全局变量、函数、类) 。可以使用as
指定模块的别名,如import complex_module as CModule
,模块命令建议使用大驼峰命名法
2.from import 导入
只希望从模块中导入部分工具,这样导入的工具可以直接使用,不用加module.
的方式调用
from module1 import func1()
func1()
注意:如果两个模块存在同名函数,那么后导入模块的函数,会覆盖掉先导入的函数。为避免变量或方法冲突,不推荐使用from module1 import *
(三) 开发模块
1.查找顺序
Python解释器在查找模块时:
-
首先在当前目录查找模块名,如果有就直接导入
-
如果没有就搜索系统目录
因此在开发时,尽量避免和系统的模块文件重名,不然可能导致模块不能正常导入。Python中每一个模块都有一个内置属性__file__
,可以查看模块的完整路径。
2.开发原则
模块的作用主要是向外提供工具 (全局变量、函数、类):
-
一个独立的Python文件就是一个模块
-
在导入模块时,模块中所有没有任何缩进的代码,都会被执行一遍
3.开发测试
有时开发人员会在模块下方增加一些测试代码,这些代码尽在模块内测试使用,导入其他文件中时不需要执行,这时可以利用Python的内置属性__name__
,测试模块的代码,只在测试情况下被执行,而在被导入时不会被执行,如下:
# 导入模块
# 定义全局变量
# 定义类
# 定义函数
...
# main()函数是测试代码
def main():
...
pass
if __name__ == "__main__":
main()
(四) 发布模块
1.包的概念
-
包是一个包含多个模块的特殊目录
-
目录下有一个特殊的文件
__init__.py
-
包名的命名方式和变量名一致,采用下划线命名法
这样import 包名
可以一次性导入包中所有的模块,要在外界使用包中的模块,需要在__init__.py
中指定对外界提供的模块列表,如包名为message,该目录下__init__.py
内容为:
# 从当前目录message导入模块列表
from . import send_message
from . import receive_message
在pycharm中新建包可以有两种方式
-
自己新建目录,然后建立
__init__.py
-
选择
新建包
功能,自动新建__init__.py
这样直接导入包,就可以导入包中的所有模块:
import message
message.send_message.function()
2.发布模块
(1) 制作发布压缩包的步骤
首先将所有模块文件放在包目录下,然后进行以下步骤:
① 创建setup.py
包含发布模块的元数据,主要包括包名称、版本号、作者、模块名等,代码内容较为固定,如下所示:
from distutils.core import setup
setup(
name='message', # 指定包名称
version='1.0', # 指定版本号
description='Send and receive message', # 描述
author='Bob', # 开发作者
author_email='110110110@qq.com', # 作者邮箱
url='https://blog.csdn.net/Captain_RB/article/details/123123.html', # 项目地址
py_modules=['message.send_message',
'message.receive_message'] # 指定模块名
)
② 构建模块
现在包目录下包含两类文件:
- Python模块文件
- setup.py元数据文件
接下要利用Python自带的发布工具制作发布文件,在包目录下执行以下命令进行构建模块:
python3 setup.py build
③ 生成发布压缩包
然后生成发布压缩包:
python3 setup.py sdist
(2) 安装模块
方法一:源码安装
也就是将上述发布的包安装在主机上:
tar -zxvf message-1.0.tar.gz
python3 setup.py install
这样安装后会将包的路径添加到python解析器中的系统路径,这样就可以直接使用包以及包中的模块。
方法二:pip安装
pip是通用的Python包管理工具,提供了包的查找、下载、安装、卸载等功能,常用命令如下:
# 将模块安装到python2环境
pip install ipython
pip uninstall ipython
# 将模块安装到python3环境
pip3 install ipython
pip3 uninstall ipython
方法三:单文件拷贝
直接把模块文件拷贝到$python_install_dir/Lib(不推荐使用)
(3) 卸载模块
方法一:删除目录
直接从安装目录下,把包的目录删除,可以通过包名.__file__
来查看包的路径。
方法二:pip删除
通过pip
安装的包,也可以使用pip
命令直接删除:
pip list
:查看目前已经安装的包pip uninstall 包名
:卸载包
三、异常
(一) 基础概念
-
程序运行时,如果Python解释器遇到错误,会停止执行,并提示错误信息,这就是异常
-
程序停止执行并且提示错误信息的动作,称之为:抛出 (raise) 异常
作用:程序开发很难将所有情况都处理的完美,通过异常捕获可以针对异常事件做集中处理,从而保证程序的稳定性和健壮性。这个异常时抛给程序处理,不是给用户处理。
(二) 捕获异常
1.简单的捕获语句
如果对代码不能确定是否正确执行,则可以通过try
语句来捕获异常,代码不出错则会跳过except正常执行
try:
尝试执行的代码
except:
出现错误的处理
2.针对错误类型捕获
可以针对不同错误类型的异常,做出不同的处理。预知到所有的错误很难,如果希望程序无论出现任何错误,都不会因为Python解释器抛出异常而终止,可以在所有可预知异常代码后再增加一个except
,除此之外,继except
之后可以用else
添加没有异常才会执行的代码,用finally
添加无论是否异常,都会执行的代码,以下是异常完整的代码:如下所示:
try:
尝试执行的代码
except 错误类型1:
处理代码1
except (错误类型2, 错误类型3):
处理代码2、3
except Exception as result:
print("未知错误:%s" % result)
else:
没有异常才会执行的代码
finally:
无论是否异常,都会执行的代码
其中Exception
是Python针对异常提供的类,result
可以名字可以随便,通过result
可以捕获到未知异常,并且不会导致程序崩溃,except具体错误类型为从Python解释器报错的最后一行错误信息的第一个单词。
(三) 异常的传递
函数/方法执行出现异常,会将异常传递给函数/方法的调用一方,如果传递到主程序,仍然没有异常处理,程序才会终止。
在开发中,可以只在主函数中添加异常捕获,而在主函数中调用其他函数,只要出现异常,都会传递到主函数的异常捕获中,这样就不需要在每个函数代码中增加大量的异常捕获,保证代码简洁性。
比如以下代码:
def demo1():
print(input("Please input a num:"))
def demo2():
demo1()
demo2()
如果print(input("Please input a num:"))
出错,那么demo1会将异常传递给demo2,没有异常处理然后继续提交给主程序。
(四) 主动抛出异常
在开发中,除了代码执行错误Python解释器会抛出异常外,还可以根据应用程序特有的业务需求主动抛出(raise)异常,比如用户输入不符合要求等。
自定义抛出异常,可以:
-
创建一个Exception的对象(Exception是Python中提供的一个异常类)
-
使用raise关键字抛出异常对象
def input_passwd():
passwd = input("请输入密码:(密码长度大于8位)")
if len(passwd) >= 8:
return passwd
exc_psswd = Exception("密码复杂程度不符合要求")
raise exc_passwd