python的一些基础知识学习

列表(list)和元组(tuple)

  • 列表和元组,都是一个可以放置任意数据类型的有序集合,比如里面可以同时包含int和string类型
  • 都是有序的
  • 列表是动态的,长度大小不固定,可以随意地增加、删减或者改变元素。
  • 元组是静态的,长度大小固定,无法增加删减或者改变

常规操作

  • 关于赋值,list可以很轻松的根据索引赋值,但是tuple不可以
listA = [1, 2, 3, 4]
listA[3] = 10
print(listA)
# [1, 2, 3, 10]
tupleA = (1, 2, 3, 4)
# tupleA[3] = 10
# 执行会报错,'tuple' object does not support item assignment
  • 关于添加元素,其实tuple也可以增加元素,但是本质是创建了一个新的元组,然后把原来两个元组的值依次填充进去。
listA = [1, 2, 3, 4]
listA.append(5)
print(listA)
# [1, 2, 3, 4, 5]
tupleA = (1, 2, 3, 4)
tupleB = (5,6)
# 实际上就是创建了一个新的元组,然后把原来两个元组的值依次填充进去。
print(tupleA+tupleB)
# (1, 2, 3, 4, 5, 6)
  • 其他常用的函数
    • count(item) 表示统计列表 / 元组中 item 出现的次数。
    • index(item) 表示返回列表 / 元组中 item 第一次出现的索引。
    • list.reverse() 和 list.sort() 分别表示原地倒转列表和排序(注意,元组没有内置的这两个函数)。
    • reversed() 和 sorted() 同样表示对列表 / 元组进行倒转和排序,reversed() 返回一个倒转后的迭代器;sorted() 返回排好序的新列表。(可以对元组用)

列表和元组存储方式的差异

  • 列表会动态扩容
l = []
print(l.__sizeof__())  # 40
# 空列表的存储空间为40字节
l.append(1)
print(l.__sizeof__())  # 72
# int类型占用了8字节
# 加入了元素1之后,列表为其分配了可以存储4个元素的空间 (72 - 40)/8 = 4
l.append(2)
print(l.__sizeof__())  # 72
# 由于之前分配了空间,所以加入元素2,列表空间不变
l.append(3)# 72
l.append(4)# 72
l.append(5)
print(l.__sizeof__())  # 104 
# 加入元素5之后,列表的空间不足,所以又额外分配了可以存储4个元素的空间,72+32=104
  • 元组申请固定空间,每个int元素都固定占用8空间
tuple1 = ()
print(tuple1.__sizeof__())  # 24
# 空元组占用24单位空间
tuple2 = (1)
# 等价于tuple2 = 1,不是元组,而是个变量,所以这个是例外
print(tuple2.__sizeof__())  # 28
tuple3 = (1, 2)
print(tuple3.__sizeof__())  # 40,24+8*2=40
tuple4 = (1, 2, 3)
print(tuple4.__sizeof__())  # 48
tuple5 = (1, 2, 3, 4)
print(tuple5.__sizeof__())  # 56
  • 不难发现,空的列表比元组多了16单位空间,为什么?
    • 这是因为列表是动态的,要用8个字节来存储指针,还要用8个字节来表示已分配的长度。

字典(dict)和集合(set)

常规操作

  • 在 Python3.7+,字典被确定为有序。3.5之前是无序的。3.6无法保证100%有序
  • 无论字典还是集合,都可以存放混合类型,比如里面可以同时包含int和string类型
  • 相比于列表和元组,字典和集合的性能更优,特别是对于查找、添加和删除操作
  • 集合不支持索引操作,因为集合本质是一个哈希表
d1 = {'name': 'jason', 'age': 20}#更高效
d = dict({'name': 'jason', 'age': 20, 'gender': 'male'})
print(d1['name'])  # jason
print(d1.get('age'))  # 20
print('name' in d1)  # true
d1['gender'] = 'male'  # 增加元素对'gender': 'male'
s1 = {3, 2, 5, 4, 1}
print(2 in s1)  # true
s1.add(6)
s1.remove(2)
# 不建议使用pop,集合的 pop() 操作是删除集合中最后一个元素,可是集合本身是无序的,你无法知道会删除哪个元素
s1.pop()
print(sorted(s1))  # [1, 3, 4, 5, 6]
  • 字典本身只有键是可迭代的,如果我们要遍历它的值或者是键值对,就需要通过其内置的函数 values() 或者 items()
d = {'name': 'jason', 'dob': '2000-01-01', 'gender': 'male'}
for k in d: # 遍历字典的键
    print(k)
for v in d.values(): # 遍历字典的值
    print(v)
for k, v in d.items(): # 遍历字典的键值对
    print('key: {}, value: {}'.format(k, v))

数据结构与原理

  • 不同于其他数据结构,字典和集合的内部结构都是一张哈希表。对于字典而言,这张表存储了哈希值(hash)、键和值这 3 个元素。
  • 对集合来说,区别就是哈希表内没有键和值的配对,只有单一的元素了。
  • 老版本 Python 的哈希表结构如下所示,随着hash表的扩张,会变得越来越稀疏
entries = [
['--', '--', '--']
[-230273521, 'dob', '1999-01-01'],
['--', '--', '--'],
['--', '--', '--'],
[1231236123, 'name', 'mike'],
['--', '--', '--'],
[9371539127, 'gender', 'male']
]
  • 新版本Python的哈希表结构,把索引和哈希值、键、值单独分开
indices = [None, 1, None, None, 0, None, 2]
entries = [
[1231236123, 'name', 'mike'],
[-230273521, 'dob', '1999-01-01'],
[9371539127, 'gender', 'male']
]

插入操作

entries[indices[index]]=hashcode-key-value,index是 indices数组的下标索引,对应存储的值是连续的整数,也就是entries的索引。 因为hash code计算出来的很可能是间隔很大的数,比如有两个数,一个计算出来的索引是1,一个计算出来的索引是100万,那么你申请的数组需要100万位。这样造成大量的内存浪费,PyDicMinSize 就是确定一个固定的长度,这样申请资源只申请这么大

每次向字典或集合插入一个元素时,Python 会首先计算键的哈希值(hash(key)),再和 mask = PyDicMinSize - 1 做与操作,计算这个元素应该插入哈希表的位置 index = hash(key) & mask。

  • 如果哈希表中此位置是空的,那么这个元素就会被插入其中。
  • 如果此位置已被占用,Python 便会比较两个元素的哈希值和键是否相等。
    • 若两者都相等,则表明这个元素已经存在,如果值不同,则更新值。
    • 若两个元素的键不相等,但是哈希值相等,这种情况我们通常称为哈希冲突(hash collision),Python 便会继续寻找表中空余的位置,直到找到位置为止。(双重探测法,无需深入了解)

查找操作

Python 会根据哈希值,找到其应该处于的位置;然后,比较哈希表这个位置中元素的哈希值和键,与需要查找的元素是否相等

删除操作

  • Python 会暂时对这个位置的元素,赋于一个特殊的值,等到重新调整哈希表的大小时,再将其删除。
  • 哈希表通常保证其至少留有 1/3 的剩余空间。随着元素的不停插入,当剩余空间小于 1/3 时,Python 会重新获取更大的内存空间,扩充哈希表。不过,这种情况下,表内所有的元素位置都会被重新排放。

输入与输出

控制台输入

  • input() 函数暂停程序运行,同时等待键盘输入;直到回车被按下,函数的参数即为提示语,输入的类型永远是字符串型(str)
a = input('a:')  # 1
b = input('b:')  # 2
print(a + b)  # 12,因为是字符串相加

文本文件读写

  • open() 函数对应于 close() 函数,也就是说,如果你打开了文件,在完成读取任务后,就应该立刻关掉它
  • 建议使用with语句,就不需要显式调用 close()。在 with 的语境下任务执行完毕后,close() 函数会被自动调用
  • 大文本读取建议限制size
# 第一个参数指定文件位置(相对位置或者绝对位置);
# 第二个参数,如果是 'r' 表示读取,如果是'w' 则表示写入,
# 'rw' ,表示读写都要。
# a 表示追加(append),这样打开的文件,如果需要写入,会从原始文件的最末尾开始写入。
with open('in.txt', 'r') as fin:
    all = fin.read()
    print(all)
with open('in.txt', 'r') as fin:
    text = fin.readline()
    while text:
        print(text, end='')  # 使用end=''参数避免在输出中添加额外的换行符
        text = fin.readline()
with open('in.txt', 'w') as fout:
    fout.write('hello world')

条件与循环

条件与循环并做一行的复用写法:

x = [1, -2, 3]
y = [value * 2 + 5 if value > 0 else -value * 2 + 5 for value in x]
# 等价于
for value in x:
    if value > 0:
        y.append(value * 2 + 5)
    else:
        y.append(-value * 2 + 5)
print(y)

异常处理

  • except block 只接受与它相匹配的异常类型并执行,如果程序抛出的异常并不匹配,那么程序照样会终止并退出
try:
    s = input('输入两个数字,用英文逗号隔开:')
    num1 = int(s.split(',')[0].strip())
    num2 = int(s.split(',')[1].strip())
except ValueError as err:
    print('Value Error: {}'.format(err))
print('continue')
#这里只捕获了ValueError,如果输入1,就会出现IndexError,程序会终止运行
  • 在最后一个 except block,声明其处理的异常类型是 Exception,这样就能覆盖所有异常类型
try:
    s = input('输入两个数字,用英文逗号隔开:')
    num1 = int(s.split(',')[0].strip())
    num2 = int(s.split(',')[1].strip())
except ValueError as err:
    print('Value Error: {}'.format(err))
except IndexError as err:
    print('Index Error: {}'.format(err))
except Exception as err:
    print('Other error: {}'.format(err))
finally:
    print('finally')
print('continue')
  • 当程序中存在多个 except block 时,最多只有一个 except block 会被执行,且是最前面的被执行

函数

函数嵌套

不太常用,一般有两个目的:

  • 保证内部函数的隐私
  • 提高程序的运行效率
def factorial(input):
    # 对输入进行校验,但是放在内嵌函数外,这样的话递归的时候不用反复校验
    if not isinstance(input, int):
        raise Exception('input must be an integer.')
    if input < 0:
        raise Exception('input must be greater or equal to 0')
    def inner_factorial(input):
        if input <= 1:
            return 1
        return input * inner_factorial(input - 1)
    return inner_factorial(input)
print(factorial(5))

函数的变量作用域

  • 全局变量可以在函数内部正常访问,但是不能随意改变全局变量的值,需要提前加上 global 这个声明
MIN_VALUE = 1
MAX_VALUE = 10
def validation_check():
    global MIN_VALUE  # 不声明的话,会报错
    MIN_VALUE += 1
    
validation_check()
print(MIN_VALUE)  # 2
  • 对于嵌套函数,内部函数可以访问外部函数定义的变量,但是无法修改,若要修改,必须加上 nonlocal 这个关键字
def outer():
    x = "local"
    print(x)
    def inner():
        nonlocal x  # nonlocal关键字表示这里的x就是外部函数outer定义的变量x
        x = 'nonlocal'
        print(x)
    inner()
outer()

闭包

  • 闭包类似于嵌套函数,不同的是,这里外部函数返回的是一个函数,而不是一个具体的值
  • 返回的函数通常赋于一个变量,这个变量可以在后面被继续执行调用。
def nth_power(exponent):
    def exponent_of(base):
        return base ** exponent
    return exponent_of  # 返回值是exponent_of函数
square = nth_power(2)  # 计算一个数的平方,可以理解square是exponent_of(base)的一个函数
print(square(2))  # 计算2的平方
print(square(3))  # 计算3的平方
print(square(4))  # 计算4的平方
  • 上面函数咋一看很傻逼,完全可以改成下面这种:
def nth_power(base, exponent):
    return base ** exponent
print(nth_power(2, 2))  # 计算2的平方
print(nth_power(3, 2))  # 计算3的平方
print(nth_power(4, 2))  # 计算4的平方
  • 比较下闭包和常规函数,不难发现,闭包每次调用函数可以 少输入一个参数,表达更为简洁,如果调用特别多的时候,不妨尝试使用闭包

匿名函数

  • lambda表达式的主体只能是一行的表达式,不能拓展成多行的代码块
print([(lambda x: x * x)(x) for x in range(10)])
# 输出[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
l = [(1, 20), (3, 0), (9, 10), (2, -1)]
l.sort(key=lambda x: x[1]) # 按列表中元组的第二个元素排序
print(l)
# 输出[(2, -1), (3, 0), (9, 10), (1, 20)]
  • lambda表达式最常用的有这三个函数map(),fiilter() 和 reduce()
    • 函数 map(function, iterable) 的第一个参数是函数对象,第二个参数是一个可以遍历的集合,它表示对 iterable 的每一个元素,都运用 function 这个函数
    • filter(function, iterable) 函数,function 同样表示一个函数对象。filter() 函数表示对 iterable 中的每个元素,都使用 function 判断,并返回 True 或者 False,最后将返回 True 的元素组成一个新的可遍历的集合。
    • reduce(function, iterable) 函数比较复杂,表示对 iterable 中的每个元素以及上一次调用后的结果,运用 function 进行计算,所以最后返回的是一个单独的数值
from functools import reduce
l1 = [1, 2, 3, 4, 5]
# map(function, iterable)
new_list1 = map(lambda x: x * 2, l1)
for i in new_list1:
    print(i)
# [2, 4, 6, 8, 10]
l2 = [1, 2, 3, 4, 5]
new_list2 = filter(lambda x: x % 2 == 0, l2)
for i in new_list2:
    print(i)
# [2,4]
l3 = [1, 2, 3, 4, 5]
result = reduce(lambda x, y: x * y, l3)  # 5*(4*(3*(2*1))) = 120
print(result)  # 120

面向对象

基础概念

  • 如果一个属性以 __ (注意,此处有两个 _) 开头,我们就默认这个属性是私有属性
  • 对象内部的一些重要函数概念:
    • 类函数,需要也必须传入cls,通常用于实现不同的init
    • 静态函数,就是一种普通函数,只是在对象内
    • 成员函数,常见的就是set 和 get,不需要装饰器声明,可以查询/修改类的属性
class Student:
    # 用全大写定义一些静态常量
    COMPANY = 'zingfront'
    def __init__(self, name, age):
        print('init function called')
        self.name = name
        self.__age = age
    # 类函数,需要也必须传入cls,通常用于实现不同的init
    @classmethod
    def create_empty_student(cls, name):
        return cls(name, age=18)
    # 静态函数,就是一种普通函数,只是在对象内
    @staticmethod
    def get_company(context):
        return Student.COMPANY+context
    # 成员函数,常见的就是set 和 get
    def get_age(self):
        print(self.__age)
    def get_name(self):
        print(self.name)
stu = Student('tangyong', 26)
stu.get_age()  # 私有属性无法再函数外面打印出来的
stu1 = Student.create_empty_student('wangjun')
stu1.get_age()  # 默认18

继承

  • 子类必须在 init() 函数中显式调用父类的构造函数。它们的执行顺序是 子类的构造函数 -> 父类的构造函数
class Animal:
    def __init__(self, atype):
        self.atype = atype
    def print_atype(self):
        print(self.atype)
class Dog(Animal):
    def __init__(self, name, size):
        Animal.__init__(self, 'dog')
        self.name = name
        self.size = size
    def print_all(self):
        print(self.name, self.size, self.atype)
class Cat(Animal):
    def __init__(self, name, age):
        Animal.__init__(self, 'cat')
        self.name = name
        self.age = age
    def print_all(self):
        print(self.name, self.age, self.atype)
dog1 = Dog('tom', 20)
cat1 = Cat('jimmy', 30)
dog1.print_all()
cat1.print_all()
dog1.print_atype()  # 可以调用父类的函数

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