python中万物皆对象,每个对象都有自己的魔法方法(特殊方法)
_repr_、_str_、_format_、_byte_
例子:
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_
例子:
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_
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模块包涵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是将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
例子:
这边查到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类型转化为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包常用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() 会根据提供的函数对指定序列做映射
两个参数
例子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() 函数用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表
两个参数
例子:
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()函数会对参数序列中元素进行累积
from functools import reduce
sum1 = reduce(lambda x, y: x + y, [1, 2, 3, 4, 5]) # 使用 lambda 匿名函数
print(sum1)
输出:
15
更详细点这里
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))
def test_func(arg1,arg2,arg3=1):
pass
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’,
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’:
内部函数对外部函数作用域里变量的引用。这是官方的解释,个人理解其作用就是函数式编程的面向对象。
众所周知,一个函数执行完毕后,其内部变量就会被销毁,要想保留函数内部变量,就需要使用到闭包。下面举一个最简单的闭包例子:
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()
该装饰器能够缓存函数执行的结果,当函数重复执行的时候直接读取缓存,不用再次执行。
例子:
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)时并未调用函数,而是使用缓存的结果。
在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的别名
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命令可能会导致对象被当作垃圾回收。
在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
学过java的同学知道,java中存在静态方法,那python中有没有呢?classmethod和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
我不能访问到内部变量
下面有个例子:
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
所以如何定义两个对象相等可以自定义
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,间接起到私有化的作用
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循环的对象统称为可迭代对象
迭代器:需要实现迭代器协议的对象,需要实现__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
上面的例子中可以看到实现可迭代对象需要实现迭代器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
如果生成器函数需要产出另一个生成器的值,传统的解决方法是使用嵌套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]
例子:
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: 没找到香蕉
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("我要操作数据库咯")
输出:
数据库开启咯
模拟获取数据库连接
我要操作数据库咯
数据库关闭咯