强娃学Python

如果你有一定的编程语言基础,学习 Python 也是不错的选择,因为 Python 很可能就是未来开发的主流方向,多学一门语言,多一个防身技能。而且 Python 有强大的功能库,能非常快速的开发工具,为你的本职开发工作提供护航。

一、Python代码规范

1.1编码

如无特殊情况, 文件头部必须加入#-*-coding:utf-8-*-标识,即文件一律使用UTF-8编码

1.2代码格式

1.2.1缩进

统一使用4个空格

1.2.2行宽

  • 每行代码尽量不超过 80 个字符(在特殊情况下可以略微超过 80 ,但最长不得超过 120)
  • 可以搭配使用反斜杠/使用换行

1.2.3空行

  • 模块级函数和类定义之间空两行;
  • 类成员函数之间空一行;
class A:

    def __init__(self):
        pass

    def hello(self):
        pass


def main():
    pass

1.2.4import语句

  • import 语句应该分行书写
  • 导入其他模块的类定义时,可以使用相对导入
from myclass import MyClass
  • 如果发生命名冲突,则可使用命名空间
import bar
import foo.bar

bar.Bar()
foo.bar.Bar()

1.2.5空格

  • 在二元运算符两边各空一格[=,-,+=,==,>,in,is not, and]
  • 函数的参数列表中,,之后要有空格
  • 函数的参数列表中,默认值等号两边不要添加空格
# 正确的写法
def complex(real, imag=0.0):
    pass

# 不推荐的写法
def complex(real, imag = 0.0):
    pass
  • 左括号之后,右括号之前不要加多余的空格
  • 字典对象的左括号之前不要多余的空格

1.2.6引号

简单说,自然语言使用双引号,机器标示使用单引号,因此 代码里 多数应该使用 单引号

  • 自然语言 使用双引号 "..."
  • 机器标识 使用单引号 '...' 例如 dict 里的 key
  • 正则表达式 使用原生的双引号 r"..."
  • 文档字符串 (docstring) 使用三个双引号 """......"""

1.2.7docstring

docstring 的规范中最其本的两点:

  1. 所有的公共模块、函数、类、方法,都应该写 docstring 。私有方法不一定需要,但应该在 def 后提供一个块注释来说明。
  2. docstring 的结束"""应该独占一行,除非此 docstring 只有一行。
"""Return a foobar
Optional plotz says to frobnicate the bizbaz first.
"""

"""Oneline docstring"""

二、注释

2.1注释

2.1.1块注释

“#”号后空一格,段落件用空行分开(同样需要“#”号)

2.1.2行注释

至少使用两个空格和语句分开,注意不要使用无意义的注释

2.2文档注释(Docstring)

作为文档的Docstring一般出现在模块头部、函数和类的头部,这样在python中可以通过对象的__doc__对象获取文档. 编辑器和IDE也可以根据Docstring给出自动提示.

  • 文档注释以 """ 开头和结尾, 首行不换行, 如有多行, 末行必需换行, 以下是Google的docstring风格示例
# -*- coding: utf-8 -*-
"""Example docstrings.

This module demonstrates documentation as specified by the `Google Python
Style Guide`_. Docstrings may extend over multiple lines. Sections are created
with a section header and a colon followed by a block of indented text.

Example:
    Examples can be given using either the ``Example`` or ``Examples``
    sections. Sections support any reStructuredText formatting, including
    literal blocks::

"""
  • 不要在文档注释复制函数定义原型, 而是具体描述其具体内容, 解释具体参数和返回值等
#  不推荐的写法(不要写函数原型等废话)
def function(a, b):
    """function(a, b) -> list"""
    ... ...


#  正确的写法
def function(a, b):
    """计算并返回a到b范围内数据的平均值"""
    ... ...

三、命名规范

3.1模块

模块尽量使用小写命名,首字母保持小写,尽量不要用下划线(除非多个单词,且数量不多的情况)

3.2类名

  • 类名使用驼峰(CamelCase)命名风格,首字母大写,私有类可用一个下划线开头
  • 将相关的类和顶级函数放在同一个模块里. 不像Java, 没必要限制一个类一个模块.

3.3函数

  • 函数名一律小写,如有多个单词,用下划线隔开
  • 私有函数在函数前加一个下划线_

3.4变量名

  • 变量名尽量小写, 如有多个单词,用下划线隔开
  • 常量采用全大写,如有多个单词,使用下划线隔开
MAX_CLIENT = 100

3.5常量

常量使用以下划线分隔的大写命名

四、基本数据类型和变量

Python 程序是大小写敏感的,如果写错了大小写,程序会报错。

4.1数据类型

4.1.1字符串(string )

字符串英文 string ,是 python 中随处可见的数据类型,字符串的识别也非常的简单,就是用「引号」括起来的。

引号包括单引号 ' ' ,双引号 " " 和 三引号 ''' ''' ,比如 'abc' ,"123" 等等。

注意:三引号 ''' ''' 是直接可以分行的。

强娃学Python_第1张图片

运行结果:

强娃学Python_第2张图片

4.1.2整数(integer )

只有一种整数类型 int,表示为长整型,没有 python2 中的 Long。

包括正整数、负整数和零,是没有小数点的数字。

强娃学Python_第3张图片

4.1.3浮点数( float )

浮点数跟整数有很多类似的地方,但是浮点数是最折磨人的,也是最难让人捉摸透的。

为什么这么说呢?

看下面的例子 ,像整数一样,只是基本的浮点数加法运算。

强娃学Python_第4张图片

可是运算结果,对于初学者来说,可能会接受不了。

强娃学Python_第5张图片

这是因为计算机对浮点数的表达本身是不精确的。保存在计算机中的是二进制数,二进制对有些数字不能准确表达,只能非常接近这个数。

所以我们在对浮点数做运算和比较大小的时候要小心。

4.1.4布尔值(True、False)

一个布尔值只有 True 、 False两种值

布尔值可以用 andor 和 not 运算。

  • and 运算是与运算,只有所有都为 True,and 运算结果才是 True。
  • or 运算是或运算,只要其中有一个为 True,or 运算结果就是 True。
  • not 运算是非运算,它是一个单目运算符,把 True 变成 False,False 变成 True。

4.1.5空值(None)

基本上每种编程语言都有自己的特殊值——空值,在 Python 中,用 None 来表示

4.2基本数据类型转换

方法 说明
int(x [,base ]) 将x转换为一个整数
float(x ) 将x转换到一个浮点数
complex(real [,imag ]) 创建一个复数
str(x ) 将对象 x 转换为字符串
repr(x ) 将对象 x 转换为表达式字符串
eval(str ) 用来计算在字符串中的有效 Python 表达式,并返回一个对象
tuple(s ) 将序列 s 转换为一个元组
list(s ) 将序列 s 转换为一个列表
chr(x ) 将一个整数转换为一个字符
unichr(x ) 将一个整数转换为 Unicode 字符
ord(x ) 将一个字符转换为它的整数值
hex(x ) 将一个整数转换为一个十六进制字符串
oct(x ) 将一个整数转换为一个八进制字符串

4.3变量

变量名必须是大小写英文、数字和下划线(_)的组合,且不能用数字开头

多个变量赋值

a = b = c = 1   #创建一个整型对象,值为 1,三个变量被分配到相同的内存空间上。

也可以:

a, b, c = 1, 2, "liangdianshui"

五、集合

List是[]包起来的;

Tuple是()包起来的;

Dict是{}包起来的;

Set是{}包起来的;

5.1List列表

List (列表)是 Python 内置的一种数据类型。 它是一种有序的集合,可以随时添加和删除其中的元素。

5.1.1创建

list1=['值1','value1',123]

5.1.2访问List的值

name = ['一点水', '两点水', '三点水', '四点水', '五点水']

# 通过索引来访问列表
print(name[2])
# 通过方括号的形式来截取列表中的数据
print(name[0:2]) #从第 0 个开始取,取到第 2 个,但是不包含第 2 个。

所以,也可以:

强娃学Python_第6张图片

输出结果:

强娃学Python_第7张图片

5.1.3更新

name = ['一点水', '两点水', '三点水', '四点水', '五点水']


# 通过索引对列表的数据项进行修改或更新
name[1]='2点水'
print(name)

# 使用 append() 方法来添加列表项
name.append('六点水')
print(name)

5.1.4删除

name = ['一点水', '两点水', '三点水', '四点水', '五点水']

print(name)

# 使用 del 语句来删除列表的的元素
del name[3]
print(name)

5.1.5List运算符

Python表达式 结果 描述
len([1, 2, 3]) 3 计算元素个数
[1, 2, 3] + [4, 5, 6] [1, 2, 3, 4, 5, 6] 组合
['Hi!'] * 4 ['Hi!', 'Hi!', 'Hi!', 'Hi!'] 复制
3 in [1, 2, 3] True 元素是否存在于列表中
for x in [1, 2, 3]: print x, 1 2 3 迭代

5.1.6List函数和方法

函数&方法 描述
len(list) 列表元素个数
max(list) 返回列表元素最大值
min(list) 返回列表元素最小值
list(seq) 将元组转换为列表
list.append(obj) 在列表末尾添加新的对象
list.count(obj) 统计某个元素在列表中出现的次数
list.extend(seq) 在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)
list.index(obj) 从列表中找出某个值第一个匹配项的索引位置
list.insert(index, obj) 将对象插入列表
list.pop(obj=list[-1]) 移除列表中的一个元素(默认最后一个元素),并且返回该元素的值
list.remove(obj) 移除列表中的一个元素(参数是列表中元素),并且不返回任何值
list.reverse() 反向列表中元素
list.sort([func]) 对原列表进行排序

5.2Tuple元组

tuple 和 List 非常类似,但是 tuple 一旦初始化就不能修改。 

元组(tuple) 不可变是指当你创建了 tuple 时候,它就不能改变了,也就是说它也没有 append(),insert() 这样的方法,但它也有获取某个索引值的方法,但是不能赋值。

为什么要有 Tuple 呢?

那是因为 Tuple是不可变的,所以代码更安全。所以建议能用 Tuple代替 list 就尽量用 Tuple。

5.2.1创建

#使用括号+逗号隔开
tuple1=('两点水','twowter','liangdianshui',123,456)
tuple2='两点水','twowter','liangdianshui',123,456
#创建空元组
tuple3=()
#元组只包含一个元素时
tuple4=(123,)
#如果不加逗号,创建出来的就不是 元组(tuple),而是指 123 这个数了。
#所以如果只有一个元素时,你不加逗号,计算机就根本没法识别你是要进行整数或者小数运算还是表示元组。

5.2.2访问元组

tuple1=('两点水','twowter','liangdianshui',123,456)
print(tuple1[1])

5.2.3修改元组

元组中的元素值是不允许修改的,但我们可以对元组进行连接组合,还有通过修改其他列表的值从而影响 Tuple的值。

list1=[123,456]
tuple1=('两点水','twowater','liangdianshui',list1)
print(tuple1)
list1[0]=789
list1[1]=100
print(tuple1)

运行结果:

('两点水', 'twowater', 'liangdianshui', [123, 456])
('两点水', 'twowater', 'liangdianshui', [789, 100])

5.2.4删除元组

与字符串一样,元组之间可以使用 + 号和 * 号进行运算。这就意味着他们可以组合和复制,运算后会生成一个新的元组。

Python表达式 结果 描述
len((1, 2, 3)) 3 计算元素个数
(1, 2, 3) + (4, 5, 6) (1, 2, 3, 4, 5, 6) 连接
('Hi!',) * 4 ('Hi!', 'Hi!', 'Hi!', 'Hi!') 复制
3 in (1, 2, 3) True 元素是否存在
for x in (1, 2, 3): print(x) 1 2 3 迭代

5.2.5Tuple函数和方法

方法 描述
len(tuple) 计算元组元素个数
max(tuple) 返回元组中元素最大值
min(tuple) 返回元组中元素最小值
tuple(seq) 将列表转换为元组

5.3Dict字典

dict 全称 dictionary,如果学过 Java ,字典就相当于 Java 中的 map,使用键-值(key-value)存储,具有极快的查找速度。

  • dict (字典)是不允许一个键创建两次的,但是在创建 dict (字典)的时候如果出现了一个键值赋予了两次,会以最后一次赋予的值为准
  •  dict (字典)键必须不可变,可是键可以用数字,字符串或元组充当,但是就是不能使用列表
  • dict 内部存放的顺序和 key 放入的顺序是没有任何关系

和 list 比较,dict 有以下几个特点:

  • 查找和插入的速度极快,不会随着key的增加而变慢

  • 需要占用大量的内存,内存浪费多

而list相反:

  • 查找和插入的时间随着元素的增加而增加

  • 占用空间小,浪费内存很少

5.3.1创建

字典是另一种可变容器模型,且可存储任意类型对象。

字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中 ,格式如下所示:

​
dict = {key1 : value1, key2 : value2 }

​#键必须是唯一的,但值则不必。值可以取任何数据类型,但键必须是不可变的。
#比如:
dict1={'liangdianshui':'111111' ,'twowater':'222222' ,'两点水':'333333'}
dict2={'abc':1234,1234:'abc'}
 

5.3.2访问dict 

name = {'一点水': '131456780001', '两点水': '131456780002', '三点水': '131456780003', '四点水': '131456780004', '五点水': '131456780005'}

print(name['两点水'])

 注意:如果字典中没有这个键,是会报错的。

5.3.3修改dict 

dict1={'liangdianshui':'111111' ,'twowater':'222222' ,'两点水':'333333'}
print(dict1)
# 新增一个键值对
dict1['jack']='444444'
print(dict1)
# 修改键值对
dict1['liangdianshui']='555555'
print(dict1)

5.3.4删除dict 

dict1={'liangdianshui':'111111' ,'twowater':'222222' ,'两点水':'333333'}
print(dict1)
# 通过 key 值,删除对应的元素
del dict1['twowater']
print(dict1)
# 删除字典中的所有元素
dict1.clear()
print(dict1)
# 删除字典
del dict1

5.3.5dict 的函数和方法

方法和函数 描述
len(dict) 计算字典元素个数
str(dict) 输出字典可打印的字符串表示
type(variable) 返回输入的变量类型,如果变量是字典就返回字典类型
dict.clear() 删除字典内所有元素
dict.copy() 返回一个字典的浅复制
dict.values() 以列表返回字典中的所有值
popitem() 随机返回并删除字典中的一对键和值
dict.items() 以列表返回可遍历的(键, 值) 元组数组

5.4Set

python 的 set 和其他语言类似, 是一个无序不重复元素集, 基本功能包括关系测试和消除重复元素。set 和 dict 类似,但是 set 不存储 value 值的。

5.4.1创建

set1=set([123,456,789])
print(set1)

5.4.2添加

在 dict (字典) 中创建时,有重复的 key ,会被后面的 key-value 值覆盖的,而 重复元素在 set 中自动被过滤的。

通过 add(key) 方法可以添加元素到 set 中,可以重复添加,但不会有效果。

5.4.3删除

通过 remove(key) 方法可以删除 set 中的元素

5.4.4并交差集

set1=set('hello')
set2=set(['p','y','y','h','o','n'])
#交集
set3=set1 & set2
#并集
set4=set1 | set2
#差集
set5=set1 - set2

六、条件&循环语句

Python 程序语言指定任何非 0 和非空(null)值为 True,0 或者 null 为 False。

强娃学Python_第8张图片

注意:在条件判断代码中的冒号 : 后、下一行内容是一定要缩进的。不缩进是会报错的。

6.1if语句

#单条件
if 判断条件1:
    执行语句1……
elif 判断条件2:
    执行语句2……
elif 判断条件3:
    执行语句3……
else:
    执行语句4……

#多条件
if 条件1 and  条件2:
elif 条件3 or 条件4

6.2循环语句

在 Python 提供了 for 循环和 while 循环。

循环控制语句 描述
break 在语句块中执行过程中终止循环,并且跳出整个循环
continue 在语句块执行过程中终止当前循环,执行下一次循环
pass pass是空语句,是为了保持程序结构的完整性

6.2.1for 循环语句

for iterating_var in sequence:
   statements(s)

6.2.2while循环

强娃学Python_第9张图片

七、函数

def 函数名(参数1,参数2....参数n):
    函数体
    return 语句

7.1函数参数

7.1.1关键字参数(位置参数)

# -*- coding: UTF-8 -*-

def print_user_info( name ,  age  , sex = '男' ):
    # 打印用户信息
    print('昵称:{}'.format(name) , end = ' ')
    print('年龄:{}'.format(age) , end = ' ')
    print('性别:{}'.format(sex))
    return;

# 调用 print_user_info 函数

print_user_info( name = '两点水' ,age = 18 , sex = '女')
print_user_info( name = '两点水' ,sex = '女', age = 18 )

7.1.2不定长参数

Python 提供了一种元组的方式来接受没有直接定义的参数。这种方式在参数前边加星号 * 。

例如:

# -*- coding: UTF-8 -*-

def print_user_info( name ,  age  , sex = '男' , * hobby):
    # 打印用户信息
    print('昵称:{}'.format(name) , end = ' ')
    print('年龄:{}'.format(age) , end = ' ')
    print('性别:{}'.format(sex) ,end = ' ' )
    print('爱好:{}'.format(hobby))
    return;

# 调用 print_user_info 函数
print_user_info( '两点水' ,18 , '女', '打篮球','打羽毛球','跑步')

运行结果:

昵称:两点水 年龄:18 性别:女 爱好:('打篮球', '打羽毛球', '跑步')

7.1.3位置参数的不定长参数

# -*- coding: UTF-8 -*-

def print_user_info( name ,  age  , sex = '男' , ** hobby ):
    # 打印用户信息
    print('昵称:{}'.format(name) , end = ' ')
    print('年龄:{}'.format(age) , end = ' ')
    print('性别:{}'.format(sex) ,end = ' ' )
    print('爱好:{}'.format(hobby))
    return;

# 调用 print_user_info 函数
print_user_info( name = '两点水' , age = 18 , sex = '女', hobby = ('打篮球','打羽毛球','跑步'))

运行结果:

昵称:两点水 年龄:18 性别:女 爱好:{'hobby': ('打篮球', '打羽毛球', '跑步')}

7.1.4只接受关键字参数

# -*- coding: UTF-8 -*-

def print_user_info( name , *, age  , sex = '男' ):
    # 打印用户信息
    print('昵称:{}'.format(name) , end = ' ')
    print('年龄:{}'.format(age) , end = ' ')
    print('性别:{}'.format(sex))
    return;

# 调用 print_user_info 函数
print_user_info( name = '两点水' ,age = 18 , sex = '女' )

# 这种写法会报错,因为 age ,sex 这两个参数强制使用关键字参数
#print_user_info( '两点水' , 18 , '女' )
print_user_info('两点水',age='22',sex='男')

7.2匿名函数

python 使用 lambda 来创建匿名函数,也就是不再使用 def 语句这样标准的形式定义一个函数。

匿名函数主要有以下特点:

  • lambda 只是一个表达式,函数体比 def 简单很多。
  • lambda 的主体是一个表达式,而不是一个代码块。仅仅能在 lambda 表达式中封装有限的逻辑进去。
  • lambda 函数拥有自己的命名空间,且不能访问自有参数列表之外或全局命名空间里的参数。

语法:

lambda [arg1 [,arg2,.....argn]]:expression

示例:

# -*- coding: UTF-8 -*-

sum = lambda num1 , num2 : num1 + num2;

print( sum( 1 , 2 ) )

注意:尽管 lambda 表达式允许你定义简单函数,但是它的使用是有限制的。 你只能指定单个表达式,它的值就是最后的返回值。也就是说不能包含其他的语言特性了, 包括多个语句、条件表达式、迭代以及异常处理等等。

特别注意:

# -*- coding: UTF-8 -*-

num2 = 100
sum1 = lambda num1 : num1 + num2 ;

num2 = 10000
sum2 = lambda num1 : num1 + num2 ;

print( sum1( 1 ) )
print( sum2( 1 ) )

运行结果:

10001
10001

这主要在于 lambda 表达式中的 num2 是一个自由变量,在运行时绑定值,而不是定义时就绑定,这跟函数的默认值参数定义是不同的。所以建议还是遇到这种情况还是使用第一种解法。

八、迭代器和生成器

迭代:

for本身就是一种迭代

# 1、for 循环迭代字符串
for char in 'liangdianshui' :
    print ( char , end = ' ' )


# 如果 list 里面一个元素有两个变量,也是很容易迭代的
for x , y in [ (1,'a') , (2,'b') , (3,'c') ] :
    print ( x , y )

输出结果:

l i a n g d i a n s h u i 

1 a
2 b
3 c

8.1迭代器

迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。

迭代器只能往前不会后退。

迭代器有两个基本的方法:iter() 和 next(),且字符串,列表或元组对象都可用于创建迭代器,迭代器对象可以使用常规 for 语句进行遍历,也可以使用 next() 函数来遍历。

# 3、tuple(元祖) 对象创建迭代器
tuple1 = ( 1,2,3,4 )
iter3 = iter ( tuple1 )

# for 循环遍历迭代器对象
for x in iter1 :
    print ( x , end = ' ' )

print('\n------------------------')

# next() 函数遍历迭代器
while True :
    try :
        print ( next ( iter3 ) )
    except StopIteration :
        break

运行结果:

1
2
3
4

8.2生成器

通过上面的学习,可以知道列表生成式,我们可以直接创建一个列表。

但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含 1000 万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?

这样就不必创建完整的 list,从而节省大量的空间。

在 Python 中,这种一边循环一边计算的机制,称为生成器:generator。

在 Python 中,使用了 yield 的函数被称为生成器(generator)。

跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。

在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值。并在下一次执行 next()方法时从当前位置继续运行。

那么如何创建一个生成器呢?

8.2.1生成器创建

最简单最简单的方法就是把一个列表生成式的 [] 改成 ()

# -*- coding: UTF-8 -*-
gen= (x * x for x in range(10))
print(gen)

输出结果:

 at 0x0000000002734A40>

创建 List 和 generator 的区别仅在于最外层的 [] 和 () 。

但是生成器并不真正创建数字列表, 而是返回一个生成器,这个生成器在每次计算出一个条目后,把这个条目“产生” ( yield ) 出来。

生成器表达式使用了“惰性计算” ( lazy evaluation,也有翻译为“延迟求值”,我以为这种按需调用 call by need 的方式翻译为惰性更好一些),只有在检索时才被赋值( evaluated ),所以在列表比较长的情况下使用内存上更有效。

8.2.2以函数形式实现生成器

# -*- coding: UTF-8 -*-
def odd():
    print ( 'step 1' )
    yield ( 1 )
    print ( 'step 2' )
    yield ( 3 )
    print ( 'step 3' )
    yield ( 5 )

o = odd()
print( next( o ) )
print( next( o ) )
print( next( o ) )

输出结果:

step 1
1
step 2
3
step 3
5

8.3综合应用:同时迭代多个序列

你想同时迭代多个序列,每次分别从一个序列中取一个元素。你遇到过这样的需求吗?

# -*- coding: UTF-8 -*-

names = ['laingdianshui', 'twowater', '两点水']
ages = [18, 19, 20]
for name, age in zip(names, ages):
     print(name,age)

输出结果:

laingdianshui 18
twowater 19
两点水 20

其实 zip(a, b) 会生成一个可返回元组 (x, y) 的迭代器,其中 x 来自 a,y 来自 b。 一旦其中某个序列到底结尾,迭代宣告结束。 因此迭代长度跟参数中最短序列长度一致。注意理解这句话喔,也就是说如果 a , b 的长度不一致的话,以最短的为标准,遍历完后就结束。

利用 zip() 函数,我们还可把一个 key 列表和一个 value 列表生成一个 dict (字典),如下:

# -*- coding: UTF-8 -*-

names = ['laingdianshui', 'twowater', '两点水']
ages = [18, 19, 20]

dict1= dict(zip(names,ages))

print(dict1)

输出结果:

{'laingdianshui': 18, 'twowater': 19, '两点水': 20}

九、面向对象

Python 就是一门面向对象的语言,

9.1基本概念

  • :用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
  • 对象:通过类定义的数据结构实例

9.2三大特性

  • 继承

即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。

  • 多态

它是指对不同类型的变量进行相同的操作,它会根据对象(或类)类型的不同而表现出不同的行为。

  • 封装

“封装”就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体(即类);封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,一特定的访问权限来使用类的成员。

9.3类定义和调用

class ClassA():
    var1 = 100
    var2 = 0.01
    var3 = '两点水'

    def fun1():
        print('我是 fun1')

    def fun2():
        print('我是 fun1')

    def fun3():
        print('我是 fun1')



ClassA.fun1()

9.4类方法

强娃学Python_第10张图片

注意:在类方法上面多了个 @classmethod ,这是干嘛用的呢?

这是用于声明下面的函数是类函数。如果你没使用,是会报错的。

强娃学Python_第11张图片

因此类方法,想要调用类属性,需要以下步骤:

  • 在方法上面,用 @classmethod 声明该方法是类方法。只有声明了是类方法,才能使用类属性
  • 类方法想要使用类属性,在第一个参数中,需要写上 cls , cls 是 class 的缩写,其实意思就是把这个类作为参数,传给自己,这样就可以使用类属性了。
  • 类属性的使用方式就是 cls.变量名

记住喔,无论是 @classmethod 还是 cls ,都是不能省去的。省了都会报错。

9.5对象

强娃学Python_第12张图片

主要的不同点有:

  • 类方法里面没有了 @classmethod 声明了,不用声明他是类方法
  • 类方法里面的参数 cls 改为 self
  • 类的使用,变成了先通过 实例名 = 类() 的方式实例化对象,为类创建一个实例,然后再使用 实例名.函数() 的方式调用对应的方法 ,使用 实例名.变量名 的方法调用类的属性

这里说明一下,类方法的参数为什么 cls 改为 self ?这里什么名字都可以。

强娃学Python_第13张图片

你看,把 self 改为 aaaaaaaa 还是可以一样运行的。

只不过使用 cls 和 self 是我们的编程习惯,这也是我们的编程规范。

因为 cls 是 class 的缩写,代表这类 , 而 self 代表这对象的意思。

所以啊,这里我们实例化对象的时候,就使用 self 。

而且 self 是所有类方法位于首位、默认的特殊参数。

除此之外,在这里,还要强调一个概念,当你把类实例化之后,里面的属性和方法,就不叫类属性和类方法了,改为叫实例属性和实例方法,也可以叫对象属性和对象方法。

9.6实例方法和类方法

强娃学Python_第14张图片

在这个例子中,我们需要改变类方法,就用到了类的重写

我们使用了 类.原始函数 = 新函数 就完了类的重写了。

要注意的是,这里的赋值是在替换方法,并不是调用函数。所以是不能加上括号的,也就是 类.原始函数() = 新函数() 这个写法是不对的。

那么如果实例方法改变了,类方法会改变吗?

强娃学Python_第15张图片

9.7初始化函数

9.7.1构造函数

懂得人都知道就是构造函数

强娃学Python_第16张图片

这个 __init__(self) 函数就是初始化函数,也叫构造函数。

初始化函数的写法是固定的格式:中间是 init,意思是初始化,然后前后都要有【两个下划线】,然后 __init__() 的括号中,第一个参数一定要写上 self,不然会报错。

构造函数(初始化函数)格式如下:

def __init__(self,[...):

9.7.2析构函数

既然一个在创建的时候,会调用构造函数,那么理所当然,这个当一个类销毁的时候,就会调用析构函数。

析构函数语法如下:

def __del__(self,[...):

强娃学Python_第17张图片

9.8继承

9.8.1定义

class ClassName(Base1,Base2,Base3):
    
    .
    .
    .
    

在定义类的时候,可以在括号里写继承的类,如果不用继承类的时候,也要写继承 object 类,因为在 Python 中 object 类是一切类的父类。

多继承有一点需要注意的:若是父类中有相同的方法名,而在子类使用时未指定,python 在圆括号中父类的顺序,从左至右搜索 , 即方法在子类中未找到时,从左到右查找父类中是否包含方法。

9.8.2调用父类的方法

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

class UserInfo(object):
    lv = 5

    def __init__(self, name, age, account):
        self.name = name
        self._age = age
        self.__account = account

    def get_account(self):
        return self.__account


class UserInfo2(UserInfo):
    pass


if __name__ == '__main__':
    userInfo2 = UserInfo2('两点水', 23, 347073565);
    print(userInfo2.get_account())

9.8.3父类方法的重写

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

class UserInfo(object):
    lv = 5

    def __init__(self, name, age, account):
        self.name = name
        self._age = age
        self.__account = account

    def get_account(self):
        return self.__account

    @classmethod
    def get_name(cls):
        return cls.lv

    @property
    def get_age(self):
        return self._age


class UserInfo2(UserInfo):
    def __init__(self, name, age, account, sex):
        super(UserInfo2, self).__init__(name, age, account)
        self.sex = sex;


if __name__ == '__main__':
    userInfo2 = UserInfo2('两点水', 23, 347073565, '男');
    # 打印所有属性
    print(dir(userInfo2))
    # 打印构造函数中的属性
    print(userInfo2.__dict__)
    print(UserInfo2.get_name())

9.8.4子类的类型判断

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

class User1(object):
    pass


class User2(User1):
    pass


class User3(User2):
    pass


if __name__ == '__main__':
    user1 = User1()
    user2 = User2()
    user3 = User3()
    # isinstance()就可以告诉我们,一个对象是否是某种类型
    print(isinstance(user3, User2))
    print(isinstance(user3, User1))
    print(isinstance(user3, User3))
    # 基本类型也可以用isinstance()判断
    print(isinstance('两点水', str))
    print(isinstance(347073565, int))
    print(isinstance(347073565, str))

运行结果:

强娃学Python_第18张图片

9.9多态

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

class User(object):
    def __init__(self, name):
        self.name = name

    def printUser(self):
        print('Hello !' + self.name)


class UserVip(User):
    def printUser(self):
        print('Hello ! 尊敬的Vip用户:' + self.name)


class UserGeneral(User):
    def printUser(self):
        print('Hello ! 尊敬的用户:' + self.name)


def printUserInfo(user):
    user.printUser()


if __name__ == '__main__':
    userVip = UserVip('两点水')
    printUserInfo(userVip)
    userGeneral = UserGeneral('水水水')
    printUserInfo(userGeneral)

运行结果:

9.10类的访问控制

在 Java 中,有 public (公共)属性 和 private (私有)属性,这可以对属性进行访问控制。

那么在 Python 中有没有属性的访问控制呢?

一般情况下,我们会使用 __private_attrs 两个下划线开头,声明该属性为私有,不能在类地外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs

为什么只能说一般情况下呢?

因为实际上, Python 中是没有提供私有属性等功能的,一切都是靠程序员自觉遵守 Python 的编程规范。

但是 Python 对属性的访问控制是靠程序员自觉的。为什么这么说呢?

看看下面的示例:

强娃学Python_第19张图片

9.11类专有的方法

方法 说明
__init__ 构造函数,在生成对象时调用
__del__ 析构函数,释放对象时使用
__repr__ 打印,转换
__setitem__ 按照索引赋值
__getitem__ 按照索引获取值
__len__ 获得长度
__cmp__ 比较运算
__call__ 函数调用
__add__ 加运算
__sub__ 减运算
__mul__ 乘运算
__div__ 除运算
__mod__ 求余运算
__pow__ 乘方

当然有些时候我们需要获取类的相关信息,我们可以使用如下的方法:

  • type(obj):来获取对象的相应类型;
  • isinstance(obj, type):判断对象是否为指定的 type 类型的实例;
  • hasattr(obj, attr):判断对象是否具有指定属性/方法;
  • getattr(obj, attr[, default]) 获取属性/方法的值, 要是没有对应的属性则返回 default 值(前提是设置了 default),否则会抛出 AttributeError 异常;
  • setattr(obj, attr, value):设定该属性/方法的值,类似于 obj.attr=value;
  • dir(obj):可以获取相应对象的所有属性和方法名的列表;

十、模块与包

在 Python 中,一个 .py 文件就称之为一个模块(Module)。

10.1import

import module1[, module2[,... moduleN]

10.2from···import

from modname import name1[, name2[, ... nameN]]

from···import 和 import 方法有啥区别呢?

from···import 直接导入 version 属性

10.3主模块和非主模块

在 Python 中,有主模块和非主模块之分,当然,我们也得区分他们啊。那么怎么区分主模块和非主模块呢?

这就需要用到 __name__ 属性了,这个 ——name—— 属性值是一个变量,且这个变量是系统给出的。利用这个变量可以判断一个模块是否是主模块。如果一个属性的值是 __main__ ,那么就说明这个模块是主模块,反之亦然。但是要注意了: 这个 __main__ 属性只是帮助我们判断是否是主模块,并不是说这个属性决定他们是否是主模块,决定是否是主模块的条件只是这个模块有没有被人调用

示例:

首先创建了模块 lname ,然后判断一下是否是主模块,如果是主模块就输出 main 不是,就输出 not main ,首先直接运行该模块,由于该模块是直接使用,而没有被人调用,所以是主模块,因此输出了 main

强娃学Python_第20张图片

然后又创建一个 user_lname 模块,里面只是简单的导入了 lname 模块,然后执行,输出的结果是 not main ,因为 lname 模块被该模块调用了,所以不是主模块,输出结果如图:

强娃学Python_第21张图片

10.4包

为了避免模块名冲突,Python 又引入了按目录来组织模块的方法,称为包(Package)。比如最开始的例子,就引入了包,这样子做就算有相同的模块名,也不会造成重复,因为包名不同,其实也就是路径不同。如下图,引入了包名后, lname.py 其实变成了 com.Learn.module.nameattributes.lname

强娃学Python_第22张图片

仔细观察的人,基本会发现,每一个包目录下面都会有一个 __init__.py 的文件,为什么呢?

因为这个文件是必须的,否则,Python 就把这个目录当成普通目录,而不是一个包 。 __init__.py 可以是空文件,也可以有Python代码,因为 __init__.py 本身就是一个模块,而它对应的模块名就是它的包名。

10.5作用域

学习过 Java 的同学都知道,Java 的类里面可以给方法和属性定义公共的( public )或者是私有的 ( private ),这样做主要是为了我们希望有些函数和属性能给别人使用或者只能内部使用。

通过学习 Python 中的模块,其实和 Java 中的类相似,那么我们怎么实现在一个模块中,有的函数和变量给别人使用,有的函数和变量仅仅在模块内部使用呢?

在 Python 中,是通过 _ 前缀来实现的。

正常的函数和变量名是公开的(public),可以被直接引用,比如:abc,ni12,PI等;类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的 __name__ 就是特殊变量,还有 __author__ 也是特殊变量,用来标明作者。

注意,我们自己的变量一般不要用这种变量名;类似 _xxx 和 __xxx 这样的函数或变量就是非公开的(private),不应该被直接引用,比如 _abc ,__abc 等;

这里是说不应该,而不是不能。因为 Python 种并没有一种方法可以完全限制访问 private 函数或变量,但是,从编程习惯上不应该引用 private 函数或变量。

十一、运算符相关方法

方法 说明
__cmp__(self, other) 如果该方法返回负数,说明 self < other; 返回正数,说明 self > other; 返回 0 说明 self == other。强烈不推荐来定义 __cmp__ , 取而代之, 最好分别定义 __lt____eq__ 等方法从而实现比较功能。 __cmp__ 在 Python3 中被废弃了。
__eq__(self, other) 定义了比较操作符 == 的行为
__ne__(self, other) 定义了比较操作符 != 的行为
__lt__(self, other) 定义了比较操作符 < 的行为
__gt__(self, other) 定义了比较操作符 > 的行为
__le__(self, other) 定义了比较操作符 <= 的行为
__ge__(self, other) 定义了比较操作符 >= 的行为

十二、枚举类

12.1枚举类的使用

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

# 遍历枚举类型
for name, member in Month.__members__.items():
    print(name, '---------', member, '----------', member.value)

# 直接引用一个常量
print('\n', Month.Jan)

运行结果:

强娃学Python_第23张图片

我们使用 Enum 来定义了一个枚举类。

上面的代码,我们创建了一个有关月份的枚举类型 Month ,这里要注意的是构造参数,第一个参数 Month 表示的是该枚举类的类名,第二个 tuple 参数,表示的是枚举类的值;当然,枚举类通过 __members__ 遍历它的所有成员的方法。

注意的一点是 , member.value 是自动赋给成员的 int 类型的常量,默认是从 1 开始的。

而且 Enum 的成员均为单例(Singleton),并且不可实例化,不可更改

12.2自定义类型的枚举类

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from enum import Enum, unique

Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))


# @unique 装饰器可以帮助我们检查保证没有重复值
@unique
class Month(Enum):
    Jan = 'January'
    Feb = 'February'
    Mar = 'March'
    Apr = 'April'
    May = 'May'
    Jun = 'June'
    Jul = 'July'
    Aug = 'August'
    Sep = 'September '
    Oct = 'October'
    Nov = 'November'
    Dec = 'December'


if __name__ == '__main__':
    print(Month.Jan, '----------',
          Month.Jan.name, '----------', Month.Jan.value)
    for name, member in Month.__members__.items():
        print(name, '----------', member, '----------', member.value)

输出的结果:

强娃学Python_第24张图片

通过上面的例子,可以知道枚举模块定义了具有迭代 (interator) 和比较(comparison) 功能的枚举类型。 它可以用来为值创建明确定义的符号,而不是使用具体的整数或字符串。

十三、元类

13.1元类的使用

元类就是用来创建类的。也可以换个理解方式就是:元类就是类的类。

因为类也是对象,所以我们可以在程序运行的时候创建类。

Python 是动态语言。

动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。

首先我们新建一个 hello.py 的模块,然后定义一个 Hello 的 class ,

class Hello(object):
    def hello(self, name='Py'):
        print('Hello,', name)

然后在另一个模块中引用 hello 模块,并输出相应的信息。

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

from com.twowater.hello import Hello

h = Hello()
h.hello()

print(type(Hello))
print(type(h))

输出结果:

type() 函数可以查看一个类型或变量的类型,Hello 是一个 class ,它的类型就是 type ,而 h 是一个实例,它的类型就是 com.twowater.hello.Hello

前面的 com.twowater 是我的包名,hello 模块在该包名下。

 type() 函数不仅可以返回一个对象的类型,也可以创建出新的类型。在 Python 中,类也是对象,你可以动态的创建类。

13.2自定义元类

首先我们来了解下 __metaclass__ 属性

metaclass,直译为元类,简单的解释就是:

当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。

但是如果我们想创建出类呢?

那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。

连接起来就是:先定义metaclass,就可以创建类,最后创建实例。

所以,metaclass 允许你创建类或者修改类。

换句话说,你可以把类看成是 metaclass 创建出来的“实例”。

class MyObject(object):
    __metaclass__ = something…
[…]

流程图:

强娃学Python_第25张图片

示例:

class Foo(Bar):
    pass

首先判断 Foo 中是否有 __metaclass__ 这个属性?如果有,Python 会在内存中通过 __metaclass__ 创建一个名字为 Foo 的类对象(注意,这里是类对象)。如果 Python 没有找到__metaclass__ ,它会继续在 Bar(父类)中寻找__metaclass__ 属性,并尝试做和前面同样的操作。如果 Python在任何父类中都找不到 __metaclass__ ,它就会在模块层次中去寻找 __metaclass__ ,并尝试做同样的操作。如果还是找不到metaclass` ,Python 就会用内置的 type 来创建这个类对象。

其实 __metaclass__ 就是定义了 class 的行为。类似于 class 定义了 instance 的行为,metaclass 则定义了 class 的行为。可以说,class 是 metaclass 的 instance。

现在,我们基本了解了 __metaclass__ 属性,但是,也没讲过如何使用这个属性,或者说这个属性可以放些什么?

答案就是:可以创建一个类的东西。那么什么可以用来创建一个类呢?type,或者任何使用到 type 或者子类化 type 的东东都可以。

十四、待续

你可能感兴趣的:(python,python)