Python3开发笔记(简洁版)

一、开发编辑器

1. pycharm

2. IDLE(Python自带软件)

方法:Microsoft Store搜索 Python 安装

二、数据类型

  • Python中有以下几种主要的数据类型:
    数字(Numbers)、
    字符串(Strings)、
    布尔值(Boolean)、
    空值(None)、
    列表(Lists)、
    元组(Tuples)、
    字典(Dictionaries)、
    集合(Sets)。

1. 数字(Numbers)

  • 在Python中,number数据类型可以分为以下几种:整数、浮点数、复数。
# 整数  
num1 = 42 #【注:python定义变量的方式很简单,通过变量名 = 数据,即可定义一个变量】
num2 = 0b0110 #【注:在Python中,二进制整数使用前缀0b表示】  
  
# 浮点数  
num3 = 3.14159  

# 复数  
num4 = 3+4j

整数、浮点数可以直接进行四则运算:

# 加法
num1 = 10 
num2 = 0.5
result = num1 + num2
print(result) # ==> 10.5
# 减法
result = num1 - num2
print(result) # ==> 9.5
# 乘法
result = num1 * num2
print(result) # ==> 5.0
# 除法
result = num1 / num2
print(result) # ==>20.0

取模运算

print(3 % 2) # ==> 1
print(33 % 10) # ==> 3
print(99 % 30) # ==> 9

地板除

  • Python除了普通除法以外,还有一个特殊的除法被称为地板除,对于地板除,结果会忽略纯小数的部分,得到整数的部分,地板除使用//进行。
10//4 # ==> 2
10//2.5 # ==> 4.0
10//3 # ==> 3

小数点位数

  • 使用Python计算小数的时候,经常需要保留小数点后若干位,可以使用round()函数来处理,这里先了解round的调用方式,使用两个参数,第一个是需要保留小数点位数的数值,第二个是保留的位数。
num = 10 / 3
print(num) # ==> 3.3333333333333335
# 使用round保留两位小数
round(num, 2) # ==> 3.33

2. 字符串(Strings)

#定义一个字符串str变量
str = "Hello, World!"

# 字符串模板
template = 'Hello {}'
# 模板数据内容
world = 'World'
result = template.format(world)
print(result) # ==> Hello World

#读取字符串某个值
s = 'ABC'
a = s[0] # 第一个
b = s[1] # 第二个
c = s[2] # 第三个
print(a) # ==> A
print(b) # ==> B
print(c) # ==> C

#字符串切片
s1 = 'ABCDEFGHIJK'
abcd = s1[0:4] # 取字符串s1中的第一个字符到第五个字符,不包括第五个字符
print(abcd) # ==> ABCD

3. 布尔值(Boolean)

b1 = True #【在Python中,布尔值True和False都是关键字,首字母需要大写】
b2 = False #【布尔值可以用and、or和not运算(注意and,or,not都是Python语言本身的关键字)】

与运算:只有两个布尔值都为 True 时,计算结果才为 True。
或运算:只要有一个布尔值为 True,计算结果就是 True。
非运算:把True变为False,或者把False变为True:

#举例
True and True # ==> True
True and False # ==> False
True or True # ==> True
True or False # ==> True
not True # ==> False
not False # ==> True

4. 空值(None)

# 空值
n = None # 【在Python中,None表示空值,它是一个特殊的类型,叫做NoneType】

5. 列表(Lists)

  • 列表(list)是一种有序的容器,放入list中的元素,将会按照一定顺序排列。构造list的方法非常简单,使用中括号[]把需要放在容器里面的元素括起来,就定义了一个列表。
scores = [45, 60, 75, 86, 49, 100]
names = ['Alice', 'Bob', 'David', 'Ellena'] # 注意,字符串元素仍需要引号
L = ['Alice', 66, 'Bob', True, 'False', 100]
  • 列表是有序的,因此我们可以按顺序访问列表中的元素。
L = ['Alice', 66, 'Bob', True, 'False', 100]
for item in L:
    print(item)

#获取L中第二个元素
print(L[1]) #66

#使用-1来获取最后一个元素
print(L[-1]) #100

#使用索引的方式访问列表时,一定要特别注意不要越界
print(L[10]) # IndexError: list index out of range

#列表和字符串一样,也支持切片
print(L[0:2]) #['Alice', 66]
  • 向list添加新的元素
names = ['Alice', 'Bob', 'David', 'Ellena']
names.append('Candy') # append()方法:追加到列表的末尾
print(names) # ==> ['Alice', 'Bob', 'David', 'Ellena', 'Candy']
names.insert(2, 'LBJ') # insert()方法需要两个参数,分别是需要插入的位置,以及需要插入的元素
print(names) # ==> ['Alice', 'Bob', 'LBJ', 'David', 'Ellena', 'Candy']
  • 从list删除元素
L = ['Alice', 'Bob', 'Candy', 'David', 'Ellena']
name = L.pop() # pop()方法默认删除列表的最后一个元素,并返回
print(name) # ==> Ellena
print(L) # ==> L = ['Alice', 'Bob', 'Candy', 'David']
name2 = L.pop(2) # pop()还可以接收一个参数,指定需要删除的元素的位置
print(L) # ==> L = ['Alice', 'Bob', 'David']
  • 替换list中的元素
L = ['Alice', 'Bob', 'Candy', 'David', 'Ellena']
L[2] = 'Canlina'
print(L) # ['Alice', 'Bob', 'Canlina', 'David', 'Ellena']

6. 元组(Tuples)

  • 元组(tuple)和list一样,也是一个有序容器,在元组中,同样可以包含0个或者多个元素,并且也支持索引访问、切片等操作。
  • 定义元组的方式是使用小括号()将元组内的元素括起来。
T = ('Alice', 'Bob', 'Candy', 'David', 'Ellena')
# 通过下标的方式访问元素
print(T[0]) # ==> Alice
print(T[4]) # ==> Ellena
# 切片
print(T[1:3]) # ==> ('Bob', 'Candy')
  • tuple和list不一样的是,tuple是固定不变的,一旦变成tuple,tuple中的每一个元素都不可被改变,同时也不能再往tuple中添加数据,而list是可以的。【请注意,元组的这个特性是非常重要的,在运行上tuple的性能是list的数倍。】
T = ('Alice', 'Bob', 'Candy', 'David', 'Ellena')
# 替换元素
T[1] = 'Boby' # TypeError: 'tuple' object does not support item assignment
  • 元组类型和列表类型相互转换
L = ['Alice', 'Bob', 'Candy', 'David', 'Ellena']
T = tuple(L)
print(T) # ==> ('Alice', 'Bob', 'Candy', 'David', 'Ellena')
L2 = list(T)
print(L2) # ==> ['Alice', 'Bob', 'Candy', 'David', 'Ellena']
  • tuple元素的其他方法

(1)count:用来统计tuple中某个元素出现的次数。


T = (1, 1, 2, 2, 3, 3, 1, 3, 5, 7, 9)
print(T.count(1)) # ==> 3
print(T.count(5)) # ==> 1

#对于不存在的元素,count方法不会报错,而是返回0,这是合理的,因为元组里面有0个不存在的元素。
print(T.count(10)) # ==> 0

(2)index:可以返回指定元素的下标,当一个元素多次重复出现时,则返回第一次出现的下标位置。

T = (1, 1, 2, 2, 3, 3, 1, 3, 5, 7, 9)
T.index(9) # ==> 10
T.index(5) # ==> 8
T.index(1) # ==> 0 # 多次出现,返回第一次出现的位置

#注意,index()方法和count()方法不一样,当指定的元素不存在时,使用index()方法Python会报错。
T.index(100)
# 报错 ValueError: tuple.index(x): x not in tuple
  • 要定义只有一个元素的tuple,需要在元素后面添加一个逗号
T = (1, )
print(T) # ==> (1, )
  • tuple的元素也可以是tuple
T = ((1+2),  ((1+2),), ('a'+'b'), (1, ), (1,2,3,4,5))
print(len(T)) # 5
  • 对于tuple,它和list一个最大的不同点就是tuple是不可变的,tuple里面的元素,也是不可替换的。但是这针对的是仅包含基础数据类型(数字类型、布尔类型、字符串类型)的数据,对于组合数据类型,则不受这个约束。
T = (1, 'CH', [3, 4])
L = T[2]
print(L) # ==> [3, 4]
# 尝试替换L中的元素
L[1] = 40
print(L) # ==> [3, 40]
print(T) # ==> (1, 'CH', [3, 40])

7. 字典(Dictionaries)

  • 在dict中,每一项包含一个key和一个value,key和value是一一对应的

(1)读取dict元素

d = {
    'Alice': 45,
    'Bob': 60,
    'Candy': 75,
    'David': 86,
    'Ellena': 49,
    'Gaven': 86
}
print(d['Bob']) # ==> 60
print(d['Alice']) # ==> 45
print(d['Dodo']) # 报错:KeyError: 'Dodo'

# get方法读取【推荐】
# print(d.get('Dodo')) # ==> None 【dict本身提供get方法,把key当作参数传递给get方法,
# 就可以获取对应的value,当key不存在时,也不会报错,而是返回None】

(2)添加dict元素

d = {
    'Alice': 45,
    'Bob': 60,
    'Candy': 75,
    'David': 86,
    'Ellena': 49
}
d['Alice'] = 100 # Alice存在,将被替换
d['Dodo'] = 88
d['Mimi'] = [72, 73]
d['Mimi'].append(75)
print(d)

(3)删除dict元素

  • pop()方法需要指定需要删除的元素的key,当key不存在时,同样会引起错误。
d = {
    'Alice': 45,
    'Bob': 60,
    'Candy': 75,
    'David': 86,
    'Ellena': 49
}
d.pop('Alice')
print(d)
d.pop('LBJ') # 报错 KeyError: 'LBJ'

(4)dict的特点

  • 查找速度快
  • dict的第一个特点是查找速度快,无论dict有10个元素还是10万个元素,查找速度都一样。而list的查找速度随着元素增加而逐渐下降。
  • 不过dict的查找速度快不是没有代价的,dict的缺点是占用内存大,还会浪费很多内容,list正好相反,占用内存小,但是查找速度慢。
  • key不可变

tuple是不可变的,list是可变的,因此tuple可以作为dict的key,但是list不可以作为dict的key,否则将会报错。

d = {
    'Alice': 45,
    'Bob': 60,
    'Candy': 75,
    'David': 86,
    'Ellena': 49
}
key = (1, 2, 3) # 以tuple作为key
d[key] = True
print(d) # {'Alice': 45, 'Bob': 60, 'Candy': 75, 'David': 86, 'Ellena': 49, (1, 2, 3): True}
key2 = [1, 2, 3]
d[key2] = True # 报错 TypeError: unhashable type: 'list'
  • 遍历dict

遍历dict有两种方法, 第一种是遍历dict的所有key,并通过key获得对应的value。

d = {
    'Alice': 45,
    'Bob': 60,
    'Candy': 75,
    'David': 86,
    'Ellena': 49
}
for key in d: 
    value = d[key]
    if value > 60:
        print(key, value)
# ==> Candy 75
# ==> David 86

第二种方法是通过dict提供的items()方法,items()方法会返回dict中所有的元素,每个元素包含key和value。

for key, value in d.items():
    if value > 60:
        print(key, value)
# ==> Candy 75
# ==> David 86

(5)dict的其他方法

  • 获取dict的所有key

dict提供keys()函数,可以返回dict中所有的key。

d = {'Alice': [50, 61, 66], 'Bob': [80, 61, 66], 'Candy': [88, 75, 90]}
for key in d.keys():
    print(key)
# ==> Alice
# ==> Bob
# ==> Candy
  • 获取dict所有的value

dict提供values()函数,可以返回dict中所有的value。

d = {'Alice': [50, 61, 66], 'Bob': [80, 61, 66], 'Candy': [88, 75, 90]}
for key in d.values():
    print(key)
# ==> [50, 61, 66]
# ==> [80, 61, 66]
# ==> [88, 75, 90]
  • 清除所有元素

dict提供clear()函数,可以直接清除dict中所有的元素。

d = {'Alice': [50, 61, 66], 'Bob': [80, 61, 66], 'Candy': [88, 75, 90]}
d.clear()
print(d) # ==> {}

8. 集合(Sets)

  • set和list类似,拥有一系列元素,但是set和list不一样,set里面的元素是不允许重复的,并且,set里面的元素是没有顺序的。
s = set([1, 4, 3, 2, 5, 4, 2, 3, 1])
print(s) # ==> set([1, 2, 3, 4, 5])

(1)读取set元素

由于set里面的元素是没有顺序的,因此我们不能像list那样通过索引来访问。访问set中的某个元素实际上就是判断一个元素是否在set中,这个时候我们可以使用in来判断某个元素是否在set中。

s = set([1, 4, 3, 2, 5, 4, 2, 3, 1])
print(1 in s) #True
print(6 in s) #False

(2)添加set元素

names = ['Alice', 'Bob', 'Candy', 'David', 'Ellena']
name_set = set(names)
name_set.add('Gina') #add:逐个添加
print(name_set) # ==> set(['Gina', 'Alice', 'Candy', 'David', 'Ellena', 'Bob'])

new_names = ['Hally', 'Isen', 'Jenny', 'Karl']
name_set.update(new_names) #update:批量添加
print(name_set) # ==> set(['Gina', 'Karl', 'Bob', 'Hally', 'Jenny', 'Alice', 'David', 'Isen', 'Ellena', 'Candy'])

(3)删除set元素

set提供了remove()方法允许我们删除set中的元素。如果remove的元素不在set里面的话,那么将会引发错误。

name_set = set(['Jenny', 'Ellena', 'Alice'])
name_set.remove('Jenny')
print(name_set) # ==> set(['Ellena', 'Alice'])

(4)set的其他方法

  • 不会报错的删除方法discard()
name_set = set(['Jenny', 'Ellena', 'Alice'])
name_set.discard('LBJ')
print(name_set) # ==> set(['Jenny','Ellena', 'Alice'])
  • 清除所有元素的方法clear()

和dict一样,set也提供了clear()方法,可以快速清除set中的所有元素。

name_set = set(['Jenny', 'Ellena', 'Alice'])
name_set.clear()
print(name_set) # ==> set([])
  • 集合的子集和超集

set提供方法判断两个set之间的关系,比如两个集合set,判断其中一个set是否为另外一个set的子集或者超集。

s1 = set([1, 2, 3, 4, 5])
s2 = set([1, 2, 3, 4, 5, 6, 7, 8, 9])
# 判断s1是否为s2的子集
s1.issubset(s2) # ==> True
# 判断s2是否为s1的超集
s2.issuperset(s1) # ==> True
  • 判断集合是否重合

isdisjoint:用于检查两个集合是否完全独立,即它们没有任何共同的元素。

set1 = {1, 2, 3}  
set2 = {4, 5, 6}  
print(set1.isdisjoint(set2))  # 输出:True  
  
set3 = {2, 4, 6}  
print(set1.isdisjoint(set3))  # 输出:False


【扩展】:使用type,可以区分不同的数据类型

print(type(3.1415926))
print(type('Learn Python in imooc.'))
print(type(100))
print(type(0b1101))
print(type([1,2,3]))
'''
结果:





'''

三、循环控制语句

1. if-else相关:

score = 59
if score < 60:
    print('抱歉,考试不及格')
else:
    if score >= 90:
        print('恭喜你,拿到卓越的成绩')
    else:
        if score >= 80:
            print('恭喜你,拿到优秀的成绩')
        else:
            print('恭喜你,考试及格')

可以使用if-elif-else语句来简化以上的逻辑。其中elif就是else if的意思。

score = 59
if score < 60:
    print('抱歉,考试不及格')
elif score >= 90:
    print('恭喜你,拿到卓越的成绩')
elif score >= 80:
    print('恭喜你,拿到优秀的成绩')
else:
    print('恭喜你,考试及格')

2. for循环

s = 'ABCD'
for item in s:
    print(item) # 注意缩进

3. while循环

  • 计算1~100的和
num = 1
sum = 0
while num <= 100:
    sum = sum + num # 注意缩进
    num = num + 1 # 注意缩进
print(sum) # ==> 5050
  • 计算0~1000以内,所有偶数的和
num = 1
sum = 0
while True:
    if num > 1000:
        break
    if num % 2 == 0: sum = sum + num
    num = num + 1
print(sum) # 250500
  • 输出字符串s中第10个以后的字符
s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
num = 1
for ch in s:
    if num < 10:
        num = num + 1
        continue # 当num < 10时,跳过后续循环代码,继续下一次for循环
    print(ch)
    num = num + 1

4. 嵌套循环

s1 = 'ABC'
s2 = '123'
for x in s1:
    for y in s2:
        print(x + y)
'''
结果:
A1
A2
A3
B1
B2
B3
C1
C2
C3
'''

四、函数

1. 定义函数

  • 在Python中,定义一个函数要使用 def 语句,依次写出函数名、括号()、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用 return 语句返回。
def fn(x):
    if x >= 0:
        return x
    else:
        return -x
result = fn(1)
print(1)

2. 递归函数

  • 计算n的阶乘
def fact(n):
    if n==1:
        return 1
    return n * fact(n - 1)
res = fact(5)
print(res) #120

我们可以拆解fact(5)计算的详细逻辑:

===> fact(5)
===> 5 * fact(4)
===> 5 * (4 * fact(3))
===> 5 * (4 * (3 * fact(2)))
===> 5 * (4 * (3 * (2 * fact(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120

五、面向对象编程

1. 类的定义与实例化

class Person(object): pass
xiaohong = Person()
xiaoming = Person()
  • 在Python中,pass是一个空操作语句,意思是什么都不做。pass常常用在定义一个空的类、函数、循环或条件语句等场合。

  • 在以上的代码中,pass不能删除。pass是Python语法的一部分,用于定义一个空的类(如Person)。如果删除pass,Python解释器会报错,因为这不符合Python的语法规则。

  • 当你创建类的实例时,如xiaohong = Person()和xiaoming = Person(),你实际上是在调用Person类的构造方法(如果定义了的话),然后创建了两个新的Person对象。这些对象是独立的,它们的属性不会互相影响。

  • 如果你删除pass,你需要在类中定义一个构造方法(使用__init__方法),并在这个构造方法中初始化你想要的属性。例如:

class Person(object):
    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age
xiaoming = Person('Xiao Ming', 'boy', 13)
xiaohong = Person('Xiao Hong', 'girl', 14)
print(xiaohong.name) # Xiao Hong
print(xiaohong.sex) # girl
print(xiaohong.age) # 14
# 但当访问不存在的属性时,依然会报错
print(xiaohong.birth)
  • 以上需要注意的是,init() 方法的第一个参数必须是 self(此处self不能省略,self 也可以用别的名字,但建议使用习惯用法),后续参数则可以自由指定,和定义函数没有任何区别。定义类后,就可以相应的实例化对象了,需要注意的是,在实例化的时候,需要提供除self以外的所有参数。

2. 类属性

在前面,实例对象绑定的属性只属于这个实例,绑定在一个实例上的属性不会影响其它实例;同样的,类也可以绑定属性,但是类的属性不属于任何一个对象,而是属于这个类。如果在类上绑定一个属性,则所有实例都可以访问类的属性,并且,所有实例访问的类属性都是同一个!也就是说,实例属性每个实例各自拥有,互相独立,而类属性有且只有一份。

定义类属性可以直接在 class 中定义,比如在Animal类中,加入地域的类属性:

class Animal(object):
    localtion = 'Asia'
    def __init__(self, name, age):
        self.name = name
        self.age = age

在上面的代码中,localtion就是属于Animal这个类的类属性,此后,通过Animal()实例化的所有对象,都可以访问到localtion,并且得到唯一的结果。

dog = Animal('wangwang', 1)
cat = Animal('mimi', 3)
print(dog.localtion) # ==> Asia
print(cat.localtion) # ==> Asia
# 类属性,也可以通过类名直接访问
print(Animal.localtion) # ==> Asia

类属性也是可以动态添加和修改的,需要注意的是,因为类属性只有一份,所以改变了,所有实例可以访问到的类属性都会变更:

Animal.localtion = 'Africa'
print(cat.localtion) # ==>Africa
print(dog.localtion) # ==>Africa
  • 案例:请给 Animal类添加一个类属性 count,每创建一个实例,count 属性就加 1,这样就可以统计出一共创建了多少个 Animal的实例。
class Animal():
    count = 0
    def __init__(self,name,age):
        Animal.count+=1 # 每创建一个实例,count 属性就加 1
        self.name = name
        self.age = age
dog = Animal('dog',2)
cat = Animal('cat',3)
print(Animal.count) #2

3. 类属性和实例属性的优先级

可以看到,属性可以分为类属性和实例属性,那么问题就来了,如果类属性和实例属性名字相同时,会怎么样?

class Animal(object):
    localtion = 'Asia'
    def __init__(self, name, age, localtion):
        self.name = name
        self.age = age
        self.localtion = localtion
dog = Animal('wangwang', 1, 'GuangDong')
cat = Animal('mimi', 3, 'ChongQing')
print(dog.localtion) # ==> GuangDong
print(cat.localtion) # ==> ChongQing
print(Animal.localtion) # ==> Asia

可见,在类属性和实例属性同时存在的情况下,实例属性的优先级是要高于类属性的,在操作实例的时候,优先是操作实例的属性。

那通过实例,可不可以修改类属性呢?我们来尝试一下:

cat.localtion = 'Africa'
print(Animal.localtion) # ==> Asia

这里依然打印了Asia,可见通过实例是无法修改类的属性的,事实上,通过实例方法修改类属性,只是给实例绑定了一个对应的实例属性:

# 新增的实例属性
print(cat.localtion) # ==> Africa

因此,需要特别注意,尽量不要通过实例来修改类属性,否则很容易引发意想不到的错误。

4. 属性访问限制

并不是所有的属性都可以被外部访问的,这种不能被外部访问的属性称为私有属性。私有属性是以双下划线’_ _'开头的属性。

  • 类私有属性
class Animal(object):
    __localtion = 'Asia'

print(Animal.__localtion) # 报错 AttributeError: type object 'Animal' has no attribute '__localtion'
  • 实例私有属性
class Animal(object):
    def __init__(self, name, age, localtion):
        self.name = name
        self.age = age
        self.__localtion = localtion

dog = Animal('wangwang', 1, 'GuangDong')
print(dog.name) # ==> wangwang
print(dog.age) # ==> 1
print(dog.__localtion) # 报错 AttributeError: 'Animal' object has no attribute '__localtion'

在外部访问私有属性将会抛出异常,提示没有这个属性。虽然私有属性无法从外部访问,但是,从类的内部是可以访问的。私有属性是为了保护类或实例属性不被外部污染而设计的。

5. 定义实例方法

前面提到,私有属性没有办法从外部访问,只能在类的内部操作;那如果外部需要操作私有属性怎么办?这个时候可以通过定义类或者实例的方法来操作私有属性。

实例的方法指的就是在类中定义的函数,实例方法的第一个参数永远都是self,self是一个引用,指向调用该方法的实例对象本身,除此以外,其他参数和普通函数是完全一样的。

class Person(object):

    def __init__(self, name):
        self.__name = name

    def get_name(self):
        return self.__name

在上面的定义,name是实例的私有属性,从外部是无法访问的,而get_name(self) 就是一个实例方法,在实例方法里面是可以操作私有属性的,注意,它的第一个参数是self。

另外,init(self, name)其实也可看做是一个特殊的实例方法。通过定义get_name(self)方法,在外部就可以通过这个方法访问私有属性了。

​p = Person('Alice')
print(p.get_name()) # ==> Alice

6. 定义类方法

为了操作实例对象的私有属性,我们定义了实例方法;同样的,如果需要需要操作类的私有属性,则应该定义类的方法。默认的,在class中定义的全部是实例方法,实例方法第一个参数 self 是实例本身。

要在class中定义类方法,需要这么写:

class Animal(object):
    __localtion = 'Asia'
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @classmethod
    def set_localtion(cls, localtion):
        cls.__localtion = localtion

    @classmethod
    def get_localtion(cls):
        return cls.__localtion

print(Animal.get_localtion()) # ==> Asia
Animal.set_localtion('Afica')
print(Animal.get_localtion()) # ==> Africa

和实例方法不同的是,这里有两点需要特别注意:

  • 类方法需要使用@classmethod来标记为类方法,否则定义的还是实例方法
  • 类方法的第一个参数将传入类本身,通常将参数名命名为 cls,上面的 cls.__localtion 实际上相当于Animal.__localtion。

六、类的继承

1. 继承类

对人类的抽象可以定义为Person类,而学生、老师等,也都是人类,所以,在Python当中,如果定义学生Student的类,可以继承Person类。

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

接着定义Student类,在定义Student类的时候,由于继承了Person类,所以Student类自动拥有name、gender属性,因此,在定义Student类的时候,只需要把额外的属性加上即可。

class Student(Person):
    def __init__(self, name, gender, score):
        super(Student, self).__init__(name, gender)
        self.score = score

student = Student('Alice', 'girl', 100)
print(student.name) # ==> Alice
print(student.gender) # ==> girl
print(student.score) # ==> 100

在定义继承类的时候,有两点是需要【注意】的:

  • class Student()定义的时候,需要在括号内写明继承的类Person
  • 在__init__()方法,需要调用super(Student, self).init(name, gender),来初始化从父类继承过来的属性

2.判断类型

随着我们学习步伐的前进,我们的程序会出现越来越多的类型,有我们自己定义的类,也有Python自有的str、list、dict等,他们的本质都是都是Python中的一种数据类型,这时有必要去判断数据的类型,通过函数isinstance()可以判断一个变量的类型

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

class Student(Person):
    def __init__(self, name, gender, score):
        super(Student, self).__init__(name, gender)
        self.score = score

class Teacher(Person):
    def __init__(self, name, gender, course):
        super(Teacher, self).__init__(name, gender)
        self.course = course

p = Person('Tim', 'Male')
s = Student('Bob', 'Male', 88)
t = Teacher('Alice', 'Female', 'English')
print(isinstance(p, Person)) # True,p是Person类型
print(isinstance(p, Student)) # False,p不是Student类型
print(isinstance(p, Teacher)) # False,p不是Teacher类型

print(isinstance(s, Person)) #True,s是Person类型
print(isinstance(s, Student)) #True,s是Student类型
print(isinstance(s, Teacher)) #False,s不是Teacher类型

s 是Student类型,不是Teacher类型,这很容易理解。但是,s 也是Person类型,因为Student继承自Person,虽然它比Person多了一些属性和方法,但是,把 s 看成Person的实例也是可以的。

这说明在一条继承链上,一个实例可以看成它本身的类型,也可以看成它父类的类型。

isinstance也可以用于Python自有数据类型的判断。

s = 'this is a string.'
n = 10
print(isinstance(s, str)) # ==> True
print(isinstance(n, str)) # ==> False

3.多态

类具有继承关系,并且子类类型可以向上转型看做父类类型,如果我们从 Person 派生出 Student和Teacher ,并都写了一个who() 方法:

class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender
    def who(self):
        return 'I am a Person, my name is %s' % self.name

class Student(Person):
    def __init__(self, name, gender, score):
        super(Student, self).__init__(name, gender)
        self.score = score
    def who(self):
        return 'I am a Student, my name is %s' % self.name

class Teacher(Person):
    def __init__(self, name, gender, course):
        super(Teacher, self).__init__(name, gender)
        self.course = course
    def who(self):
        return 'I am a Teacher, my name is %s' % self.name
class Boss(Person):
    def __init__(self, name, gender,company):
        super(Boss, self).__init__(name, gender)
        self.company = company

p = Person('Tim', 'Male')
s = Student('Bob', 'Male', 88)
t = Teacher('Alice', 'Female', 'English')
b = Boss('Bob', 'Male', 'Alibaba')
print(p.who()) # I am a Person, my name is Tim
print(s.who()) # I am a Student, my name is Bob
print(t.who()) # I am a Teacher, my name is Alice
print(b.who()) # ==> I am a Person, my name is Bob

这种行为称为多态。从定义上来讲,Student和Teacher都拥有来自父类Person继承的who()方法,以及自己定义的who()方法。但是在实际调用的时候,会首先查找自身的定义,如果自身有定义,则优先使用自己定义的函数;如果没有定义,则顺着继承链向上找。

七、类的特殊方法

1. 类的__str__方法

对于Python的内建对象,比如int、dict、list等,通过str()方法,可以把这些对象转换为字符串对象输出。

num = 12
str(num) # ==> '12'
d = {1: 1, 2: 2}
str(d) # ==> '{1: 1, 2: 2}'
l = [1,2,3,4,5]
str(l) # ==> '[1, 2, 3, 4, 5]'

2. 类的__slots__方法

由于Python是动态语言,任何实例在运行期都可以动态地添加属性。比如:

class Student(object):
    def __init__(self, name, gender, score):
        self.name = name
        self.gender = gender
        self.score = score

此时,Student类有三个属性,name、gender、score,由于是动态语言,在运行时,可以随意添加属性。

student = Student('Bob', 'Male', 99)
student.age = 12 # ==> 动态添加年龄age属性

如果要限制添加的属性,例如,Student类只允许添加 name、gender和score 这3个属性,就可以利用Python的一个特殊的__slots__来实现。

class Student(object):
    __slots__ = ('name', 'gender', 'score')
    def __init__(self, name, gender, score):
        self.name = name
        self.gender = gender
        self.score = score
student = Student('Bob', 'Male', 99)
student.age = 12 # AttributeError: 'Student' object has no attribute 'age'

3. 类的其他方法

在Python中,类似于__str__方法的特殊方法还有很多。这些特殊方法通常被称为“魔法方法”或“双下划线方法”。比如:

  • repr:返回一个对象的“官方”字符串表示形式。与__str__不同,__repr__的返回值应该是一个精确的、无歧义的字符串表示形式,用于调试和开发。
  • eq:定义对象的相等性比较操作(例如obj1 == obj2)。该方法应返回一个布尔值,表示两个对象是否相等。
  • iter:使对象成为可迭代的。该方法应返回一个迭代器对象,用于遍历对象的元素。
  • getitem:使得对象支持下标操作(例如obj[index])。该方法用于获取指定索引处的元素。
  • setitem:使得对象支持下标赋值操作(例如obj[index] = value)。该方法用于设置指定索引处的元素值。
  • delitem:使得对象支持下标删除操作(例如del obj[index])。该方法用于删除指定索引处的元素。

八、模块

1. 创建模块

Python语言本身提供了非常多的模块,比如数学模块math、cmath、decimal、statistics;文件模块pathlib、stat、shutil等;除了使用官方模块,有时候也需要自定义模块。

如果我们需要创建一个tools模块,用来实现众多的工具函数,那么我们可以创建一个tools.py的文件,并在这个文件里面实现一些函数,如:say_hello()函数、say_goodbye()函数。

# tools.py
def say_hello():
    print('hello')

def say_goodbye():
    print('goodbye')

2. 导入模块

(1)导入官方模块

Python使用import语句导入一个模块,导入官方模块,不需要考虑路径的问题,例如,导入系统自带的模块 math,直接导入即可。

import math

导入以后,你就可以认为math是一个指向已导入模块的变量,通过该变量,我们可以访问math模块中所定义的所有公开的函数、变量和类:

# 属性:圆周率
import math
print(math.pi) # 3.141592653589793

# 函数:次方
print(math.pow(2, 3)) # 8.0

如果希望导入模块的指定部分属性或函数,那么使用from…import…语句。

# 属性:圆周率
from math import pi
print(pi) # 3.141592653589793

这个时候,由于pow()函数没有导入,所以是不能使用pow()函数的。如果希望导入模块里面的所有内容,那么使用from …import *语句。

from math import *
print(pi) # 3.141592653589793
print(pow(2, 3)) # 8.0

如果从一个模块导入函数,有可能会遇到导入的函数与本文件的函数冲突的情况。例如:本文件定义了一个pow()函数,同时从math模块也导入了一个pow()函数,这种情况下就会引起冲突;事实上,这种冲突的情况经常发生。

有两种方法可以解决这个问题,第一种是直接导入模块,不指定导入模块里面的具体内容;第二种方法就是使用from … import as …语句,as类似重命名,可以把导入的函数或属性重命名为别的名字。

from math import pow as mathpow
print(mathpow(2, 3)) # 8.0
(2)导入自定义模块

在pycharm中,创建了demo.py模块如下:

# demo.py模块
def say_hello():
    print('say_hello')

假如想要在demo2模块中导入demo模块,但不知道demo在哪个文件夹目录下,此时有2种方法:

  • 使用__file__属性:这个属性可以返回模块文件的路径。但是,这个方法并不总是有效,特别是当模块是内置的或者是在某种特殊的环境中加载的时候。
import demo
print(demo.__file__) # E:\myProject\python_project\pythonProject\demo.py
  • 使用os模块的path.abspath()函数:可以返回给定文件或目录的绝对路径。
import os
import demo
print(os.path.abspath(demo.__file__)) # E:\myProject\python_project\pythonProject\demo.py

这样,我们得到demo.py模块的路径后,就可以再demo2进行导入使用了:

import sys
sys.path.append('E:\\myProject\\python_project\\pythonProject') # 添加模块demo所在的目录到系统路径中 
import demo
demo.say_hello()

九、输入内容

到目前为止,我们编写的程序都是直接运行的,在运行过程中并没有接收程序外部的输入。

如果想向Python程序输入内容,使用input()函数即可接收外部的输入。如:

num = input('please input number: ')
print(num) 
print(type(num))

IDLE运行,效果如下:
在这里插入图片描述
注意:input()函数中,输入的是字符串,有时候需要转型为数字类型,否则会报错。例如:

num = input('please input number: ')
num = int(num) #不转为整形,下面代码将无法运行
for i in range(1,num):
    print(i)

十、函数式编程

1. map()

map()是 Python 内置的高阶函数,它接收一个函数 f 和一个 list,并通过把函数 f依次作用在list的每个元素上,map()函数会返回一个迭代器,可以依次迭代得到原来list的元素被函数f处理后的结果。

例如,对于list [1, 2, 3, 4, 5, 6, 7, 8, 9]。
如果希望把list的每个元素都作平方,就可以利用map()函数。

我们定义需要传入函数f(x)=x*x,就可以利用map()函数完成这个计算:

def f(x):
    return x*x

for item in map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]):
    print(item) 
# 结果:[1, 4, 9, 10, 25, 36, 49, 64, 81]

2. reduce()

和map函数一样,reduce()函数也是Python内置的一个高阶函数。reduce()函数接收的参数和 map() 类似,一个函数 f,一个list,但行为和 map()不同,reduce()传入的函数 f 必须接收两个参数,reduce()对list的每个元素反复调用函数f,并返回最终结果值。

例如,编写一个f函数,接收x和y,返回x和y的和:

from functools import reduce

def f(x, y):
    return x + y

print(reduce(f, [1,3,5,7,9])) # ==> 25

得到的结果是25,实际过程是这样的,reduce()函数会做如下计算:

先计算头两个元素:f(1, 3),结果为4;
再把结果和第3个元素计算:f(4, 5),结果为9;
再把结果和第4个元素计算:f(9, 7),结果为16;
再把结果和第5个元素计算:f(16, 9),结果为25;
由于没有更多的元素了,计算结束,返回结果25。

上述计算实际上是对 list 的所有元素求和。虽然Python内置了求和函数sum(),但是,利用reduce()求和也很简单。
reduce()还可以接收第3个可选参数,作为计算的初始值。如果把初始值设为100,计算:

print(reduce(f, [1, 3, 5, 7, 9], 100)) # ==> 125

结果将变为125,因为第一轮计算是:

计算初始值和第一个元素:f(100, 1),结果为101。

3. filter()

filter()函数是 Python 内置的另一个有用的高阶函数,filter()函数接收一个函数 f 和一个list,这个函数 f 的作用是对每个元素进行判断,返回 True或 False,filter()根据判断结果自动过滤掉不符合条件的元素,并返回一个迭代器,可以迭代出所有符合条件的元素。

例如,要从一个list [1, 4, 6, 7, 9, 12, 17]中删除偶数,保留奇数,首先,要编写一个判断奇数的函数:

def is_odd(x):
    return x % 2 == 1
for item in filter(is_odd, [1, 4, 6, 7, 9, 12, 17]):
    print(item)

4. sorted()

Python内置的 sorted()函数可对list进行排序:

>>> sorted([36, 5, 12, 9, 21])
[5, 9, 12, 21, 36]

可以看到,sorted()函数,默认是由小到大排序列表的元素。

>>> score = [('Alice', 72), ('Candy', 90), ('Bob', 62)]
>>> sorted(score)
[('Alice', 72), ('Bob', 62), ('Candy', 90)]

当list的每一个元素又是一个容器时,则会以第一个元素来排序,比如在score中,每个元素都是包含名字和成绩的一个tuple,sorted()函数则按名字首字母进行了排序并返回。

对于上述排序成绩的情况,默认是按照第一个名字进行排序的,有没有办法让sorted()函数按照成绩来进行排序呢?

如果需要按照成绩高低进行排序,需要指定排序的字段是成绩,sorted接受key参数,用来指定排序的字段,key的值是一个函数,接受待排序列表的元素作为参数,并返回对应需要排序的字段。因此,sorted()函数也是高阶函数。

score = [('Alice', 72), ('Candy', 90), ('Bob', 62)]
def k(item):
    return item[1] # ==> 按成绩排序,成绩是第二个字段

new_socre = sorted(score, key=k)
print(new_socre) # [('Bob', 62), ('Alice', 72), ('Candy', 90)]

# 如果需要倒序,指定reverse参数即可
new_socre2 = sorted(score, key=k, reverse=True)
print(new_socre2) # [('Candy', 90), ('Alice', 72), ('Bob', 62)] 

5. range()

在 Python 中,range 是一个用于生成一系列整数的函数。它可以接受一个、两个或三个参数,返回一个表示等差整数序列的可迭代对象。

range 的基本语法是:

range(stop)
range(start, stop)
range(start, stop, step)
  • start: 序列的起始值,默认为0。
  • stop: 序列的结束值,但不包括该值。
  • step: 步长(增量),表示连续数字之间的差。默认为1。

range 生成的序列是左闭右开的,即包括起始值,但不包括结束值。

++ 只有一个参数 (stop):

for i in range(5):
    print(i)
# 结果:
# 0
# 1
# 2
# 3
# 4

++ 两个参数 (start, stop):

for i in range(2, 8):
    print(i)
# 结果:
# 2
# 3
# 4
# 5
# 6
# 7

++ 三个参数 (start, stop, step):

for i in range(1, 10, 2):
    print(i)
# 结果:
# 1
# 3
# 5
# 7
# 9

range 还可以用于创建一个列表:

my_list = list(range(5, 20, 3))
print(my_list) # [5, 8, 11, 14, 17]

案例:找出1到20内的所有质数

for num in range(2, 21):  # 起始值为2,对于范围在2到20的每一个数字
    for i in range(2, num):  # 对于从2到num-1的每一个数字
        if num % i == 0:  # 如果num能被i整除
            break  # 退出内层循环,说明num不是质数
    else:
        print(num)  # 如果内层循环完整执行(即未中断),则说明num是质数,打印输出
# 结果:2、3、5、7、11、13、17、19

6. 闭包

在函数内部定义的函数和外部定义的函数是一样的,只是他们无法被外部访问:

def g():
    print('g()...')

def f():
    print('f()...')
    return g
f() # ==> f()...

将g的定义移入函数 f 内部,防止其他代码调用 g:

def f():
    print('f()...')
    def g():
        print('g()...')
    return g
f() # ==> f()...

假如,有这么一个 calc_sum 函数:

def calc_sum(list_):
    def lazy_sum():
        return sum(list_)
    return lazy_sum

注意: 发现没法把 lazy_sum 移到 calc_sum 的外部,因为它引用了 calc_sum 的参数 list_。

像这种内层函数引用了外层函数的变量(参数也算变量),然后返回内层函数的情况,称为闭包(Closure)。

闭包的特点是返回的函数还引用了外层函数的局部变量,所以,要正确使用闭包,就要确保引用的局部变量在函数返回后不能变。举例如下:

# 希望一次返回3个函数,分别计算1x1,2x2,3x3:
def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs

f1, f2, f3 = count()
print(f1()) #9 【因为f1现在才计算i*i,但现在i的值已经变为3】
print(f2()) #9
print(f3()) #9

你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果全部都是 9(请自己动手验证)。

原因就是当count()函数返回了3个函数时,这3个函数所引用的变量 i 的值已经变成了3。由于f1、f2、f3并没有被调用,所以,此时他们并未计算 i*i,当 f1 被调用时,i的值已经变为3。

因此,返回函数不要引用任何循环变量,或者后续会发生变化的变量。

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