python基础-笔记
来源:互联网 发布:win7阻止安装软件 编辑:程序博客网 时间:2024/06/07 21:53
- 注释
- 单行注释
- 多行注释
- 中文支持
- 变量
- 输出
- 格式化输出
- 换行输出
- 输出后不换行
- 输入
- raw_input
- input
- 运算符
- 算术运算符
- 赋值运算符
- 复合赋值运算符
- 比较关系运算符
- 逻辑运算符
- 数据类型转换
- if 语句
- while 循环
- for 循环
- break和continue
- break
- continue
- 注意点
- 字符串
- 切片
- 字符串常见操作
- find
- index
- count
- replace
- split
- strcapitalize
- strtitle
- strstartswithobj
- strendswithobj
- strlower
- strupper
- strljustwidth
- strrjustwidth
- strcenterwidth
- strlstrip
- strrstrip
- strstrip
- rfind
- rindex
- mystrpartitionstr
- mystrrpartitionstr
- strsplitlines
- strisalpha
- strisdigit
- strisalnum
- strisspace
- mystrjoinstr
- 列表
- 添加元素 append extend insert
- 修改元素
- 查找元素 in not in index count
- 删除元素 delpopremove
- 排序
- 元组
- 访问元组
- 元组内置函数 count index
- 字典
- 访问字典
- 修改添加元素
- 删除元素
- 字典常见操作
- 字符串字典元组列表公共方法
- python内置函数
- 引用
- 可变类型与不可变类型
- 函数
- 函数的文档说明
- 全局变量
- 在函数中修改全局变量那么就需要使用global进行声明
- 可变类型的全局变量
- 函数返回多个值
- 函数参数
- 缺省参数
- 不定长参数
- 引用传参
- 匿名函数
- 注释
- 文件操作
- 打开文件
- 关闭文件
- 文件读写
- 写数据
- 读数据 read
- 读数据 readlines
- 读数据 readline
- 文件随机读写
- 获取当前读写的位置 tell
- 定位到某位置 seek
- 文件重命名
- 删除文件
- 创建文件夹
- 获取当前目录
- 改变默认目录
- 获取目录列表
- 删除文件夹
- 类对象
- 定义类
- 创建对象
- init
- str
- self
- _del
- 继承
- mro
- 调用父类的方法
- 类属性实例属性
- 类方法
- 静态方法
- new
- 单例
- 创建单例-保证只有1个对象
- 创建单例时只执行1次init方法
- 异常
- 捕获异常
- 异常嵌套
- 抛出自定义的异常
- super__init
- 模块
- import
- from import
- as
- 定位模块
- name
- all
- 包
注释
单行注释
# 我是注释
多行注释
``` 多行注释 ```
中文支持
#coding=utf-8 写在程序开头
变量
- Numbers数字
- int 整型
- long 长整型
- float 浮点型
- complex 复数
- 布尔类型
- True
- False
- String 字符串
- List 列表
- Tuple 元组
- Dictionary 字典
查看变量类型 type(变量名)
输出
print()
格式化输出
age=10print("我是%d"%age)
换行输出
print("asdf\n")
输出后不换行
print("asdf",end="")
输入
raw_input()
- raw_input()的小括号中放入的是,提示信息,用来在获取数据之前给用户的一个简单提示
- raw_input()在从键盘获取了数据以后,会存放到等号右边的变量中
- raw_input()会把用户输入的任何值都作为字符串来对待
- python3版本中没有,只有input(),并且 python3中的input与python2中的raw_input()功能一样
input()
- 接受的输入必须是表达式
- 接受表达式输入,并把表达式的结果赋值给等号左边的变量
- python3版本的input()与python2的raw_input()功能一样
运算符
算术运算符
赋值运算符
复合赋值运算符
比较(关系)运算符
=检查左操作数的值是否大于或等于右操作数的值,如果是,则条件成立。 如a=3,b=3则(a >= b) 为 true. <= 检查左操作数的值是否小于或等于右操作数的值,如果是,则条件成立。 如a=3,b=3则(a <= b) 为 true.
逻辑运算符
数据类型转换
if 语句
if 判断条件: passelif 判断条件: passelse: pass
while 循环
while 条件: pass
for 循环
for 临时变量 in 列表或者字符串等: 循环满足条件时执行的代码else: 循环不满足条件时执行的代码
break和continue
break
- 用来结束整个循环
continue
- 用来结束本次循环,紧接着执行下一次的循环
注意点
- break/continue只能用在循环中,除此以外不能单独使用
- break/continue在嵌套循环中,只对最近的一层循环起作用
字符串
- 双引号或者单引号中的数据,就是字符串
- 字符串实际上就是字符的数组,所以支持下标索引。
name = 'abcdef'print(name[0])
切片
- 切片是指对操作的对象截取其中一部分的操作。字符串、列表、元组都支持切片操作。
变量[起始:结束:步长]
- 注意:选取的区间属于左闭右开型,即从”起始”位开始,到”结束”位的前一位结束(不包含结束位本身)。
- 步长默认为1,可负值
字符串常见操作
如有字符串mystr = 'hello world ',以下是常见的操作
find
- 检测 str 是否包含在 mystr中,如果是返回开始的索引值,否则返回-1
mystr.find(str, start=0, end=len(mystr))
index
- 跟find()方法一样,只不过如果str不在 mystr中会报一个异常.
mystr.index(str, start=0, end=len(mystr))
count
- 返回 str在start和end之间 在 mystr里面出现的次数
mystr.count(str, start=0, end=len(mystr))
replace
- 把 mystr 中的 str1 替换成 str2,如果 count 指定,则替换不超过 count 次.不指定就全换
mystr.replace(str1,str2,count)
split
- 以 str 为分隔符切片 mystr,如果 maxsplit有指定值,则仅分隔 maxsplit 个子字符串
mystr.split(str=" ", maxsplit)
str.capitalize()
- 把字符串的第一个字符大写
str.title()
- 把字符串的每个单词首字母大写
str.startswith(obj)
- 检查字符串是否是以 obj 开头, 是则返回 True,否则返回 False
str.endswith(obj)
- 检查字符串是否以obj结束,如果是返回True,否则返回 False.
str.lower()
- 转换 str 中所有大写字符为小写
str.upper()
- 转换 str 中的小写字母为大写
str.ljust(width)
- 返回一个原字符串左对齐,并使用空格填充至长度 width 的新字符串
str.rjust(width)
- 返回一个原字符串右对齐,并使用空格填充至长度 width 的新字符串
str.center(width)
- 返回一个原字符串居中,并使用空格填充至长度 width 的新字符串
str.lstrip()
- 删除 mystr 左边的空白字符
str.rstrip()
- 删除 mystr 字符串末尾的空白字符
str.strip()
- 删除mystr字符串两端的空白字符
rfind
- 类似于 find()函数,不过是从右边开始查找.
mystr.rfind(str, start=0,end=len(mystr) )
rindex
- 类似于 index(),不过是从右边开始.
mystr.rindex( str, start=0,end=len(mystr))
mystr.partition(str)
- 把mystr以str分割成三部分,str前,str和str后
mystr.rpartition(str)
- 类似于 partition()函数,不过是从右边开始.
str.splitlines()
- 按照行分隔,返回一个包含各行作为元素的列表
str.isalpha()
- 如果 mystr 所有字符都是字母 则返回 True,否则返回 False
str.isdigit()
- 如果 mystr 只包含数字则返回 True 否则返回 False.
mystr.isdigit()
str.isalnum()
- 如果 mystr 所有字符都是字母或数字则返回 True,否则返回 False
str.isspace()
- 如果 mystr 中只包含空格,则返回 True,否则返回 False.
mystr.join(str)
- mystr 中每个字符后面插入str,构造出一个新的字符串
列表
mlist=['asd',1,True]
添加元素 append, extend, insert
mlist.append('a')mlist.extend([3,4])#通过extend可以将另一个集合中的元素逐一添加到列表中mlist.insert(index,object)#在指定位置index前插入元素object
修改元素
mlist[0]='a'# 修改元素的时候,要通过下标来确定要修改的是哪个元素,然后才能进行修改
查找元素 in, not in, index, count
if 'a' in mlist: pass#in(存在),如果存在那么结果为true,否则为false#not in(不存在),如果不存在那么结果为true,否则false
mlist.index('a', 1, 3)# 注意是左闭右开区# index和count与字符串中的用法相同
删除元素 del,pop,remove
del mlist[0]mlist.pop()mlist.remove('abc')
排序
- sort方法是将list按特定顺序重新排列,默认为由小到大,参数reverse=True可改为倒序,由大到小。
mlist.sort(reverse=True)
元组
- 元组与列表类似,不同之处在于元组的元素不能修改。元组使用小括号,列表使用方括号。
访问元组
tuple=('a',2,3.23,4)tuple[2]
元组内置函数 count, index
- index和count与字符串和列表中的用法相同
字典
mdict = {'name':'libai','id':1}
访问字典
mdict['name']mdict.get('name')#若info中不存在'name'则返回Nonemdict.get('name','abc')#若info中不存在'name'则返回默认值'abc'
修改、添加元素
mdict['id']=100
删除元素
del mdict['id'] #删除指定的元素del mdict #删除整个字典mdict.clear() #清空整个字典
字典常见操作
len(mdict) #键值对的个数mdict.keys() #返回包含字典所有key的列表mdict.values() #返回包含字典所有value的列表mdict.items() #返回包含所有(键,值)元组的列表mdict.has_key('name') #如果key在字典中,返回True
字符串、字典、元组、列表公共方法
python内置函数
- cmp在比较字典数据时,先比较键,再比较值。
- len在操作字典数据时,返回的是键值对个数。
引用
- 在python中,值是靠引用来传递来的。
可变类型与不可变类型
可变类型,值可以改变:
- 列表 list
- 字典 dict
不可变类型,值不可以改变:
- 数值类型 int, long, bool, float
- 字符串 str
- 元组 tuple
函数
def 函数名(): 代码# 函数名不能重复
函数的文档说明
def test(a,b): "啦啦啦" passhelp(test) #能够看到test函数的相关说明
全局变量
- 在函数外边定义的变量叫做全局变量
- 全局变量能够在所有的函数中进行访问
- 如果在函数中修改全局变量,那么就需要使用global进行声明,否则出错
- 如果全局变量的名字和局部变量的名字相同,那么使用的是局部变量的,小技巧强龙不压地头蛇
在函数中修改全局变量,那么就需要使用global进行声明
i = 10def test(): global i i = 100
可变类型的全局变量
- 在函数中不使用global声明全局变量时不能修改全局变量的本质是不能修改全局变量的指向,即不能将全局变量指向新的数据。
- 对于不可变类型的全局变量来说,因其指向的数据不能修改,所以不使用global时无法修改全局变量。
- 对于可变类型的全局变量来说,因其指向的数据可以修改,所以不使用global时也可修改全局变量。
函数返回多个值
- 本质是利用了元组
函数参数
缺省参数
调用函数时,缺省参数的值如果没有传入,则被认为是默认值。
def test(name='hehe'): pass
- 带有默认值的参数一定要位于参数列表的最后面
不定长参数
def test(a,b,*args,**kwargs): pass
- 加了星号()的变量args会存放所有未命名的变量参数,args为元组;而加*的变量kwargs会存放命名参数,即形如key=value的参数, kwargs为字典。
引用传参
- Python中函数参数是引用传递(注意不是值传递)。对于不可变类型,因变量不能修改,所以运算不会影响到变量自身;而对于可变类型来说,函数体中的运算有可能会更改传入的参数变量。
匿名函数
lambda [arg1 [,arg2,.....argn]]:expression
- Lambda函数能接收任何数量的参数但只能返回一个表达式的值
- 匿名函数不能直接调用print,因为lambda需要一个表达式
文件操作
打开文件
open('文件名', '访问模式')
关闭文件
f = open('j.txt','w')f.close()
文件读写
写数据
f = open('test.txt', 'w')f.write('hello world')f.close()
读数据 read
- 使用read(num)从文件中读取数据,num表示要从文件中读取数据的长度(单位是字节),如果没有传入num,那么就表示读取文件中所有的数据
f = open('test.txt', 'r')content = f.read(5)print(content)
- 如果使用读了多次,那么后面读取的数据是从上次读完后的位置开始的
读数据 readlines
- 就像read没有参数时一样,readlines可以按照行的方式把整个文件中的内容进行一次性读取,并且返回的是一个列表,其中每一行的数据为一个元素
读数据 readline
- 只读一行
文件随机读写
获取当前读写的位置 tell
position = f.tell()
定位到某位置 seek
seek(offset, from)有2个参数offset:偏移量from:方向0:表示文件开头1:表示当前位置2:表示文件末尾
文件重命名
import osos.rename(需修改的文件名, 新的文件名)
删除文件
import osos.remove(待删除的文件名)
创建文件夹
import osos.mkdir('test')
获取当前目录
import osos.getcwd()
改变默认目录
import osos.chdir("../")
获取目录列表
import osos.listdir("./")
删除文件夹
import osos.rmdir('test')
类、对象
定义类
class 类名: 方法列表
创建对象
对象名 = 类名()
init()
class 类名: #初始化函数,用来完成一些默认的设定 def __init__(self): pass
- 在创建一个对象时默认被调用,不需要手动调用
- 默认有1个参数名字为self,如果在创建对象时传递了2个实参,那么init(self)中出了self作为第一个形参外还需要2个形参,例如init(self,x,y)
- init(self)中的self参数,不需要开发者传递,python解释器会自动把当前的对象引用传递进去
str()
- 当使用print输出对象的时候,只要自己定义了str(self)方法,那么就会打印从在这个方法中return的数据
self
- 所谓的self,可以理解为自己
- 可以把self当做C++中类里面的this指针一样理解,就是对象自身的意思
- 某个对象调用其方法时,python解释器会把这个对象作为第一个参数传递给self,所以开发者只需要传递后面的参数即可
### 保护对象的属性- Python中没有像C++中public和private这些关键字来区别公有属性和私有属性
- 它是以属性命名方式来区分,如果在属性名前面加了2个下划线’__’,则表明该属性是私有属性,否则为公有属性(方法也是一样,方法名前面加了2个下划线的话表示该方法是私有的,否则为公有的)。
_del()
- 当删除一个对象时,python解释器也会默认调用一个方法,这个方法为del()方法
- 当有1个变量保存了对象的引用时,此对象的引用计数就会加1
- 当使用del删除变量指向的对象时,如果对象的引用计数不为1,比如3,那么此时只会让这个引用计数减1,即变为2,当再次调用del时,变为1,如果再调用1次del,此时会真的把对象进行删除
继承
- 子类在继承的时候,在定义类时,小括号()中为父类的名字
- 父类的属性、方法,会被继承给子类
- 私有的属性,不能通过对象直接访问,但是可以通过方法访问
- 私有的方法,不能通过对象直接访问
- 私有的属性、方法,不会被子类继承,也不能被访问
- 一般情况下,私有的属性、方法都是不对外公布的,往往用来做内部的事情,起到安全的作用
mro
obj_C = C()print(C.__mro__) #可以查看C类的对象搜索方法时的先后顺序
调用父类的方法
class B(Cat): def __init__(self,name): # 调用父类的方法(python2) Cat.__init__(self.name) # super().__init__(name)
类属性、实例属性
- 类属性就是类对象所拥有的属性,它被所有类对象的实例对象所共有,在内存中只存在一个副本,这个和C++中类的静态成员变量有点类似。对于公有的类属性,在类外可以通过类对象和实例对象访问
- 如果需要在类外修改类属性,必须通过类对象去引用然后进行修改。如果通过实例对象去引用,会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性,并且之后如果通过实例对象去引用该名称的属性,实例属性会强制屏蔽掉类属性,即引用的是实例属性,除非删除了该实例属性。
类方法
- 是类对象所拥有的方法,需要用修饰器@classmethod来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数(当然可以用其他名称的变量作为其第一个参数,但是大部分人都习惯以’cls’作为第一个参数的名字,就最好用’cls’了),能够通过实例对象和类对象去访问。
- 类方法还有一个用途就是可以对类属性进行修改
#类方法,用classmethod来进行修饰@classmethoddef getCountry(cls): return cls.country@classmethoddef setCountry(cls,country): cls.country = country
静态方法
- -
@staticmethoddef getCountry(): return People.country
- 从类方法和实例方法以及静态方法的定义形式就可以看出来,类方法的第一个参数是类对象cls,那么通过cls引用的必定是类对象的属性和方法;而实例方法的第一个参数是实例对象self,那么通过self引用的可能是类属性、也有可能是实例属性(这个需要具体分析),不过在存在相同名称的类属性和实例属性的情况下,实例属性优先级更高。静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类对象来引用
new()
- new至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供
- new必须要有返回值,返回实例化出来的实例,这点在自己实现new时要特别注意,可以return父类new出来的实例,或者直接是object的new出来的实例
- init有一个参数self,就是这个new返回的实例,init在new的基础上可以完成一些其它初始化的动作,init不需要返回值
- 我们可以将类比作制造商,new方法就是前期的原材料购买环节,init方法就是在有原材料的基础上,加工,初始化商品环节
单例
- 确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,单例模式是一种对象创建型模式。
创建单例-保证只有1个对象
# 实例化一个单例class Singleton(object): __instance = None def __new__(cls, age, name): #如果类数字能够__instance没有或者没有赋值 #那么就创建一个对象,并且赋值为这个对象的引用,保证下次调用这个方法时 #能够知道之前已经创建过对象了,这样就保证了只有1个对象 if not cls.__instance: cls.__instance = object.__new__(cls) return cls.__instance
创建单例时,只执行1次init方法
# 实例化一个单例class Singleton(object): __instance = None __first_init = False def __new__(cls, age, name): if not cls.__instance: cls.__instance = object.__new__(cls) return cls.__instance def __init__(self, age, name): if not self.__first_init: self.age = age self.name = name Singleton.__first_init = True
异常
- 当Python检测到一个错误时,解释器就无法继续执行了,反而出现了一些错误的提示,这就是所谓的”异常”
捕获异常
try: passexcept Exception as result: #as result存储异常的基本信息 pass #捕获到异常else: pass #没有捕获到异常finally: pass #无论异常是否产生都要执行
- 当捕获多个异常时,可以把要捕获的异常的名字,放到except 后,并使用元组的方式仅进行存储
异常嵌套
- 如果try嵌套,那么如果里面的try没有捕获到这个异常,那么外面的try会接收到这个异常,然后进行处理,如果外边的try依然没有捕获到,那么再进行传递。。。
- 如果一个异常是在一个函数中产生的,例如函数A—->函数B—->函数C,而异常是在函数C中产生的,那么如果函数C中没有对这个异常进行处理,那么这个异常会传递到函数B中,如果函数B有异常处理那么就会按照函数B的处理方式进行执行;如果函数B也没有异常处理,那么这个异常会继续传递,以此类推。。。如果所有的函数都没有处理,那么此时就会进行异常的默认处理,即通常见到的那样
- 当调用test3函数时,在test1函数内部产生了异常,此异常被传递到test3函数中完成了异常处理,而当异常处理完后,并没有返回到函数test1中进行执行,而是在函数test3中继续执行
抛出自定义的异常
- 用raise语句来引发一个异常。异常/错误对象必须有一个名字,且它们应是Error或Exception类的子类
super().__init()
- 这一行代码,可以调用也可以不调用,建议调用,因为init方法往往是用来对创建完的对象进行初始化工作,如果在子类中重写了父类的init方法,即意味着父类中的很多初始化工作没有做,这样就不保证程序的稳定了,所以在以后的开发中,如果重写了父类的init方法,最好是先调用父类的这个方法,然后再添加自己的功能
模块
import
import module1,mudule2...#在调用math模块中的函数时,必须这样引用:模块名.函数名
from … import
- 让你从模块中导入一个指定的部分到当前命名空间中
from modname import name1[, name2[, ... nameN]]#不仅可以引入函数,还可以引入一些全局变量、类等
- 通过这种方式引入的时候,调用函数时只能给出函数名,不能给出模块名,但是当两个模块中含有相同名称函数的时候,后面一次引入会覆盖前一次引入。也就是说假如模块A中有函数function( ),在模块B中也有函数function( ),如果引入A中的function在先、B中的function在后,那么当调用function函数的时候,是去执行模块B中的function函数。
- 如果想一次性引入math中所有的东西,还可以通过from math import *来实现
as
import time as tt#给模块起别名
定位模块
当你导入一个模块,Python解析器对模块位置的搜索顺序是:
1.当前目录
2.如果不在当前目录,Python则搜索在shell变量PYTHONPATH下的每个目录。
3.如果都找不到,Python会察看默认路径。UNIX下,默认路径一般为/usr/local/lib/python/
4.模块搜索路径存储在system模块的sys.path变量中。变量里包含当前目录,PYTHONPATH和由安装过程决定的默认目录。
name
- 可以根据name变量的结果能够判断出,是直接执行的python脚本还是被引入执行的,从而能够有选择性的执行测试代码
- 直接运行py文件,name==”main”
- 在其他文件中import此文件,name==”被导入的py文件名”
all
- 如果文件中有all变量,就意味着这个变量的元素,才能被from xxx import * 时导入
__all__ = ["test1","test2"]
包
python中的包
- 引入包
1.1 有2个模块功能有些联系
1.2 所以将其放到同一个文件夹下
1.3 使用import 文件.模块 的方式导入
1.4 使用from 文件夹 import 模块 的方式导入
1.5 在msg文件夹下创建init.py文件
1.6 在init.py文件中写入
1.7 重新使用from 文件夹 import 模块 的方式导入
总结:
- 包将有联系的模块组织在一起,即放到同一个文件夹下,并且在这个文件夹创建一个名字为init.py 文件,那么这个文件夹就称之为包
有效避免模块名称冲突问题,让应用组织结构更加清晰
2. init.py文件有什么用
init.py 控制着包的导入行为
2.1 init.py为空
仅仅是把这个包导入,不会导入包中的模块
2.2 all
在init.py文件中,定义一个all变量,它控制着 from 包名 import *时导入的模块
2.3 (了解)可以在init.py文件中编写内容
可以在这个文件中编写语句,当导入时,这些语句就会被执行
init.py文件
- 扩展:嵌套的包
假定我们的包的例子有如下的目录结构:
Phone/ __init__.py common_util.py Voicedta/ __init__.py Pots.py Isdn.py Fax/ __init__.py G3.py Mobile/ __init__.py Analog.py igital.py Pager/ __init__.py Numeric.py
Phone 是最顶层的包,Voicedta 等是它的子包。 我们可以这样导入子包:
import Phone.Mobile.Analog
Phone.Mobile.Analog.dial()
你也可使用 from-import 实现不同需求的导入
第一种方法是只导入顶层的子包,然后使用属性/点操作符向下引用子包树:
from Phone import Mobile
Mobile.Analog.dial(‘555-1212’)
此外,我们可以还引用更多的子包:
from Phone.Mobile import Analog
Analog.dial(‘555-1212’)
事实上,你可以一直沿子包的树状结构导入:
from Phone.Mobile.Analog import dial
dial(‘555-1212’)
在我们上边的目录结构中,我们可以发现很多的 init.py 文件。这些是初始化模块,from-import 语句导入子包时需要用到它。 如果没有用到,他们可以是空文件。
包同样支持 from-import all 语句:
from package.module import *
然而,这样的语句会导入哪些文件取决于操作系统的文件系统。所以我们在init.py 中加入 all 变量。该变量包含执行这样的语句时应该导入的模块的名字。它由一个模块名字符串列表组成.。
- [Python] Python基础笔记
- Python基础笔记摘要
- Python 笔记 : 基础
- Python基础学习笔记
- [笔记]Python对象基础
- python 基础笔记
- Python基础笔记
- Python 基础笔记(1)
- Python 基础笔记(2)
- python基础笔记
- Python基础笔记
- python基础笔记1
- python基础笔记(一)
- python基础学习笔记
- Python基础学习笔记
- Python基础笔记
- Python基础语法笔记
- python基础笔记
- sudo: unable to resolve host [hostname](已解决)
- gprof使用介绍-优化程序性能
- 学习笔记TF059:自然语言处理、智能聊天机器人
- 【LintCode-655】大整数加法(Java实现)
- Tree形结构
- python基础-笔记
- Tarjan算法小结1——SCC
- Error: need a single repository as argument.(已解决)
- 20171103周测题
- Java编程思想第四版第三章练习
- oprofile性能分析优化程序
- Scala——基于Akka的并发编程和分布式应用程序开发
- MapReduce数据类型及自定义MapReduce数据类型
- physx 3.3.4中的 Revolute Joint