写出地道的python代码--高级用法大全

文章目录

  • python中的魔法函数
    • 字符串
    • 集合模拟
    • 迭代枚举
    • 可调用模拟
    • 上下文管理
    • 实例创建销毁
    • 属性管理
    • 属性描述符
    • 运算符
  • 数据结构
    • 列表
      • 列表推导式
      • 列表推导的笛卡尔集
    • 元组
      • 元组拆包
      • 具名元组
    • 切片
      • 普通切片
      • 隔值切片
      • 切片赋值
    • 使用bitsect来管理已经排序的序列
    • 双向队列
    • 字典
      • 字典推导
      • 有序字典
    • 集合
      • 集合初始化
      • 集合的运算
        • 交集
        • 并集
        • 差集
      • 集合推导式
    • 字节序列
      • 编码解码
      • 创建一个bytes数据类型
      • 修改bytes
      • 强大的struct包
  • 函数
    • 高阶函数
      • map函数
      • filter函数
      • reduce函数
      • partial函数
    • 匿名函数
    • 函的数参数
      • 函数参数分类
      • 参数信息获取
    • 函数注解
    • 函数装饰器和闭包
      • 闭包
      • 装饰器
      • 带参数的装饰器
      • 标准库中常用装饰器
        • lru_cache
        • singledispatch
  • 对象
    • 对象引用可变性和垃圾回收
      • 标识,值和别名
      • ==和is
      • 可变对象与不可变对象
      • 值传递与引用传递?
      • 深拷贝与浅拷贝
      • del与垃圾回收
      • 弱引用
    • 符合python风格的对象
      • classmethod和staticmethod
      • \__hash\__和\__eq\__
      • python的私有属性
      • \__slots\__
    • 抽象基类
  • 流程控制
    • 迭代器和生成器
      • 可迭代对象,迭代器
      • 迭代器的应用
      • 生成器
      • yield from
    • 上下文管理和else块
      • if 语句之外的else块
      • 上下文管理器和with块

python中的魔法函数

python中万物皆对象,每个对象都有自己的魔法方法(特殊方法)

字符串

_repr_、_str_、_format_、_byte_

  • _str_: 调用str()方法时,就是调用对象内的_str_()方法

例子:

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

    def __str__(self):
        return "该人姓名:%s,年龄:%s" % (self.name, self.age)


person = Person("小明", 18)
print(str(person))

最终结果:
该人姓名:小明,年龄:18

集合模拟

_len_、_getitem_、_setitem_、_delitem_、_contains_

  • _len_: 调用len()方法时,就是调用对象内的_len_()方法

  • _getitem_: 调用object[item]方法时,就是调用对象内的_getitem_()方法,字典专用

  • _setitem_: 调用object[item]=xxx方法时,就是调用对象内的_setitem_()方法,字典专用

例子:

class TestContainer:
    def __init__(self):
        self.my_list = []
        self.my_dict = {}

    def __len__(self):
        return len(self.my_list)

    def append(self, v):
        self.my_list.append(v)

    def __getitem__(self, item):
        return self.my_dict.get(item)

    def __setitem__(self, key, value):
        self.my_dict[key] = value

    def __delitem__(self, key):
        del self.my_dict[key]

    def __contains__(self, item):
        return True if item in self.my_list else False


testContainer = TestContainer()
testContainer.append(1)
print(len(testContainer))
testContainer["name"] = "xiaoming"
print(testContainer["name"])
del testContainer["name"]
print(testContainer["name"])
print(1 in testContainer)

输出结果:
1
xiaoming
None
True

迭代枚举

_iter_、_reversed_、_next_
生成器迭代器那一章具体说明

可调用模拟

_call_
说白了就是对象也能像函数那样调用

例子:

class TestCall:
    def __init__(self):
        self.my_list = []

    def count_list_num(self):
        return len(self.my_list)

    def __call__(self, *args, **kwargs):
        print("调用call方法")
        print(self.count_list_num())


testCall = TestCall()
testCall()

输出:
调用call方法
0

上下文管理

_enter_、_exit_

一般与with关键字一同出现,一般用于文件读写开启关闭,数据库连接开启关闭,具体见例子:

class Sample:
    def __enter__(self):
        print("数据库开启咯")
        return "模拟获取数据库连接"

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("数据库关闭咯")


sample = Sample()
with sample as s:
    print(s)
    print("我要操作数据库咯")

输出:

数据库开启咯
模拟获取数据库连接
我要操作数据库咯
数据库关闭咯

实例创建销毁

_new_、_init_、_del_

  • new: 对象实例化之前调用,new()方法会返回cls(cls指代当前类)的实例,然后该类的__init__()方法作为构造方法会接收这个实例
  • init:对象初始化函数
  • del:垃圾回收对象销毁调用

例子:

class Sample:
    def __new__(cls, *args, **kwargs):
        print("对象创建之前调用new方法")
        instance = super().__new__(cls)
        return instance

    def __init__(self, name):
        self.name = name
        print("对象创建咯")

    def __del__(cls, *args, **kwargs):
        print("对象销毁之后调用del方法")


sample = Sample("xioming")
my_list = []
my_list.append(sample)
del my_list[0]  # 删除引用,垃圾回收

输出:
对象创建之前调用new方法
对象创建咯
对象销毁之后调用del方法

属性管理

_getattr_、_getattribute_、_setattr_、_delattr_、_dir_

  • setattr 一旦该对象中没有这个属性,在设置未知属性,就会调用该方法
  • getattr 一旦该对象中没有这个属性,在查找未知属性,就会调用该方法
  • getattribute 无论是访问存在的,还是不存在的属性 ,都访问到了getattribute这个函数
class Foo(object):
    def __init__(self):
        pass

    def __setattr__(self, key, value):
        print("调用setattr方法,属性为:", key, value)
        super().__setattr__(key, value)

    def __getattr__(self, item):
        print("调用getattr方法,属性为:", item)
        return None


obj = Foo()
obj.x = 123
print(obj.x)
print(obj.w)

输出:
调用setattr方法,属性为: x 123
123
调用getattr方法,属性为: w
None

属性描述符

_get_、_set_、_delete_
描述器具体展开

运算符

太多了,_add__lt_、_eq_、__iadd__等等

定义好这些函数,对象也能加减乘除

例子:

class Foo(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        return Foo(self.x + other.x, self.y + other.y)


obj = Foo(1, 2)
obj2 = Foo(3, 4)
print((obj+obj2).__dict__)

输出:
{‘x’: 4, ‘y’: 6}

数据结构

想写出地道的python代码,就需要尽可能多的使用推导式代替原本的容器创建方式。

列表

列表推导式

一般在创建一个初始列表时都会使用如下方式创建。
例如找出0-100中的奇数部分,并转化为16进制

res = []
for i in range(100):
    if i % 2 == 1:
        res.append(hex(i))
print(res)

但是,有了列表推导,一行搞定,即装逼又清爽

res = [hex(i) for i in range(100) if i % 2 == 1]
print(res)

最终结果相同

列表推导的笛卡尔集

在遇到两层for循环时候也同样可以使用列表推导来进行代码简化与可读性的提升。

abc = ["a", "b", "c"]
nums = ["1", "2"]
obj = [(i, j) for i in abc for j in nums]
print(obj)

输出:
[(‘a’, ‘1’), (‘a’, ‘2’), (‘b’, ‘1’), (‘b’, ‘2’), (‘c’, ‘1’), (‘c’, ‘2’)]

元组

元组与列表的区别就是元组是不可变的列表,元组还有一个作用就是可以用于没有字段名的记录。

元组拆包

例子1:普通拆包

a, b = ("123", "456")
print(a)
print(b)

输出:
123
456

例子2:不确定参数拆包

a, b, *res = ("123", "456", 1, 2, 3, 4, 5, 6)
print(a)
print(b)
print(res)

输出:
123
456
[1, 2, 3, 4, 5, 6]

例子3:函数参数拆包

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


arg = (1, 2)
print(add(*arg))

输出:
3

具名元组

具名元组个人理解就是带顺序的不可变的字典,一下子是具名元组的例子:

from collections import namedtuple

Person = namedtuple("Person", "name age sex")  # 创建一个具名元组,第一个参数是类名字,第二个参数定义元组属性,空格隔开
person1 = Person("xiaoming", "10", "男")  # 创建Person具名元组
print(person1)
print(person1.sex)
print(person1[1])

输出:

Person(name=‘xiaoming’, age=‘10’, sex=‘男’)

10

切片

普通切片

python中元组、列表、字符串都是支持切片的,切片的功能异常强大,一起来看看吧!
所谓切片,也就是分割元组、列表、字符串,生成新的元组、列表、字符串对象。
关于下标,记住python一般都是前闭后开区间的。

l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
print(l[2:4])
print(l[:4])
print(l[2:])
print(l[2:-1])

输出:
[3, 4]
[1, 2, 3, 4]
[3, 4, 5, 6, 7, 8, 9, 0]
[3, 4, 5, 6, 7, 8, 9]

隔值切片

s[a️c]这种模式的切片代表在a,b区间间间隔c取值
例子:

l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
print(l[::2])
print(l[::-1])
print(l[2::2])

输出:

[1, 3, 5, 7, 9]
[0, 9, 8, 7, 6, 5, 4, 3, 2, 1]
[3, 5, 7, 9]

切片赋值

l = [i for i in range(10)]
l[2:4] = [100, 100]
print(l)

输出:
[0, 1, 100, 100, 4, 5, 6, 7, 8, 9]

l = [i for i in range(10)]
del l[2:4]
print(l)

输出:
[0, 1, 4, 5, 6, 7, 8, 9]

使用bitsect来管理已经排序的序列

bitsect模块包涵2个主要函数:bitset和insort,两个函数都利用二分查找方法查找或者插入元素。

import bisect

l = [i for i in range(10)]
a = bisect.bisect_left(l, 2)  # 传入排序数组,数组元素,返回第一次遇见元素的下标
print(a)

输出:
2

import bisect

l = [i for i in range(10)]
bisect.insort(l, 2)  # 传入排序数组,数组元素,自动找到位置并插入
print(l)

输出:
[0, 1, 2, 2, 3, 4, 5, 6, 7, 8, 9]

双向队列

from _collections import deque

queue = deque([1, 2, 3, 4])
queue.append(5)
print(queue)
queue.pop()
print(queue)
queue.appendleft(-1)
print(queue)
queue.popleft()
print(queue)

输出:
deque([1, 2, 3, 4, 5])
deque([1, 2, 3, 4])
deque([-1, 1, 2, 3, 4])
deque([1, 2, 3, 4])

字典

字典推导

raw_dict = [(1, "xiaoming"), (2, "xiaohong"), (3, "xiaozhang")]
my_dict = {k: v for k, v in raw_dict}
print(my_dict)

输出:
{1: ‘xiaoming’, 2: ‘xiaohong’, 3: ‘xiaozhang’}

有序字典

众所周知,字典的底层原理是散列表,最简单的形式就是数组+链表的方式进行存储,key的顺序并不是有序的,collections模块中的OrderedDict模块解决了这个问题,具体看例子:

from collections import OrderedDict

order_dict = OrderedDict()
order_dict[1] = "xiaoming"
order_dict[2] = "xiaohong"
order_dict[3] = "liming"
print(order_dict.popitem())

输出:
(3, ‘liming’)

集合

集合初始化

集合的底层原理是字典,根据字典实现去重。具体看例子:

l = [1, 2, 3, 4, 1, 2, 3]
my_set = set(l)
print(my_set)

输出:
{1, 2, 3, 4}

集合的运算

交集

set1 = {1, 2, 3, 4, 5}
set2 = {1, 2, 3, 6}
print(set1 & set2)

输出:
{1, 2, 3}

并集

set1 = {1, 2, 3, 4, 5}
set2 = {1, 2, 3, 6}
print(set1 | set2)

输出:
{1, 2, 3, 4, 5, 6}

差集

set1 = {1, 2, 3, 4, 5}
set2 = {1, 2, 3, 6}
print(set1 - set2)

输出:
{4, 5}

集合推导式

set1 = {i for i in range(10)}
print(set1)

输出:
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

字节序列

编码解码

什么是编码,什么是解码?

  • 编码(encode):可以理解成把人类读得懂的字符串转化为计算机能读懂的二进制语言叫编码。
  • 解码(decode):可以理解成把计算机能读懂的二进制语言转化成人类可读文本的过程。

总结:encode是将str数据结构类型转为bytes数据结构类型,decode是将bytes数据结构类型转换为str类型

虽然这么解释是存在缺陷的,不过便于理解。

下面是python编解码的一个例子:

my_str = "python牛逼!"
print(len(my_str))
my_encode_str = my_str.encode("utf-8")
print(my_encode_str)
print(len(my_encode_str))
my_decode_str = my_encode_str.decode("utf-8")
print(my_decode_str)

输出:
9
b’python\xe7\x89\x9b\xe9\x80\xbc\xef\xbc\x81’
15
python牛逼!

“python牛逼!”经过utf-8编码后长度变为15byte,也就是这个字符串在计算机中占15byte,为什么呢?中文在utf-8编码中一般占3个字节,牛逼+中文感叹号就占用9byte,python占6byte,9+6=15

创建一个bytes数据类型

  • 可以使用bytes.fromhex()方法,解析16进制数字构建bytes对象
  • 可以使用数组中的原始数据初始化bytes对象

例子:
这边查到python英文对应的ascii码为112,121,116,104,111,110

第一种方法:

bytes_arr = bytes.fromhex('70 79 74 68 6f 6e')
print(bytes_arr)
print(bytes_arr.decode())

输出:
b’python’
python

第二种方法:

import array

a = array.array('h', [112, 121, 116, 104, 111, 110])
bytes_array = bytes(a)
print(bytes_array.decode())

输出:
python

修改bytes

修改bytes就需要将bytes类型转化为bytearray类型。

  • bytes对象是由单字节组成的不可修改序列
  • bytearray对象是由单字节组成的可修改序列
bytes_arr = bytes.fromhex('70 79 74 68 6f 6e')
bytes_arr2=bytearray(bytes_arr)
bytes_arr2.append(114)
print(bytes_arr2)
print(bytes_arr2.decode())
bytes_arr2.pop()
print(bytes_arr2.decode())

输出:
bytearray(b’pythonr’)
pythonr
python

强大的struct包

struct包常用web框架底层,主要用于解析协议。 struct 模块执行Python 值 和以Python bytes 表示的C结构体之间的转换,这可以用于处理存储在文件中或来自网络连接以及其他源的二进制数据;它使用一定格式的字符串作为C语言结构布局的简洁描述以及到或从Python值的预期转换。
下表来自网络:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k5SiNJVZ-1620804359983)(http://km.oa.com/files/photos/pictures/202105/1619956079_70_w790_h1282.png)]

说的比较抽象,下面举个例子:

接受到网络中的bytes数值解析:

import struct

bytes_from_net = bytes.fromhex('70 00 00 00 19 00 00 00')  # 假设这是来自网络接受到的bytes类型的数据,两个int类型数据,占8个字节(来自java或者c程序)
print(bytes_from_net)
i1,i2 = struct.unpack('ii', bytes_from_net)
print(i1,i2)

输出:
b’p\x00\x00\x00\x19\x00\x00\x00’
112 25

发送bytes类型数据到网络中

import struct

res = struct.pack('ii', 112, 25)
print(res)# res即为所求
print(len(res))

输出:
b’p\x00\x00\x00\x19\x00\x00\x00’
8

函数

python中一切都是对象,函数也不例外

高阶函数

接受函数作为参数,或者把函数结果作为参数返回的函数叫高阶函数
下面举例一些python中常见的高阶函数

map函数

map() 会根据提供的函数对指定序列做映射
两个参数

  • function – 函数
  • iterable – 一个或多个序列

例子1:

def square(x):
    return x ** 2


res = map(square, [1, 2, 3, 4, 5])
print(list(res))

输出:
[1, 4, 9, 16, 25]

例子2:

res = map(lambda x, y: x + y, [1, 2, 3, 4, 5], [2, 3, 4, 5, 6])
print(list(res))

输出:
[3, 5, 7, 9, 11]

filter函数

filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表
两个参数

  • function – 函数
  • iterable – 一个或多个序列

例子:

def is_odd(n):
    return n % 2 == 1


newlist = filter(is_odd, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(list(newlist))

输出:
[1, 3, 5, 7, 9]

过滤其实只需要列表推导式就可以完成

def is_odd(n):
    return n % 2 == 1


old_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
newlist = [i for i in old_list if is_odd(i)]
print(newlist)

输出的结果也是相同的

reduce函数

reduce()函数会对参数序列中元素进行累积

  • function – 函数,有两个参数
  • iterable – 可迭代对象
  • initializer – 可选,初始参数
    例子:
from functools import reduce

sum1 = reduce(lambda x, y: x + y, [1, 2, 3, 4, 5])  # 使用 lambda 匿名函数
print(sum1)

输出:
15

更详细点这里

partial函数

https://blog.csdn.net/qq_33688922/article/details/91890142

匿名函数

lambda关键字在python表达式内部创建匿名函数,尽可能少使用匿名函数,因为这会使得你的代码可读性降低。在每次使用匿名函数时候,尽量加上注释。

例1:传入多个参数的lambda函数

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

用lambda来实现:

p = lambda x,y:x+y
print(p(4,6))

函的数参数

函数参数分类

  1. 关键字参数
    按照形参位置传入的参数被称为位置参数。如果使用位置参数的方式来传入参数值,则必须严格按照定义函数时指定的顺序来传入参数值;如果根据参数名来传入参数值,则无须遵守定义形参的顺序,这种方式被称为关键字(keyword)参数
def test_func(arg1,arg2,arg3=1):
    pass
  1. 缺省参数
    python 允许在形参面前添加一个星号(*),这样就意味着该参数可接受多个参数值,多个参数值被当做元组传入。
def test_func(arg1, arg2, *arg, arg3=1):
    print(arg1, arg2, arg, arg3)


test_func(1, 2, 3, 4, 5, 6)

输出:
1 2 (3, 4, 5, 6) 1

*arg就是缺省参数
3. 可变参数
python 还可以收集关键字参数,此时python会将这种关键字收集成字典,为了让python能收集关键字参数,需要在参数前面添加两个星号。在这种情况下,一个函数可同时包含一个支持“普通”参数收集的参数和一个支持关键字参数收集的参数。

def test_func(arg1, arg2, *arg, arg3=1, **kwargs):
    print(arg1, arg2, arg, arg3, kwargs)


test_func(1, 2, 3, 4, 5, 6, arg3=2, arg4=5, arg5=9)

输出:
1 2 (3, 4, 5, 6) 2 {‘arg4’: 5, ‘arg5’: 9}

参数信息获取

如何获取到一个函数的参数信息呢?
第一种:

def test_func(arg1,arg2,arg3=1,**arg):
    pass

print(test_func.__defaults__)
print(test_func.__code__.co_varnames)
print(test_func.__code__.co_argcount)

输出:
(1,)
(‘arg1’, ‘arg2’, ‘arg3’, ‘arg’)
3

第一种获取方式不够优雅,而且无法得知参数类型,推荐使用第二种方法,也就是inspect包中signature模块。

from inspect import signature


def test_func(arg1, arg2, *arg, arg3=1, **kwargs):
    print(arg1, arg2, arg, arg3, kwargs)


sig = signature(test_func)
print(sig.parameters)
for name,param in sig.parameters.items():
    print(name,param.kind,param.default)

输出:
OrderedDict([(‘arg1’, ), (‘arg2’, ), (‘arg’, ), (‘arg3’, ), (‘kwargs’, )])
arg1 POSITIONAL_OR_KEYWORD
arg2 POSITIONAL_OR_KEYWORD
arg VAR_POSITIONAL
arg3 KEYWORD_ONLY 1
kwargs VAR_KEYWORD

param.kind就可以知道参数类型,到底是关键字参数,还是缺省参数还是可变参数。

signature模块主要获取参数信息,可以用于比如参数类型判断之类的用途。

函数注解

python3提供一种语法,用于为函数声明中参数和返回值附加元数据。
例子中第一行就是函数注解,注解并非硬性的,比如注解了str第一个就必须传入字符串类型,其实就是为了方便阅读。
例子:

def test(text: str, max_len: 'int > 0' = 10) -> str:
    pass

print(test.__annotations__)

输出:
{‘text’: , ‘max_len’: ‘int > 0’, ‘return’: }

函数装饰器和闭包

闭包

内部函数对外部函数作用域里变量的引用。这是官方的解释,个人理解其作用就是函数式编程的面向对象。
众所周知,一个函数执行完毕后,其内部变量就会被销毁,要想保留函数内部变量,就需要使用到闭包。下面举一个最简单的闭包例子:

def func():  # 外部函数
    a = [1]  # 外部函数作用域里的变量

    def func1(num):  # 内部函数
        a.append(num)
        print(a)

    return func1


func1 = func()
func1(0)
func1(0)  # a并没有随着func()的调用而被垃圾回收
del func1  # 调用del后,外部函数变量a被垃圾回收

输出:
[1, 0]
[1, 0, 0]

装饰器

装饰器其实就是闭包的一种语法糖。其意义是不影响原有函数的功能还能添加新功能。

下面举一个最简单的装饰器例子:

def func(in_func):
    def func2():
        print("调用函数之前调用")
        in_func()
        print("调用函数之后调用")

    return func2


@func
def test_func():
    print("调用函数")


test_func()

最终结果:
调用函数之前调用
调用函数
调用函数之后调用

这段代码等价于以下代码:

def func(in_func):
    def func2():
        print("调用函数之前调用")
        in_func()
        print("调用函数之后调用")

    return func2


def test_func():
    print("调用函数")


func2 = func(test_func)
func2()

我们发现在不改变原本test_func函数的功能的情况下在这个函数的之前之后做了处理。
装饰器的使用场景一般用于打日志或者测试框架中,比如想要测试这个函数,需要在执行N个函数之前和之后都要进行相同的处理,就可以使用装饰器一次性搞定。

带参数的装饰器

def log(prefix):
    def log_decorator(f):
        def wrapper(*args, **kw):
            print("参数:", prefix)
            print("函数调用前执行")
            f(*args, **kw)
            print("函数调用后执行")

        return wrapper

    return log_decorator


@log('DEBUG')
def test():
    print("函数调用")


test()

输出:
参数: DEBUG
函数调用前执行
函数调用
函数调用后执行

上述代码与下述代码是一致的:

def log(prefix):
    def log_decorator(f):
        def wrapper(*args, **kw):
            print("参数:", prefix)
            print("函数调用前执行")
            f(*args, **kw)
            print("函数调用后执行")

        return wrapper

    return log_decorator


def test():
    print("函数调用")


log_decorator = log('DEBUG')
wrapper = log_decorator(test)
wrapper()

标准库中常用装饰器

lru_cache

该装饰器能够缓存函数执行的结果,当函数重复执行的时候直接读取缓存,不用再次执行。
例子:

from functools import lru_cache


@lru_cache(None)
def add(x, y):
    print("calculating: %s + %s" % (x, y))
    return x + y


print(add(1, 2))
print(add(1, 2))
print(add(2, 3))

输出:
calculating: 1 + 2
3
3
calculating: 2 + 3
5

可以看到,第二次使用add(1, 2)时并未调用函数,而是使用缓存的结果。

singledispatch

在Python如果需要根据传入参数的类型来让函数执行不同的操作,则此时应该将函数写成泛函数,即使用singledispatch装饰器。太多的if else不容易解耦。

from functools import singledispatch
from collections import abc
import numbers


@singledispatch
def print_type_new(obj):
    pass


@print_type_new.register(numbers.Integral)
def _(n):
    print(f"{n}的类型是Integral")


@print_type_new.register(str)
def _(text):
    print(f"{text}的类型是str")


@print_type_new.register(tuple)
@print_type_new.register(abc.MutableSequence)
def _(text):
    print(f"{text}的类型是Sequence")


@print_type_new.register(abc.Mapping)
def _(text):
    print(f"{text}的类型是Mapping")


print_type_new(1)

输出:
1的类型是Integral

对象

对象引用可变性和垃圾回收

标识,值和别名

狭义来说,等号左边的值就是标识,右边就是值。具有不同标识,相同值的互为别名。

a = [1, 2, 4, 3]
b = a

a,b为标识
[1, 2, 4, 3]为值
a为b的别名

==和is

  • is:比较的是两个对象的id值是否相等,也就是比较俩对象是否为同一个实例对象,是否指向同一个内存地址。
  • ==:比较的是两个对象的内容是否相等,默认会调用对象的__eq__()方法。
a = [1, 2, 4, 3]
b = a
c = [1, 2, 4, 3]

print(a is b)
print(a == c)
print(a is c)
print(id(a))
print(id(b))
print(id(c))

输出:
True
True
False
140424247827392
140424247827392
140424269884096

可变对象与不可变对象

我们考虑一个非常有趣的例子,如果将上面的例子中列表替换成元组,结果将是如何呢?

a = (1, 2, 4, 3)
b = a
c = (1, 2, 4, 3)

print(a is b)
print(a == c)
print(a is c)
print(id(a))
print(id(b))
print(id(c))

输出:
True
True
True
140309300491024
140309300491024
140309300491024

这结果令人大惊失色,就连我也惊了一下,他们竟然都指向了内存的同一区域。

原来,python将所有不可变的变量都集中在了统一的池子中(个人这么理解),创建一个不可变对象时,会先从这个池子中搜索看看是否存在内容相同的,如果存在,直接引用这个值,如果不存在就会创建。

再看下面这个例子:

a = (1, 2, 4, 3)


def test():
    c = (1, 2, 4, 3)
    print(id(c))


print(id(a))
test()

输出:
140307934196576
140307934196576

虽然作用域不同,一个是全局,一个是局部,但他们指向同一个内存区域!

值传递与引用传递?

java中存在值传递和引用传递,但python中只存在引用传递,因为python中万物皆对象!只是引用传递分为可变引用传递与不可变引用传递!
下面举一个例子:

a = [1, 2, 4, 3]
b = 1


def test(a, b):
    a.append(0)
    b += 1


test(a, b)
print(a)
print(b)

输出:
[1, 2, 4, 3, 0]
1

对于可变类型的参数输出,是以引用的方式输入的,所以在调用a.append(0)后原来的a也会改变。
b也是以引用方式传入,只不过指向不可变常量池,当调用b+=1时,常量池中创建了2,局部变量b就指向了2,全局变量还是指向1,那么结果就得以解释!

所以就造成了也有java值传递引用传递的现象,但是本质是不同的!

深拷贝与浅拷贝

  • 浅拷贝:在浅拷贝时,拷贝出来的新对象的地址和原对象是不一样的,但是新对象里面的可变元素(如列表)的地址和原对象里的可变元素的地址是相同的。
  • 深拷贝:在深拷贝时,拷贝出来的新对象的地址和原对象是不一样的,并且新对象里面的可变元素(如列表)的地址和原对象里的可变元素的地址不相同,但值相同。

浅拷贝例子:

import copy

a = [1, 2, [1, 2, 3]]
b = copy.copy(a)

print(id(a))
print(id(b))
b[2].append(4)
print(a)

输出:
140472585594304
140472586169216
[1, 2, [1, 2, 3, 4]]

浅拷贝出来的b中可变对象元素[1,2,3]的地址还是原本的地址,所以b[2].append(4)后a也会变化。

深拷贝例子:

import copy

a = [1, 2, [1, 2, 3]]
b = copy.deepcopy(a)

print(id(a))
print(id(b))
b[2].append(4)
print(a)

输出:
140295886907008
140295887481920
[1, 2, [1, 2, 3]]

深拷贝后,b[2].append(4)并不会影响原本a中的内容。

注:除非调用copy.deepcopy方法,其他一般都是浅拷贝。

del与垃圾回收

del是删除标识,而非对象,对象是在垃圾回收启动时进行销毁。del命令可能会导致对象被当作垃圾回收。
在Cpython中,垃圾回收算法主要是引用计数算法。每个对象会统计有多少个引用指向自己。当引用计数归零时,对象立刻被销毁。

例子:

class Demo:
    def __del__(self):
        print("我被销毁了")


a = Demo()
b = a
del a
print("删除了a")
del b
print("删除了b")

输出:
删除了a
我被销毁了
删除了b

Cpython2.0增加了分代垃圾回收算法。Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。

为什么要分代?给堆内存分代是为了提高对象内存分配和垃圾回收的效率。试想一下,如果堆内存没有区域划分,所有的新创建的对象和生命周期很长的对象放在一起,随着程序的执行,堆内存需要频繁进行垃圾收集,而每次回收都要遍历所有的对象,遍历这些对象所花费的时间代价是巨大的,会严重影响我们的GC效率,这简直太可怕了。

弱引用

一般用于解决循环引用问题,还可以用于做缓存,具体见:https://blog.csdn.net/z_feng12489/article/details/89433878

符合python风格的对象

classmethod和staticmethod

学过java的同学知道,java中存在静态方法,那python中有没有呢?classmethod和staticmethod装饰器有什么区别呢?

  • classmethod的第一个参数为类本身(cls),正如实例方法的第一个参数为对象本身(self);
  • staticmethod第一个参数不需要传入cls或self,故staticmethod中是无法访问类和对象的数据的。
class Demo:
    a = 1

    def __init__(self):
        self.a = 2

    @classmethod
    def class_method(self):
        print(self.a)

    @staticmethod
    def static_method():
        print("我不能访问到内部变量")


Demo.class_method()
Demo.static_method()

输出:
1
我不能访问到内部变量

__hash__和_eq_

下面有个例子:

class Demo:

    def __init__(self):
        self.a = 2
        self.b = [1, 2]


a = Demo()
b = Demo()
print(a == b)

输出:
False

虽然看似类的内部内容是一样的,但a == b还是输出False了,这时候就需要重写hash或者eq方法了。

class Demo:

    def __init__(self):
        self.a = 2
        self.b = [1, 2]

    def __eq__(self, other):
        if self.a == other.a and self.b == other.b:
            return True
        return False

    __hash__ = None  # 假设属性是可变的,所以这里返回None作为可变对象
    # def __hash__(self):
    # 假设为不可变的,那么需要返回一个值。
    # return hash(a)


a = Demo()
b = Demo()
print(a == b)

输出:
True

所以如何定义两个对象相等可以自定义

python的私有属性

java中使用private修饰符创建私有属性,protect修饰符创建保护属性,私有属性只能本类修改,保护属性只能本类或者子类修改。

python中并没有此类约束,如果非要约束,那么对于私有属性,在属性前加两个下划线代表私有属性。加了两个下划线后就会被改写成另外一个名字。

例子:

class Demo:

    def __init__(self):
        self.__a = 2


a = Demo()
print(a.__a)

输出:
AttributeError: ‘Demo’ object has no attribute __a

此时,外部再访问__a就会报错。我们来查询一下__a被改写成什么样子了?

class Demo:

    def __init__(self):
        self.__a = 2


a = Demo()
print(a.__dict__)

输出:
{’_Demo__a’: 2}

__a被改写成了_Demo__a,间接起到私有化的作用

_slots_

https://www.liaoxuefeng.com/wiki/897692888725344/923030542875328

抽象基类

抽象基类的作用类似于JAVA中的接口。在接口中定义各种方法,然后继承接口的各种类进行具体方法的实现。抽象基类就是定义各种方法而不做具体实现的类,任何继承自抽象基类的类必须实现这些方法,否则无法实例化。

例子:

import abc


class Animal(abc.ABC):

    @abc.abstractmethod
    def eat(self):
        return

    @abc.abstractmethod
    def voice(self):
        return


animal = Animal()
animal.eat()

输出:
TypeError: Can’t instantiate abstract class Animal with abstract methods eat, voice

这边定义了一个Animal抽象类,直接调用抽象类中的抽象方法就会报错。

import abc


class Animal(abc.ABC):

    @abc.abstractmethod
    def eat(self):
        return

    @abc.abstractmethod
    def voice(self):
        return


class Bird(Animal):
    def eat(self):
        print("小鸟吃东西啦")

    def voice(self):
        print("小鸟叫啦")


class Fish(Animal):
    def eat(self):
        print("金鱼吃东西啦")

    def voice(self):
        print("金鱼叫啦")


class People(Animal):
    def eat(self):
        print("人吃东西啦")


animal = Bird()
animal.eat()
animal.voice()
animal = Fish()
animal.eat()
animal.voice()
animal = People()
animal.eat()

输出:
小鸟吃东西啦
小鸟叫啦
金鱼吃东西啦
金鱼叫啦
TypeError: Can’t instantiate abstract class People with abstract methods voice

必须要继承抽象类并实现所有抽象方法才能使用,最后一个People类没有实现voice方法,所以会抛出异常。

流程控制

迭代器和生成器

可迭代对象,迭代器

迭代器的意义:迭代是数据处理的基石,扫描内存中放不下的数据集时,我们需要找一种惰性获取数据的方式,即按需一次获取一个数据项。

python中,所有集合都是可迭代的,迭代器用于支持:

  • for循环
  • 构建和扩展集合类型
  • 逐行遍历文本文件
  • 列表推导,字典推导和集合推导
  • 元组拆包
  • 调用函数使用*拆包

可迭代对象:可以直接作用于for循环的对象统称为可迭代对象
迭代器:需要实现迭代器协议的对象,需要实现__next()__和__iter()__方法

下面举一个例子:

class Demo:

    def __init__(self, word):
        self.word = word
        self.word_list = word.split(" ")

    def __iter__(self):
        return DemoIter(self.word_list)


class DemoIter:
    def __init__(self, word_list):
        self.word_list = word_list
        self.index = 0

    def __next__(self):
        try:
            res = self.word_list[self.index]
        except IndexError:
            raise StopIteration()
        self.index += 1
        return res

    def __iter__(self):
        return self


a = Demo("Go home and eat dinner")
for i in a:
    print(i)

输出:
Go
home
and
eat
dinner

其实上面代码和下面等价:

it = iter(a)
while True:
    try:
        print(next(it))
    except StopIteration:
        break

Demo就是个可迭代对象
DemoIter就是迭代器

__next()__方法当raise StopIteration()时候就是迭代器终止之时

my_list=[1,2,3]
for i in my_list:
    print(i)

之所以python可以这么使用而其他语言不可以,就是因为list实现了迭代器协议。

迭代器的应用

单单这么说可能有人觉得这有啥用呢,直接用java的方式用下标索引遍历和这个没有区别啊。
考虑这样一个场景,假如一个文件有几百G,需要便利这个文件显然将其所有内容读入内存中是不可以的,这时候迭代器简化代码的作用就出来了。

这边模拟一种情况,每次读取文件3byte进行处理。这边模拟一个文件test.txt

123456789123456789qwertyuiopasdfghjklzxcvbnm

以下是处理逻辑

class ReadTextLineByLine:

    def __init__(self, file_path):
        self.file_path = file_path

    def __iter__(self):
        return ReadTextLineByLineIter(self.file_path)


class ReadTextLineByLineIter:
    def __init__(self, file_path):
        self.f = open(file_path)

    def __next__(self):
        chunk = self.f.read(3)  # 每次读取3字节
        if not chunk:
            self.f.close()
            raise StopIteration()
        return chunk

    def __iter__(self):
        return self


a = ReadTextLineByLine("test.txt")
for i in a:
    print(i)

输出:
123
456
789
123
456
789
qwe
rty
uio
pas
dfg
hjk
lzx
cvb
nm

生成器

  • 迭代器(iterator)是一个实现了迭代器协议的对象,python的一些内置数据类型(列表,数组,字 符串,字典等)都可以通过for语句进行迭代,我们也可以自己创建一个容器,实现了迭代器协议,可以通过for,next方法进行迭代,在迭代的末尾,会引发stopIteration异常。
  • 生成器(generator)是通过yield语句快速生成迭代器,可以不用iter和next方法

上面的例子中可以看到实现可迭代对象需要实现迭代器ReadTextLineByLineIter,这样非常笨重,生成器就是解决这个问题的类似语法糖的东西。

上面的例子可以这样改写:

class ReadTextLineByLine:

    def __init__(self, file_path):
        self.file_path = file_path
        self.f = open(self.file_path)

    def __iter__(self):
        chunk = self.f.read(3)  # 每次读取3字节
        while chunk:
            chunk = self.f.read(3)
            yield chunk
        self.f = open(self.file_path)

    def __del__(self):
        self.f.close()


a = ReadTextLineByLine("test.txt")
for i in a:
    print(i)

输出:
456
789
123
456
789
qwe
rty
uio
pas
dfg
hjk
lzx
cvb
nm

可以看到输出的结果相同

yield是生成器特有的关键字,每次调用next方法是就会生成出一个值。

def gen():
    yield 1
    print("begin")
    yield 2
    print("continue")
    yield 3
    print("end")


func = gen()
print(next(func))
print(next(func))
print(next(func))
print(next(func))

输出:
1
begin
2
continue
3
end
Traceback (most recent call last):
File “/Users/tangdexuan/myApp/dev/shuati/test.py”, line 14, in
print(next(func))
StopIteration

def gen():
    yield 1
    print("begin")
    yield 2
    print("continue")
    yield 3
    print("end")


func = gen()
for i in func:
    print(i)

输出:
1
begin
2
continue
3
end

yield from

如果生成器函数需要产出另一个生成器的值,传统的解决方法是使用嵌套for循环,yield from是语法糖简化这个逻辑的。
例子:

b = [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]


def gen(b):
    for i in b:
        yield from i


print(list(gen(b)))

输出:
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]

上下文管理和else块

if 语句之外的else块

  • for:仅当for循环运行完毕(没有break语句终止)才运行else块
  • while:仅当while循环运行完毕(没有break语句终止)才运行else块
  • try:仅当try没有抛出异常才运行else块

例子:

my_list = ["apple", "banana", "orange"]
my_list2 = ["apple", "grape", "orange"]


def find_banana(my_list):
    for i in my_list:
        if i == "banana":
            print("找到香蕉")
            break
    else:
        raise Exception("没找到香蕉")


find_banana(my_list)
find_banana(my_list2)

输出:
找到香蕉
Exception: 没找到香蕉

上下文管理器和with块

class Sample:
    def __enter__(self):
        print("数据库开启咯")
        return "模拟获取数据库连接"

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("数据库关闭咯")


sample = Sample()
with sample as s:
    print(s)
    print("我要操作数据库咯")

输出:
数据库开启咯
模拟获取数据库连接
我要操作数据库咯
数据库关闭咯

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