目录
引入
一、PEP8代码规范和风格
二、变量和数据
1、变量
2、运算符
三、三种程序结构
1、分支结构
2、循环结构
四、组合数据类型
1、列表(list)
2、元组(tuple)
3、字典(dict)
5、集合(set)
五、字符串与正则表达式
1、字符串基础
2、字符串方法进阶
3、正则表达式
引入
python语法查询:https://www.runoob.com/python/python-dictionary.html
>>> country=2
>>> print("china",country)
china 2
>>> print(country,end='')
2
>>> country[]={1,2,3,4,5}
SyntaxError: invalid syntax
>>> country[]=[1,2,3,4,5]
SyntaxError: invalid syntax
>>> country=[1,2,3,4,5]
>>> print("country",country)
country [1, 2, 3, 4, 5]
>>> object=input("请输入年龄:")
请输入年龄:21
>>> print(type(age))
Traceback (most recent call last):
File "<pyshell#8>", line 1, in <module>
print(type(age))
NameError: name 'age' is not defined
>>> print(type(object))
<class 'str'>
>>> object=int(input("请输入年龄:"))
请输入年龄:21
>>> print(type(object))
<class 'int'>
>>> #函数打印完后会自动换行,若不需要后加,end=‘’
>>> #有双引号或单引号的就当作字符串打印,没有则当作变量打印其内容(数值、字符、布尔、列表、字典等数据类型)
一、PEP8代码规范和风格
1、全局变量使用英文大写,单词之间使用下划线_
SCHOOL_NANE="BUCT"
全局变量一般只在模块内有效,实现方法:使用_ALL_机制或添加一个前置下划线
2、私有变量使用英文小写和一个前导下划线
_student_name
3、内置变量使用英文小写,两个前导下划线和两个后置下划线
__maker__
4、一般变量使用英文小写,单词之间使用下划线
class_name
注:变量第一个不能使用数字,不能使用python关键字或保留字符
pyrthon关键字
>>> keyword.kwlist
['False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
>>> keyword.iskeyword('pass')
True
5、函数名是英文小写,单词之间加下划线,提高可读写;不要使用·关键字、缩写等。
实例方法的第一个参数总是使用self
类方法的第一个参数总是使用cls
6、属性和类
类的命名遵循首字母大写规则
类的属性(方法和变量)命名使用全部小写,可用下划线
7、模块和包
模块命名使用简短的小写英文的方式,可用下划线
包的命名也用简短的小写英文,但不推荐使用下划线
8、规定
下列运算符前后都需使用一个空格:
= + - < > == >= <= and or not
下列运算符前后不使用空格:
* / **
二、变量和数据
python是面向对象(object)的编程语言,在python中一切皆为对象。对象是某类型具体实例中的某一个,每个对象都有身份、类型和值。
>>身份:身份(Identity)与对象是唯一对应关系,每一个对象的身份产生后就独一无二,无法改变。对象的ID是对象在内存中获取的一段地址的标识。
>>类型:类型(Type)是决定对象以那种数据类型进行存储的。
>>值:值(Value)存储对象的数据,某些情况下可以修改值,某些对象声明值过后就不可修改。
1、变量
变量:指向对象的值的名称,一种标识符,对内存中存储位置的命名。python中变量可不声明直接赋值使用,由于python使用动态类型(Dynamic Type),变量可根据赋值类型决定变量的数据类型。
数据类型:可自由改变变量数据类型的动态类型&变量事先说明的静态类型,动态类型更灵活。
①Numbers(数字类型):int、float、complex
不可改变的数据类型,如果改变就会分配一个新的对象。整型、浮点型、复数
②Strings(字符串类型):str
序列类型
③Lists(列表类型):list
序列类型
④Tuples(元组类型):tuple
序列类型
⑤Dictionaries(字典类型):dict
映射类型
⑥Sets(集合类型):set
集合类型
下面先简单介绍下整型、浮点型、复数、布尔类型、字符串类型:
(1)整型:int,包括正整数和负整数,python中为长整型,范围无限(只要内存足够大)。除了十进制,二进制0b、八进制0o、十六进制0x也使用整型,之间的进制可以转换:
>>> a=15
>>> print(bin(a))
0b1111
>>> print(oct(a))
0o17
>>> print(hex(a))
0xf
>>> s='010011101'
>>> print(int(s,2))
157
注:int(s,base)表示将字符串s按照base参数提供的进制转为十进制。内置函数input()输入时是字符串,需使用int()函数转换为整型。
(2)浮点类型:float,含有小数的数值,表示实数,由正负号、数字和小数点组成
fromhex(s):十六进制浮点数转换为十进制数
hex():以字符串形式返回十六进制的浮点数
is_integer():判断是否为小数,小数非0返回False,为0返回True
(3)复数类型:complex由实数和虚数组成,虚数部分加上j或J(其他语言一般没有)
(4)布尔类型:bool是整型的一个子类,用数值1和0表示常量的True和False。在Python中,False可以是数值为0、对象为None、序列中的空字符串、空列表、空元组等。
(5)字符串类型:str,使用一对单引号、双引号和三对单引号或双引号的字符就是字符串,如‘hello’、“hello”等,显然这里把字符串当作了对象的值,但严格地说字符串表示一种对象的类型。
拓:调用type(变量名)函数可查看变量的类型;调用help(函数名)函数可查看函数用途
2、运算符
算数运算符和逻辑运算符等,这里暂且省略,使用到可自行查看。
三、三种程序结构
顺序结构、分支结构、循环结构
1、分支结构
①单向条件
if 表达式:
语句块
②双向条件
if 表达式1:
语句块1
else 表达式2:
语句块2
③多向条件
if 表达式1:
语句块1
elif 表达式2:
语句块2
...
else 表达式n:
语句块n
条件嵌套(一定要控制好缩进),可表达更复杂的逻辑
2、循环结构
①for循环
for 变量 in 序列/迭代对象:
循环体(语句块1)
else:
语句块2
注:else只有在循环结束时才会执行,如果用break跳出就不会执行else部分,根据需要可省略else。range(起始数,终点数+1,步长)可用于生成一段数字序列,不指定起始默认从0开始,默认步长为1。
例1:1~100求和
>>> sum=0
>>> for s in range(1,101):
sum = sum+s
>>> print(sum)
5050
例2:删除1~10之间的所有偶数
>>> x=list(range(11))
>>> for i in x:
x.remove(i)
else:
print("奇数")
>>> print(x)
奇数
[1, 3, 5, 7, 9]
②for循环嵌套
例1:打印乘法表
>>> for i in range(1,10):
for j in range(1,i+1):
print(str(j)+'x'+str(i)+'='+str(j*i),end=' ')#转换为字符类型才能进行语句拼接,end使不换行
print('')#换行
1x1=1
1x2=2 2x2=4
1x3=3 2x3=6 3x3=9
1x4=4 2x4=8 3x4=12 4x4=16
1x5=5 2x5=10 3x5=15 4x5=20 5x5=25
1x6=6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36
1x7=7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49
1x8=8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64
1x9=9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81
③while循环
不知道循环次数,但知道循环条件时使用(注意python中没有do while)
while 循环条件:
循环体(语句块1)
else:
语句块2
注:break跳出所在层级循环,执行循环后的语句;continue表示结束本次循环,进行下一次循环
四、组合数据类型
列表、元组、字典、集合
1、列表(list)
序列类型,是任意对象的有序集合,通过”位置“或者”索引“访问其中元素,具有可变对象、可变长度、异构、任意嵌套的特点。(索引从0开始)
①创建列表
sample_list1 = [0,1,2,3,4]
sample_list2 = ["A","B","C","D","D"]
列表中允许有不同的数据类型元素:
sample_list3 = [0,"a",3,'python']
列表可嵌套使用:
sample_list4 = [sample_list1,sample_list2,sample_list3]
②使用列表
sample_list1 = [0,1,2,3,4]
print(sample_list1[1]) #1
>>> print(sample_list1[-2])#从列表右侧倒数第2个元素:3
del sample_list1[2]#以索引方式删除元素
sample_list1.remove('2')#直接以值的方式删除元素
del sample_list1 #删除整个列表
sample_list1=[] #清空列表
③列表中的内置函数&其他方法
>>> print(len(sample_list1))
5
>>> print(max(sample_list1))
4
>>> print(min(sample_list1))
0
>>> a = (0,1,2,3,4)
>>> print(list(a)) #将元组转换为列表
[0, 1, 2, 3, 4]
>>> sample_list1.append(5) #末尾添加
>>> print(sample_list1)
[0, 1, 2, 3, 4, 5]
>>> b = [6,7,8,9]
>>> sample_list1.extend(b) #扩展(序列合并)
>>> print(sample_list1)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> sample_list1.insert(0,3) #在特定索引位置插入元素
>>> print(sample_list1)
[3, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> sample_list1.reverse() #翻转元素顺序
>>> print(sample_list1)
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 3]
>>> print(sample_list1.count(3)) #计数元素出现次数
2
>>> print(sample_list1.index(8)) #获取元素所在索引(位置)
1
其他还有pop、remove、sort、copy、clear等
2、元组(tuple)
与列表一样属于序列类型,也是任意对象的有序集合,也通过索引(位置)访问其中元素,也具有可变长度、异构和任意嵌套的特点。与列表不同的是,元组中的元素是不可修改的。
①创建元组
sample_tuple1 = (0,1,2,3,4)
sample_tuple2 = ("p",'python',1989)
元素可以使各种可迭代的类型,也可以是空:
sample_tuple3 = ()
若只有一个元素,为避免歧义在元素后加逗号,否则括号会被当作运算符:
sample_tuple4 = (123,)
元组也可嵌套使用:
sample_tuple5 = (sample_tuple1,sample_tuple2)
②使用元组
也是利用索引访问(也是中括号[]):
>>> tuple = (1,2,3,4,5,6)
>>> print(tuple[2])
3
>>> print(tuple[-3])
4
支持切片操作:
>>> print(tuple[:]) #全部元素
(1, 2, 3, 4, 5, 6)
>>> print(tuple[2:4]) #索引为2和3的元素,不包括索引为4的
(3, 4)
>>> print(tuple[2:])
(3, 4, 5, 6)
>>> print(tuple[0:5:2])
(1, 3, 5)
删除元组:
del tuple
③元组的内置函数
len、max、min、tuple等,与列表相似,tuple(listname)把列表转换为元组
3、字典(dict)
字典属于映射类型,通过键实现元素的存取,具有无序、可变长度、异构、嵌套、可变类型容器等特点。
①创建字典
dict1 = {'Name':'Xiaohuang','City':'Beijing','School':'BUCT'}
dict2 = {12:34,56:78,45:67}
dict3 = {'Name':'Xiaohuang',12:34,'School':'BUCT'}
若一个键被两次赋值,则第一个值无效:
dict4 = {'Name':'Xiaohuang','City':'Beijing','School':'BUCT','City':'Henan'}
也支持嵌套:
dict5 = {'student':{'stu1':'Xiaoming','stu2':'Xiaohong','stu3':'Xiaogang'},
'school':{'sch1':'Tsinghua','sch2':'Penking','sch3':'BUCT'}}
②使用字典
利用键访问,也配合中括号[]:
>>> print(dict1['City'])
Beijing
>>> print(dict2[56])
78
>>> print(dict5['student'])
{'stu1': 'Xiaoming', 'stu2': 'Xiaohong', 'stu3': 'Xiaogang'}
>>> print(dict5['student']['stu2']) #嵌套访问
Xiaohong
修改已有值:
>>> dict1['City'] = 'Henan'
>>> print(dict1['City'])
Henan
在字典中添加新的键值元素:
>>> dict1['Age'] = 21
>>> print(dict1)
{'Name': 'Xiaohuang', 'City': 'Henan', 'School': 'BUCT', 'Age': 21}
删除某个元素和整个字典:
>>> del dict1['School']
>>> print(dict1)
{'Name': 'Xiaohuang', 'City': 'Henan', 'Age': 21}
>>> del dict1
>>> print(dict1)
Traceback (most recent call last):
File "<pyshell#14>", line 1, in <module>
print(dict1)
NameError: name 'dict1' is not defined
③字典内置函数和方法
len、str输出字典、type:
>>> len(dict2)
3
>>> str(dict2)
'{12: 34, 56: 78, 45: 67}'
>>> type(dict2)
<class 'dict'>
其他内置方法:
①dictname.fromkeys(序列,value)
>>> seq1 = ('Name','City','School')
>>> seq2 = ['Name','City','School']
>>> dict1 = {}
>>> dict1 = dict1.fromkeys(seq1)
>>> print(dict1)
{'Name': None, 'City': None, 'School': None}
>>> dict2 = {}
>>> dict2 = dict2.fromkeys(seq2,10)
>>> print(dict2)
{'Name': 10, 'City': 10, 'School': 10}
②key in dictname,在的话返回True否则False
>>> 'Name' in dict2
True
③dictname.get(key)
>>>dict2 = {'Name': 10, 'City': 10, 'School': 10}
>>> print(dict2.get('Name'))
10
>>> print(dict2.get('Age'))
None
>>>dict5 = {'student':{'stu1':'Xiaoming','stu2':'Xiaohong','stu3':'Xiaogang'},'school':{'sch1':'Tsinghua','sch2':'Penking','sch3':'BUCT'}}
>>> print(dict5.get('student').get('stu1')) #嵌套使用
Xiaoming
对比:
dictname.get(key) 方法在 key(键)不在字典中时,可以返回默认值 None 或者设置的默认值。
dictname[key] 在 key(键)不在字典中时,会触发 KeyError 异常。
④dictname.keys()、dictname.values()、dictname.items()
>>> dict1 = {'Name': 'Xiaohuang', 'City': 'Henan', 'School': 'BUCT', 'Age': 21}
>>> print(dict1.keys())
dict_keys(['Name', 'City', 'School', 'Age'])
>>> print(dict1.values())
dict_values(['Xiaohuang', 'Henan', 'BUCT', 21])
>>> print(dict1.items()) #返回可遍历的元组数组
dict_items([('Name', 'Xiaohuang'), ('City', 'Henan'), ('School', 'BUCT'), ('Age', 21)])
>>> for key,value in dict1.items():
print(key,':',value)
Name : Xiaohuang
City : Henan
School : BUCT
Age : 21
④dictname.update(dictname1)
>>> dict1 = {'Name': 'Xiaohuang', 'City': 'Henan', 'School': 'BUCT', 'Age': 21}
>>> dict2 = {}
>>> dict2.update(dict1)
>>> print(dict2)
{'Name': 'Xiaohuang', 'City': 'Henan', 'School': 'BUCT', 'Age': 21}
⑤dictname.pop(key),删除对应键值,返回值为key对应的value
>>> dict1 = {'Name': 'Xiaohuang', 'City': 'Henan', 'School': 'BUCT', 'Age': 21}
>>> x = dict1.pop('Name')
>>> print(x)
Xiaohuang
>>> print(dict1)
{'City': 'Henan', 'School': 'BUCT', 'Age': 21}
⑥dictname.popitem()弹出最后一组键值(返回值),并删除
>>> x = dict1.popitem()
>>> print(x)
('Age', 21)
>>> print(dict1)
{'Name': 'Xiaohuang', 'City': 'Henan', 'School': 'BUCT'}
5、集合(set)
集合是一种集合类型(无序、不可重复),表示任意元素的集合,索引可通过另一个任意键值的集合进行,可无序排列和哈希。
可变集合set:创建后可通过各种方法被改变,如add()、update()等
不可变集合frozenset:可哈希(一个对象在其生命周期内,其哈希值不会变化,并可与其他对象做比较),也可作为一个元素被其他集合使用,或者作为字典的键。
①创建集合
可使用{}创建,或set()、frozenset()创建。为了防止与字典歧义,创建空集合时必须使用:
empty = set()不能用empty = {}
>>> set1 = {1,2,3,4,5,6}
>>> set2 = {'a','b','c','d','e'}
>>> set3 = set([10,20,30,40,50])
>>> set4 = frozenset(['huang','zhang','zhao'])
②使用集合
字典可自行去除重复的元素:
>>> set5 = {1,2,3,4,5,6,1,2,3}
>>> print(set5)
{1, 2, 3, 4, 5, 6}
>>> print(len(set5))
6
集合是无序的,没有”索引“或”键“来指定调用某个元素,但可用循环输出:
>>> for x in set5:
print(x,end=' ')
1 2 3 4 5 6
增加元素,update不允许整型只允许序列:
>>> set5.add(7)
>>> print(set5)
{1, 2, 3, 4, 5, 6, 7}
>>> set5.update('huang')
>>> print(set5)
{'g', 1, 2, 3, 4, 5, 6, 7, 'u', 'h', 'a', 'n'}
>>> set5.update('89')
>>> print(set5)
{'g', 1, 2, 3, 4, 5, 6, 7, 'u', '8', 'h', 'a', 'n', '9'}
>>> set5.update(1234) #不允许int
Traceback (most recent call last):
File "<pyshell#18>", line 1, in <module>
set5.update(1234)
TypeError: 'int' object is not iterable
成员测试:
>>> 'h' in set5
True
>>> 1 not in set5
False
集合运算:
>>> set1 = {1,2,3,4,5}
>>> set2 = {3,4,5,6,7}
>>> set1 - set2 #差集
{1, 2}
>>> set1 | set2 #并集
{1, 2, 3, 4, 5, 6, 7}
>>> set1 & set2 #交集
{3, 4, 5}
>>> set1 ^ set2 #对称差集
{1, 2, 6, 7}
删除元素:
>>> set1.remove(3)
>>> print(set1)
{1, 2, 4, 5}
③集合的方法
很多。。。省略,详细可见python语言知识点查阅: https://www.runoob.com/python/python-dictionary.html
五、字符串与正则表达式
字符串用于表示文本类型的数据,也是有序的字符数组集合,序列不可更改,索引也是从0开始。
正则表达式是操作字符串的特殊字符串,通过正则表达式可验证相应的字符串是否符合对应的规则。
1、字符串基础
字符串的字符可以是ASCII字符、或其他各种符号,伴随单引号、双引号、三引号出现。
一些特殊字符叫做转义字符,用于不能直接输入的情况:
\\(反斜线)、\'(单引号)、\"(双引号)、\a(响铃符)、\b(退格)、\f(换页)、
\n(换行)、\r(回车)、\t(水平制表符)、\v(垂直制表符)、\0(Null空字符串)、
\000(以八进制表示的ASCII码对应符)、\xhh(以十六进制表示的ASCII码对应符)
①字符串基本操作
求字符串长度:
>>> str1 = 'I love python'
>>> print(len(str1))
13
字符串的连接:
>>> str2 = 'I','love','python'
>>> print(str2,type(str2))
('I', 'love', 'python') <class 'tuple'>
>>> str3 = 'I''love''python'
>>> print(str3,type(str3))
Ilovepython <class 'str'>
>>> str4 = 'I'+'love'+'python'
>>> print(str4,type(str4))
Ilovepython <class 'str'>
>>> str5 = 'love'*5
>>> print(str5)
lovelovelovelovelove
字符串的遍历:
>>> str6 = 'Python'
>>> for s in str6:
print(s)
包含判断:
>>> print('P' in str6)
True
>>> print('y' not in str6)
False
可索引获取但不可修改:
>>> print('P' in str6)
True
>>> print('y' not in str6)
False
>>> print(str6[2])
t
>>> print(str6[:3])
Pyt
>>> print(str6[3:])
hon
>>> str6[0] = 'p' #报错不可修改
Traceback (most recent call last):
File "<pyshell#22>", line 1, in <module>
str6[0] = 'p'
TypeError: 'str' object does not support item assignment
②字符串格式化
用format()方法:
>>> print(' I am {0},and I am from {1}'.format('Liming','China'))
I am Liming,and I am from China
用格式化符号:
%s、%r、%c、%b、%o、%d、%i、%f等(详细自找)
%可理解为占位符,后面就是实际要跟的参数,实际参数的本质就是元组
>>>print(' I am %s,and I am from %s'%('Liming','China'))
I am Liming,and I am from China
>>> print('花了%.2f元'%(19.56789))
花了19.57元
2、字符串方法进阶
①strip删除首尾字符,未指定字符默认删除首尾空格或换行符
>>> str1 = ' Hello world*##'
>>> print(str1.strip())
Hello world*##
>>> print(str1.strip('#'))
Hello world*
>>> print(str1.strip('*##'))
Hello world
②count统计指定范围内某个字符出现的次数
>>> str2 = '101010101010'
>>> print(str2.count('1',2,10)) #不包括索引为10的末尾
4
③capitalize将字符串首字母大写
>>> str3 = 'xiaohuang'
>>> print(str3.capitalize())
Xiaohuang
④replace替换字符,不指定第3个参数替换次数默认全部替换
>>> str2 = '101010101010'
>>> print(str2.replace('10','89'))
898989898989
>>> print(str2.replace('10','89',2))
898910101010
⑤find在指定范围内查找字符并返回索引号(未找到返回-1,多次出现返回第一次出现的索引号)
>>> str4 = '01234507'
>>> print(str4.find('2'))
2
>>> print(str4.find('2',3,8))
-1
>>> print(str4.find('0'))
0
⑥index与find一样但是未找到会直接报错
>>> print(str4.index('8'))
Traceback (most recent call last):
File "<pyshell#16>", line 1, in <module>
print(str4.index('8'))
ValueError: substring not found
⑦isalnum判断字符串是否全由字母或数字组成
>>> str5 = 'aaaa1111'
>>> str6 = 'asdfgg'
>>> str7 = 'ash17%%#'
>>> print(str5.isalnum(),str6.isalnum(),str7.isalnum())
True True False
类似的函数还有:
isalpha判断是否全部由字母组成
isdigital判断是否全部由数字组成
isspace判断是否全部由空格组成
islower判断是否全是小写
isupper判断是否全是大写
istitle判断首字母是否是大写
⑧lower、upper全部换为小写或大写
>>> str8 = 'aBcDeF'
>>> print(str8.lower())
abcdef
>>> print(str8.upper())
ABCDEF
⑨split(sep,maxsplit)按照指定sep字符进行分割(不指定sep默认分割空格),maxsplit为分割次数(不指定默认全部分割)
>>> str9 = 'uhaduadhaxbu'
>>> print(str9.split('a'))
['uh', 'du', 'dh', 'xbu']
>>> print(str9.split('a',2))
['uh', 'du', 'dhaxbu']
>>> str10 = 'I love python'
>>> print(str10.split())
['I', 'love', 'python']
⑩startswith判断(在指定范围内)是否以指定字符开头;endswith判断(在指定范围内)是否以指定字符结尾
>>> str11 = '13hhhh'
>>> print(str11.startswith('13'))
True
>>> print(str11.startswith('13',2,5))
False
>>> print(str11.endswith('hh',2,6))
True
⑩partition(sep),以sep第一次出现位置分成三部分,返回一个三元元组,若没有sep则返回空格;repartition(sep),以sep最后一次 出现位置分成三部分
>>> str12 = '1234561234'
>>> print(str12.partition('34'))
('12', '34', '561234')
>>> print(str12.partition('78'))
('1234561234', '', '')
>>> print(str12.rpartition('12'))
('123456', '12', '34')
3、正则表达式
正则表达式,即描述某种规则的表达式,代码简写regex、regexp或RE。它使用某些单个字符串来描述或匹配某个句法规则的字符串,在很多文本编辑器中,RE被用来检索或替换那些符合某个模式的文本:
表格——P84?????????????没懂干啥哩???
①re模块
re模块提供了正则表达式操作所需的方法
>>> import re
>>> result = re.match('huang','huang666') #匹配
>>> print(result.group()) #group用于只返回匹配结果
huang
>>> print(result)
<_sre.SRE_Match object; span=(0, 5), match='huang'>
#match与search的区别,search就算不是从开头对应也能匹配到
>>> result1 = re.match('huang','xiaohuang') #匹配不到huang
>>> print(result1.group())
AttributeError: 'NoneType' object has no attribute 'group'
>>> result2 = re.search('huang','xiaohuang') #匹配到huang
>>> print(result2.group())
huang