嘘~ 正在从服务器偷取页面 . . .

Python复习



Python 复习

func(1, 2, '3', '4', client="John Cleese")
# 上面调用中,1,2,3,4都会传递给args
# client会传递给kwargs

运算符优先级

运算符说明 Python 运算符 优先级 结合性
小括号 ( ) 19
索引运算符 x[i] 或 x[i1: i2 [:i3]] 18
属性访问 x.attribute 17
乘方 ** 16
按位取反 ~ 15
符号运算符 +(正号)、-(负号) 14
乘除 *、/、//、% 13
加减 +、- 12
位移 >>、<< 11
按位与 & 10
按位异或 ^ 9
按位或 | 8
比较运算符 ==、!=、>、>=、<、<= 7
is 运算符 is、is not 6
in 运算符 in、not in 5
逻辑非 not 4
逻辑与 and 3
逻辑或 or 2
逗号运算符 exp1, exp2 1
  • 所谓结合性,就是当一个表达式中出现多个优先级相同的运算符时,先执行哪个运算符:先执行左边的叫左结合性,先执行右边的叫右结合性。

类的常用内置属性:

  • __dict__:类的属性字典
  • __bases__:类的所有父类构成的元组
  • __doc__:类的文档字符串(类的介绍)
  • __name__:类名
  • __module__:类定义所在的模块

类的常用内置方法:

  • __str__:如果定义,那么在 print 输出这个类的时候会输出这个方法的返回值。

  • __repr__:如果__str__未定义,那么 print 输出的时候,会再找 repr

    • repr 面向的是开发人员,在控制台交互模式下,直接输出类对象就会输出 repr 的返回值
  • __call__:当一个类实例化出一个对象,默认情况下是不能直接加括号进行调用的

    • 需要实现__call__函数,当实例化的对象以函数形式调用就会调用 call
  • __setitem__:当实例通过.属性,或者[]设置值或者对象的时候会调用这个方法。

    • 只有在这个方法内进行赋值,才会真正的复制到 dict 中
    • 但函数默认是不支持[]列表形式赋值的,需要自己实现
  • __getitem__:当实例获取属性的时候会调用这个方法

    • 当被迭代或 in 的时候,也会返回这里返回的内容
    • 也就是可以被迭代,需要终止条件,要不然就会死循环
    • 主动定义返回的内容,结束主动抛出异常即停止
  • __delitem__:当实例删除属性的时候会调用这个方法

  • 类实例的切片操作:同样是会在__getitem__,__delitem__,__setitem__内部被拦截,

    • 在 getitem 的 key 中传入的就是切片范围以及步长
  • __bool__:这个方法返回的内容,会被比较操作的时候作为参数(比如 if 比较操作)

  • __iter__:被迭代的时候优先调用 iter,iter 没有定义才会去找 getitem。

    • 返回一个迭代器即可
    • 比如:return iter([1,2,3,4,5])
    • 每次被迭代的时候,会先调用 iter 获取迭代器
    • 如果类本身已经定义__next__:那么简单的返回 self 即可。
  • __next__:当对象被迭代或者用 next()调用的时候,自动调用这个方法

    • 需要终止条件,以抛出异常为终止
    • 当 iter 获取到迭代器的时候,每次迭代调用这个方法
  • 迭代器:需要同时实现 next 和 iter 方法,或者用 getitem 配合 iter

  • __getattribute__:描述器相关操作

    • 当属性实现了描述器的__get__方法,那么直接调用
    • 如果没有实现,则按内部的查找顺序进行查找
  • __enter__:上下文进入后主动调用

  • __exit__:上下文结束后主动调用

类实例大于,小于,不等于,等操作的实现

  • __eq__:相等 and 不相等,接受的参数:other 代表另外一个参数
  • _ne__:只实现 eq 就会自动实现相反的操作,但是 ne 代表的就是!=
  • __gt__:大于
  • __ge__:大于等于
  • __lt__:小于
  • __le__:小于等于
  • 如果在定义的时候只定义了大于,但是调用的时候调用了小于,那么解释器会自动调换参数,来调用大于

元类:

  • 比如 int,str 的类都继承自 type
  • 除了用 class 关键字,也可以用 type 进行类的实例化
  • 查找机制:
    • __metaclass__:指定实例化的元类
    • 先查找本身是否有指定实例化的元类
    • 没有后再查找父类是否有指定
    • 最后查找模块是否有指定
    • 如果都没有,那就以 Python 内部定义的元类为准

私有属性:(Python 的私有都是假私有)

  • _a:单下划线,属性只能在模块内部访问,外部导入需要用__all__方法进行声明
  • __a:双下划线,属性只能在类内部访问,外部可以通过某种手段访问,但不推荐

私有属性的设置和获取

  • 虽然可以定义一个方法进行获取以双斜杠开头的属性,但是每次都要调用一个函数来获取就增加麻烦
  • 可以使用装饰器,将函数指定为一个属性
  • @property:将一个属性删,改,查关联到一个函数内
class T:
    def __init__(self):
        self.__age = 123

    # property直接将函数改为属性,无须执行即可获取结果
    # 获取属性,每个的函数名字必须都一样
    @property
    def age(self):
        return self.__age

    # 设置属性
    @age.setter
    def age(self, value):
        self.__age = value

    # 删除属性
    @age.deleter
    def age(self):
        del self.__age

    # 下面为第二种写法
    # 实现三个操作函数,当外界以属性的方式调用的时候,自动调用相应的函数
    age = property(age_get,age_set,age_del,'第四个参数为描述器的描述')


a = T()
a.age = 2313
print(a.age)

私有属性设置方法二:

# 当通过.属性的方式或者[]进行赋值以及取值操作的时候,都会走这个方法,
# 但函数默认是不支持[]列表形式赋值的,需要自己实现
# 在这个属性内部,才会真正的修改数据到__dict__
def __setattr__(self, key, value):
    # 需要限定的话可以进行如下操作
    if key == 'age' and key in self.__dict__.keys():
        # 如果判断是age,并且存在于keys中,不进行操作
        pass
    else:
        # 不是进行赋值,这里不能以.属性进行赋值,否则会死循环
        self.__dict__[key] = value

私有方法:

  • 私有方法的规则和私有属性的规则相同

描述器:将类定义成一个属性:

  • 描述一个属性操作的对象
  • 描述器的定义方式之一就是用装饰器:@property
    • 但这种方式赋值的私有属性还是可以被修改。
    • 所以就引入了第二种方式
class Age:
    # 定义设置,获取,删除操作
    def __set__(self, instance, value):
        print(instance, value)
        print('set')

    def __get__(self, instance, value):
        print('get')

    def __delete__(self, instance, value):
        print('delete')


class T:
    # 实例化描述器
    age = Age()

    def __init__(self):

        # 这里操作表示的是操作描述器,而不是定义一个新的属性
        self.age = 2

# 以.属性的方式,直接调用
t = T()

t.age = 1


"""
__getattribute__:描述器相关操作

+ 当属性实现了描述器的__get__方法,那么直接调用
+ 如果没有实现,则按内部的查找顺序进行查找

"""

装饰器:

正常实现装饰器:

import functools


def pr(func):
    # 保留元信息
    @functools.wraps(func)
    def inner():
        print('装饰器内部')
        func()

    return inner

@pr
def func():
    print('函数内输出')

# 执行被装饰器装饰的函数
func()

# 不带参数的可以理解为
func = pr(func)
func()

带参数的普通装饰器:

# 带参数的普通装饰器
import functools


def ar(arg):
    def pr(func):
        # 保留元信息
        @functools.wraps(func)
        def inner():
            print('装饰器内部')
            func()
            print('传入的参数是:', arg)

        return inner

    return pr


# 装饰器
@ar('123')
def func():
    print('函数内输出')


# 执行被装饰器装饰的函数
func()

# 带参数的函数执行流程
# 先执行函数,传入参数
f = ar('123')
# 这时候上面的ar会返回一个函数,再将被装饰的函数传入
f(func)  # 这里还是会返回一个函数
# 然后最终返回一个新的函数,当调用原始的函数的时候这个函数就会被执行
f()

将类当作装饰器:

class Pr:
    # 实例化的时候,会将装饰器下面的函数输出到init方法
    def __init__(self, func):
        self.func = func

    # 并且会自动执行实例,也就是自动调用这个方法
    def __call__(self, *args, **kwargs):
        print('类装饰器内部执行')
        self.func()

# 将类当作装饰器
@Pr
def func():
    print('函数内输出')

# 当类被当作装饰器的时候执行和普通的装饰器一样
# 初始化,然后调用
# 初始化写init方法,调用写call方法
# 所以实现一下即可

将类当作装饰器 并且支持参数

class Pr:
    def __init__(self, arg):
        self.arg = arg

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print('装饰器参数:', self.arg)
            func(*args, **kwargs)
            print('函数执行完毕')

        return wrapper

@Pr(777)
def func1():
    print('函数内输出')

内存管理机制

  • Python 是万物皆对象
  • 使用引用来进行后续处理(根据类型的不同,开辟不同的空间进行存储)
  • 可以使用 id 获取十进制内存地址,hex 获取十六进制内存地址
  • Python 对小整形,以及部分短浮点型会创建一个对象的引用,不会创建多个相同的对象。
  • 内存泄露:a 对象引用 b 对象,b 对象引用 a 对象。并且没有任何其他对象指向这两个对象可以供进行操作。
    • 就导致了互相存在内存中无法被释放

上下文管理器

class T:
    def __init__(self):
        self.t = 666

    def __enter__(self):
        print('上下文管理器开始!')
        # 这里直接返回类的本身,with后面就是类实例
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('上下文管理器开始!')


    async def __aenter__(self):
        print('异步上下文管理器开始!')
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        print('异步上下文管理器结束!')


# with后面的as就是在enter中返回的对象
with T() as t:
    print(t.t)

async with T() as t:
    print(t.t)

# 当上下文没有调用之后,自动调用exit方法

三大特效

封装:

  • 将一写属性和方法相关的方法封装在一个对象中,对外隐藏内部具体实现细节

继承

  • 一个类“拥有”另外一个类的“资源”的方式之一
    • 拥有:并不是复制资源变成两份,而是资源的使用权
    • 资源:指的是非私有的属性和方法
  • 继承分为单继承和多继承。
  • 从创建的角度来说,任何数据类型都由 type 创建(包括 object)
  • 从继承的角度来说,任何类都继承自 object(包括 type)
  • 以双下划线开头的属性无法被子类继承(或许是可以继承,但访问需要特殊方式)
  • 如果 A 里面有 age=10,B 继承自 A,那么在 B 只有 A 里面 age 的使用权限。如果使用 B.age=9
    • 属于是给 B 定义了一个新的 age=9,并不会修改 A 内的 age

MRO 继承链:

单继承:最简单的继承,依次向上查找即可(从下往上即可)

无重叠的多继承:多继承,但每个类都是依次继承不同的类

  • d(a,b,c):前面的多继承链,在查找时会先找 a,再找 b,再找 c。
  • 并且会一直找到继承链的顶端之后,才会接下去找

有重叠的多继承:多继承,继承多个类,并且这多个类还继承自同一个类

  • d(a,b),并且 a 和 b 同时继承自 c:查找流程就是先查找 a,再找 b。最后才会去查找到共同继承的 c

多继承的资源访问顺序:

  • 当一个类继承了多个类之后,比如每个类都有相同的 age 属性,那么按照查找顺序返回 age。
  • 并不是说,优先级高的类,把优先级低的类给覆盖了,而是查找顺序优先返回了。
  • 方法的也是相同的,就相当于优先级高的重写优先级低的

  • cls 和 self 问题:在继承的关系中,self 和 cls 代表调用者本身,不会因为向上查找就变为父类。
  • 因为查找顺序的原因初始化方法都是会默认调用 init 方法,如果子类重写了 init 方法那么系统就不会自动去调用父类的 init 方法,导致父类的 init 内部值无法被初始化。所以当父类的初始化方法中有需要初始化的值,并且子类也要重写 init 方法的时候,需要用 super 来调用父类的初始化方法
  • super:起着代理的作用,帮助我们沿着 MRO 链条,找到下一级的节点,调用对应方法
    • 参数一:查找这个参数的下一个节点
    • 参数二:沿着哪个对象的 MRO 链条往下找
    • 使用的是第二个参数进行(self,cls)调用
class A:
  def __init__(self):
    self.a = 'a'
    print(self.a)
    @classmethod
    def get_a(cls):
      print('a')

class B(A):
    def __init__(self):
        # 沿着B的查找下一个节点,因为B继承自A,所以找到的就是A
        # 沿着self(实例)的MRO链条往下找,所以找到了A
        # 因为上面的是沿着B往下找,所以这里的MRO链条,需要能找到B
        # 因为self是B的实例,所以self往上找就可以找到B。
        # 最终调用了A的init方法
        super(B, self).__init__()
        self.b = 'b'
        print(self.b)

    @classmethod
    def get_a(cls):
    # 沿着B的查找下一个节点,因为B继承自A,所以找到的就是A
    # 使用的是第二个参数进行(self,cls)调用
    # 所以传入到A中的get_a方法的cls是(cls)B
        super(B, cls).get_a()
        print('b')
        b = B()
        b.get_a()
    # 正常情况下推荐以下写法,系统会自动传入当前的类对象
    # 并且将方法的第一个参数作为参数传入
class B(A):
    def __init__(self):
      super().__init__()

多态:

  • 一个类延伸的多种形态,调用时的多种形态
  • 同一个方法,不同的对象调用,产生不同的形态
  • 但是在 Python 中没有真正意义上的多态,也不需要多态。

抽象:

  • 抽象类:并不是某一个具化的类。不能直接创建抽象类的实例——会报错
  • 抽象方法:不具备具体实现,不能直接调用,子类必须要实现这个抽象方法,否则报错。
  • 比如动物:
    • 动物是一个抽象类,是一个统称,并不存在动物这个类型。
    • 但狮子,老虎都是属于动物。
  • Python 中并不能直接实现抽象类,需要使用 abc 模块。

与或非复习

if __name__ == '__main__':
  set1 = {1, 2, 3, 4}
  set2 = {4, 5, 6, 7}
  # and 运算符是左边和右边都要成立,所以肯定会返回右边的内容
  # 先检测左边,再检测右边。最终返回右边内容。
  print('我是and的结果:', set1 and set2)
  # 我是and的结果: {4, 5, 6, 7}
  # or 就是左边和右边要有一个成立,如果左边成立就不会检测右边了。
  # 所以左边set1有值,直接返回左边的内容。
  print('我是or的结果', set1 or set2)
  # 我是or的结果 {1, 2, 3, 4}
  # 与(交集) 运算符 输出set1 与 set2中 都有的元素
  # 内存比较是:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0
  # 也就是说上面和下面比,最终只有4是两个都为1,所以最终只有返回一个4.
  print('与 运算结果:', set1 & set2)
  # 与 运算结果: {4}
  # 或(并集) 运算符:两个位中,只要有一个位置为1,那么这个结果就为一
  # 所以,两个集合的内容都会输出,重复的只会输出一次
  print('或 运算结果:', set1 | set2)
  # 或 运算结果: {1, 2, 3, 4, 5, 6, 7}
  # 异或(补集) 运算符:两个集合不是共同拥有的内容
  # 内存比较:set1 和 set2 中不一样的位置才为1
  print('异或 运算符:', set1 ^ set2)
  # 异或 运算符: {1, 2, 3, 5, 6, 7}
  # 差集 简单理解就是输出前面那个独有的
  # 比如下面就是set1中独有的内容
  print('差集:set1中独有的:',set1 - set2)
  # 差集:set1中独有的: {1, 2, 3}

参数注解

类型注解并不代表强制要求了函数的传参,比如一个函数指定了传入的类型是 str,但你还是可以传入一个列表,这注解是给程序员看的

并且有一个好处就是,可以让编译器知道是什么类型,就会有相应的代码提示:

那些 int,str,List 就不用写了:写稍微复杂一丢丢的

  1. 列表内的每个元素都是字符串:arg:List[str]
  2. 字典 key 是字符串,value 是数字类型:Dict[str,int]
  3. 函数的返回值是列表,列表内是小数:def func(arg: Dict[str,int]) -> List[float]:
  4. 函数参数可以是字典,也可以是元组,返回是集合:def func(arg: Union[dict, Tuple]) -> Set:
    1. 空元组可以写为:Tuple[()]
    2. Union 联合类型必须是其中的一个。
    3. Union[int]:代表的就是 int 本身
      • Union[int,str] 代表可以是 int 可以是 str
  5. 还有:
    1. Iterable:可迭代类型
    2. Iterator:迭代器类型
    3. Generator:生成器类型
  6. arg 是一个可以被调用的类型:def func(arg: Callable):
  7. arg 是一个可以被调用的对象,并且调用需要传入一个列表,和一个浮点数:def func(arg: Callable[list, float]):
  8. 可选类型:def func(arg: Optional[int] = None) -> None::arg 可以是 int,或者 None
  9. 自定义类型:
class MyType:
  def __init__(self):
    self.__age = 18
    def get_age(self):
      return self.__age
      # 函数li代表 用户定义的类 并且返回值也是用户定义的类
      # 在内部调用也会出现代码提示。
    def func(li: MyType) -> MyType:
      li.get_age()
      return li

官方文档笔记

  1. 字典直接 in 是得到的 key,需要解包键值对可以用:dict.items():同时取出对应的键和值。
  2. 复制一个对象可以用 obj.copy()
  3. enumerate:同时取出索引,和对应的值。返回一个的是一个迭代器;
users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}
print(list(enumerate(users)))
# [(0, 'Hans'), (1, 'Éléonore'), (2, '景太郎')]
  1. 浏览器备注所写的注释,可以用 func__doc__:取出来
def func(*args, **kwargs):
  """    文档字符串位置。。。。    """
  print(args, kwargs)print(func.__doc__)

列表:

使用列表方法实现堆栈非常容易,最后插入的最先取出(“后进先出”)。

然而,列表作为队列的效率很低。因为,在列表末尾添加和删除元素非常快,但在列表开头插入或移除元素却很慢(因为所有其他元素都必须移动一位)。

使用队列可以用dequehttps://docs.python.org/zh-cn/3/library/collections.html#collections.deque

列表转置

matrix = [    [1, 2, 3, 4],    [5, 6, 7, 8],    [9, 10, 11, 12],]
print([[row[i] for row in matrix] for i in range(len(matrix) + 1)])
# [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

*号解包:

print(*[[1, 2], [5, 6], ])
# [1, 2] [5, 6]print(*[1, 2])  # 1 2

直接按行读取

with open('1.txt', 'r') as fp:
  for line in fp:
    print(line, end='')
    # list(f) 或 f.readlines() 也可以进行相同的操作

JSON 的本地写入与读取

with open('1.txt', 'r+', encoding='utf-8') as fp:
  # 将x写入到本地文件中
  x = [1, 'simple', 'list']
  json.dump(x, fp)
  # 将文件中的json数据读取到b中
  b = json.load(fp)
  print(b)

生成器表达式:(使用括号的是生成器表达式,中括号的是列表推导式)

# 下面函数相当于上面生成器的 reverse 函数,但是更加简洁,相对不灵活一些。
# 但是相对于列表推导式更加节省内存,因为他并不是一次性生成所有内容。
reverse = (s for s in 'global')
for s in reverse:
  print(s)

虚拟环境:

# 创建 python3 -m venv tutorial-env
# 在windows上运行 tutorial-env\Scripts\activate.bat
# 在unix上运行 source tutorial-env/bin/activate

Python 语法糖记录

if else 简写:

a = 1
b = 2
# 这里通过列表的索引方式写出一个简洁版的if else
# 当后面表达式为真,True》代表的就是
# 1:取到的就是b的值# 当后面表达式为假,False》代表的是0,取到的就是a的值
# 相当于直接用0和1进行索引,来达到if else的简写
# 复杂写法:b if a>b else a
# 但下面这种写法不好理解。装逼之用c = [a, b][a > b]

海象运算符:海象运算符本质上就是一个“赋值表达式”,一边完成赋值工作,一边返回刚刚赋值完的变量。

# 因为上面的解释,所以下面的比较是#
# 先将string的长度给n,然后用n和5进行比较
# 因为海象运算符的优先级比 比较运算符低,所以需要加上括号
# 否则n的值将变为比较的bool值
if (n := len('string')) > 5:
    print(n)
    # 循环读取文件并且复制,然后判断是否为空
while (block := f.read(256)) != '':
  process(block)

迭代解包:前面和后面获取一个值,中间对象获取任意长度的值

(a, *rest, b) = range(6)# (0, 1, 2, 3, 4, 5)

yield from:

# [1, 2, 3, 4]# 正常的yield语法使用需要使用循环,yield出去每一个元素的。
for a in x:
   yield a
  # 新语法可以用 yield from,
  # 去除循环体,语法更加简洁。
  # [1, 2, 3, 4]
  # yield from x

3.9 字典更新

# 3.9语法
x = {"key1": "value1 from x", "key2": "value2 from x"}
y = {"key2": "value2 from y", "key3": "value3 from y"}

# 以x为准,将y的数据覆盖到x上。
print(x | y)    # {'key1': 'value1 from x', 'key2': 'value2 from y', 'key3': 'value3 from y'}

# 也可以通过解包语句,以后面那个为准。
print({**x,**y})
#{'key1': 'value1 from x', 'key2': 'value2 from y', 'key3': 'value3 from y'}

3.10 语法:

# 3.10语法
with (
    open('1.txt') as fp1,
    open('2.txt') as fp2,
    open('3.txt') as fp3,
):
# 多条上下文括号连写

单例类:

当一个类型你希望全局只有一个实例的时候,可以使用单例类

  • 通过 hasattr 判断类是否有指定属性,这里使用的是 _instance 属性
  • 如果没有这个属性,说明类没有被实例化,通过 object 进行实例化(所有类默认继承自 object)
  • 定义 _instance 属性,然后返回,下次判断不成立,直接返回属性
  • 在异步操作的时候可能会出错,这里加个锁,虽然会影响效率,但保证了数据的安全
import threading


class Singleton:
    _Lock = threading.Lock()

    def __new__(cls, *args, **kwargs):
        if not hasattr(Singleton, '_instance'):
            with Singleton._Lock:
                Singleton._instance = object.__new__(cls)
        return Singleton._instance

文章作者: 林木木
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 林木木 !
评论
  目录