This post was published in 2021-10-19. Obviously, expired content is less useful to users if it has already pasted its expiration date.
Table of Contents
Python dunder methods/magic methods
🔗 [Python中的魔术方法(Dunder or magic methods) - 简书] https://www.jianshu.com/p/cfcec753b8cd
在Python中,有一类名字前后有双下划线做前缀和后缀的方法,例如:__ init __, __ add __, __ len __, __ repr __ 。这类方法叫做魔术方法Magic method,通常用于重载Python内建方法和运算(overload Python’s built-in methods and its operators)。更Pythonic的叫法是:Dunder method -- “Double Under (Underscores)”。
https://www.jianshu.com/p/cfcec753b8cd
__repr__()应该如何理解?应该理解为 representation
补充:🔗 [python - How to print instances of a class using print()? - Stack Overflow] https://stackoverflow.com/questions/1535327/how-to-print-instances-of-a-class-using-print
在这个stackoverflow的提问里,有类似这样一段代码:
class Test:
def __init__(self):
self.a = "foo"
self.b = "bar"
t = Test()
print(t)
# <__main__.Test object at 0x7fca19726e20>
本意是想要打印一些有用的东西,比如object t里面的所有attributes,显然这个时候不能直接print(t)
解决方法1:使用print(vars(t)),但此时只能打印所有变量,如果我有什么其他要求呢?(比如按照某种格式打印部分变量)
所以解决方法2为:重写__repr__(self)方法:
class Test:
def __init__(self):
self.a = "foo"
self.b = "bar"
def __repr__(self):
return "a value: %s, b value: %s" % (self.a, self.b)
t = Test()
print(t)
# a value: foo, b value: bar
另一个需要注意的是__repr__和__str__一起使用时的效果:
class demo:
def __repr__(self):
return 'call repr'
def __str__(self):
return 'call str'
d = demo()
print(repr(d))
print(d)
结果:
call repr
call str
如果在class里重写了 __repr__() ,但又想调用python原本的 __repr__() ,那么可以使用 object.__repr__(obj) (同理其他dunder methods),如下:
参考:[python - How can we get the default behavior of __repr__()? - Stack Overflow] https://stackoverflow.com/questions/48777014/how-can-we-get-the-default-behavior-of-repr
class demo:
def __repr__(self):
return 'call repr'
def __str__(self):
return 'call str'
d = demo()
print(repr(d))
print(d)
print(object.__repr__(d))
结果:
call repr
call str
<__main__.demo object at 0x7ffaa4377c10>
但是仍然要注意:
class demo:
def __repr__(self):
return 'call repr'
def __str__(self):
return 'call str'
d = demo()
print(repr(d))
print(d)
print(object.__repr__(d))
print(str(d))
print(object.__str__(d))
结果:
call repr
call str
<__main__.demo object at 0x7f952ca97c50>
call str
call repr
注意上面代码的第14行,执行 object.__str__(d) 会涉及到 __repr__() ,是因为:
同时,stackoverflow上面还有一个热门问题:🔗 [python - What is the difference between __str__ and __repr__? - Stack Overflow] https://stackoverflow.com/questions/1436703/what-is-the-difference-between-str-and-repr
在这个问题里,有一个答案:https://stackoverflow.com/a/1436756
可以引申出这个话题:【python创建新对象】,单独拉出来作为一节目录:
Python创建新对象
class demo:
pass
d = demo()
print(d)
e = demo()
print(e)
print(demo())
print(demo())
print(id(demo()))
print(id(demo()))
结果:
<__main__.demo object at 0x7fefc4185210>
<__main__.demo object at 0x7fefc41d2b90>
<__main__.demo object at 0x7fefc4397c90>
<__main__.demo object at 0x7fefc4397c90>
140667766013072
140667766013072
看起来两个(不进行赋值操作的) demo() objects拥有相同的地址和id,那么它们是同一个object吗?( demo() is demo() 会返回True吗?)
不是的:
class demo:
pass
d = demo()
print(d)
e = demo()
print(e)
print(demo())
print(demo())
print(id(demo()))
print(id(demo()))
print(demo() is demo())
结果:
<__main__.demo object at 0x7fcfb967d210>
<__main__.demo object at 0x7fcfb96cabd0>
<__main__.demo object at 0x7fcfbb297c90>
<__main__.demo object at 0x7fcfbb297c90>
140530175016080
140530175016080
False
具体解释可以见:🔗 [How can two Python objects have same id but 'is' operator returns False? - Stack Overflow] https://stackoverflow.com/questions/50893267/how-can-two-python-objects-have-same-id-but-is-operator-returns-false
大概意思就是说:在 demo() is demo() 里,前面的demo()创建了一个新的对象,但是因为没有创建对应的reference,所以立刻被销毁了;后面的demo()创建的是另一个新的对象,只是使用了相同的地址。
为了验证前面的猜想(两个生命周期不重叠的对象拥有相同的ID)不是偶然,可以加大剂量:
class demo:
pass
for i in range(0, 100000):
if id(demo()) != id(demo()):
print('Find two different IDs')
exit()
a = demo()
b = demo()
c = demo()
d = demo()
print('not found')
结果:
not found
针对"different object with same id"还有其他更深入的技巧,见:🔗 [How to make two objects have the same id in python? - Stack Overflow] https://stackoverflow.com/questions/16977196/how-to-make-two-objects-have-the-same-id-in-python
Python 继承
2021-10-19原版笔记
快速入门教程:🔗 [继承和多态 - 廖雪峰的官方网站] https://www.liaoxuefeng.com/wiki/1016959663602400/1017497232674368#0
代码(同时参考了🔗 [python - How to call super method from grandchild class? - Stack Overflow] https://stackoverflow.com/questions/31232098/how-to-call-super-method-from-grandchild-class)
class A:
def call(self):
print('A')
class B(A):
def call(self):
print('B')
class C(B):
def call(self):
super().call()
A.call(self)
super(B, self).call()
if __name__ == '__main__':
C().call()
结果:
B
A
A
如果修改class B的代码如下:
class A:
def call(self):
print('A')
class B(A):
# def call(self):
# print('B')
pass
class C(B):
def call(self):
super().call()
A.call(self)
super(B, self).call()
if __name__ == '__main__':
C().call()
则结果为:
A
A
A
2024-01-15新增
实际写代码的时候遇到了一些新的需求:想快速把父类转换为子类,但又不想写那么多代码(因为子类仅仅比父类多几个特殊属性),怎么办?
比如:class Vehicle:一堆属性,速度、坐标、制造商、路线计划、行驶里程,等等
现在我临时要加个class Bus,拥有class Vehicle的所有属性,自己仅仅是多了几个特殊属性
但当前的一堆代码都是操作class Vehicle的,比如move(v: Vehicle),比如shortest_path(v: Vehicle),而且class Vehicle里面的属性特别多,根本不想写又臭又长的代码把属性一个一个迁移到Bus object里面去
核心代码: **vars(vehicle_obj)
from copy import deepcopy
class Vehicle:
def __init__(self):
self.uid = None
self.speed = None
self.plate = None
self.manufacturer = None
self.coordinate_x = None
self.coordinate_y = None
def assign_attributes(self, uid, speed, plate, manufacturer, coordinate_x, coordinate_y):
self.uid = uid
self.speed = speed
self.plate = plate
self.manufacturer = manufacturer
self.coordinate_x = coordinate_x
self.coordinate_y = coordinate_y
class Bus(Vehicle):
def __init__(self, vehicle_obj, bus_capacity):
super().__init__()
# 这是shallow copy
# self.assign_attributes(**vars(vehicle_obj))
self.assign_attributes(**deepcopy(vars(vehicle_obj))) # 这是deepcopy
self.bus_capacity = bus_capacity
def vehicle_move(v: Vehicle):
# 假设这是一个原本写的方法,现在不想修改它
print('move %s' % v.__class__.__name__)
v.coordinate_x += 1
v.coordinate_y += 1
v = Vehicle()
v.assign_attributes(uid=12345, speed=10, plate="AAAAAA", manufacturer="Tesla", coordinate_x=99, coordinate_y=99)
vehicle_move(v)
# 临时增加一个新的class Bus
b_v = Bus(v, bus_capacity=30)
vehicle_move(b_v)
print(vars(v))
print(vars(b_v))
运行结果:
move Vehicle
move Bus
{'uid': 12345, 'speed': 10, 'plate': 'AAAAAA', 'manufacturer': 'Tesla', 'coordinate_x': 100, 'coordinate_y': 100}
{'uid': 12345, 'speed': 10, 'plate': 'AAAAAA', 'manufacturer': 'Tesla', 'coordinate_x': 101, 'coordinate_y': 101, 'bus_capacity': 30}
最后打印v和b_v变量的时候可以看到,b_v和v的坐标是不同的,说明b_v被复制创造出来以后,vehicle_move(b_v)确实只移动了b_v,而没有移动v ,事实上,代码里提到的两种复制变量的语句都能做到这一点:
self.assign_attributes(**vars(vehicle_obj))
self.assign_attributes(**deepcopy(vars(vehicle_obj)))
但deepcopy显然能把这件事情做得更彻底,使用deepcopy创造出b_v以后,b_v和v不会具有任何联系,可以放心大胆的修改它们,而无需担心reference to object这样的东西暗中作祟。
有关deepcopy的笔记:🔗 [2021-03-24 - Truxton's blog] https://truxton2blog.com/2021-03-24/
如果一定要看看不使用deepcopy的后果:
下面这个代码没有使用deepcopy,并尝试在b_v被复制出来以后修改v的属性,结果发现b_v的属性也被一带修改了:
from copy import deepcopy
class Vehicle:
def __init__(self):
self.uid = None
self.speed = None
self.plate = None
self.manufacturer = None
self.coordinate_x = None
self.coordinate_y = None
def assign_attributes(self, uid, speed, plate, manufacturer, coordinate_x, coordinate_y, routing_plan):
self.uid = uid
self.speed = speed
self.plate = plate
self.manufacturer = manufacturer
self.coordinate_x = coordinate_x
self.coordinate_y = coordinate_y
self.routing_plan = routing_plan
class Bus(Vehicle):
def __init__(self, vehicle_obj, bus_capacity):
super().__init__()
# 这是shallow copy
self.assign_attributes(**vars(vehicle_obj))
# self.assign_attributes(**deepcopy(vars(vehicle_obj))) # 这是deepcopy
self.bus_capacity = bus_capacity
def vehicle_move(v: Vehicle):
# 假设这是一个原本写的方法,现在不想修改它
print('move %s' % v.__class__.__name__)
v.coordinate_x += 1
v.coordinate_y += 1
v = Vehicle()
v.assign_attributes(uid=12345, speed=10, plate="AAAAAA", manufacturer="Tesla", coordinate_x=99, coordinate_y=99,
routing_plan=[0, 1, 2, 3, [4, 5, 6]])
vehicle_move(v)
# 临时增加一个新的class Bus
b_v = Bus(v, bus_capacity=30)
vehicle_move(b_v)
del v.routing_plan[-1] # 新增了这一行
print(vars(v))
print(vars(b_v))
运行结果:
move Vehicle
move Bus
{'uid': 12345, 'speed': 10, 'plate': 'AAAAAA', 'manufacturer': 'Tesla', 'coordinate_x': 100, 'coordinate_y': 100, 'routing_plan': [0, 1, 2, 3]}
{'uid': 12345, 'speed': 10, 'plate': 'AAAAAA', 'manufacturer': 'Tesla', 'coordinate_x': 101, 'coordinate_y': 101, 'routing_plan': [0, 1, 2, 3], 'bus_capacity': 30}
发现 del v.routing_plan[-1] 这句话也会把b_v变量里的routing_plan一带改变了。这就不是我们想要的结果。
重新复习一遍merge sort的性质
参考🔗 [2021-10-17 - Truxton's blog] https://truxton2blog.com/2021-10-17/ , 🔗 [2021-10-18 - Truxton's blog] https://truxton2blog.com/2021-10-18/
worst case | best case | average | |
merge sort | [mathjax]n\log(n)[/mathjax] | [mathjax]n\log(n)[/mathjax] | [mathjax]n\log(n)[/mathjax] |
各大主流排序算法的时间复杂度
资料查找来源:
Insertion sort 🔗 [Insertion sort - Wikipedia] https://en.wikipedia.org/wiki/Insertion_sort
quicksort:🔗 [2021-10-18 - Truxton's blog] https://truxton2blog.com/2021-10-18/
heap sort: 🔗 [2021-10-18 - Truxton's blog] https://truxton2blog.com/2021-10-18/ (搜索"主定理")🔗 [Heapsort - Wikipedia] https://en.wikipedia.org/wiki/Heapsort
merge sort:🔗 [2021-10-17 - Truxton's blog] https://truxton2blog.com/2021-10-17/ ,🔗 [2021-10-18 - Truxton's blog] https://truxton2blog.com/2021-10-18/
bucket sort: 📚 见中文算法导论p103
radix sort: 来源于中文算法导论和网络资源(wikipedia, geekforgeeks.org)
Best Case | Worst Case | Average | |
bubble sort | [mathjax]O(n)[/mathjax] | [mathjax]O(n^2)[/mathjax] | [mathjax]O(n^2)[/mathjax] |
selection sort | [mathjax]O(n^2)[/mathjax] | [mathjax]O(n^2)[/mathjax] | [mathjax]O(n^2)[/mathjax] |
Insertion sort | [mathjax]O(n)[/mathjax] | [mathjax]O(n^2)[/mathjax] | [mathjax]O(n^2)[/mathjax] |
quicksort | [mathjax]O(n\log(n))[/mathjax] | [mathjax]O(n^2)[/mathjax] | [mathjax]O(n\log(n))[/mathjax] |
randomized quicksort | [mathjax]O(n\log(n))[/mathjax] | [mathjax]O(n\log(n))[/mathjax] | [mathjax]O(n\log(n))[/mathjax] |
heap sort | [mathjax]O(n\log(n))[/mathjax] | [mathjax]O(n\log(n))[/mathjax] | [mathjax]O(n\log(n))[/mathjax] |
merge sort | [mathjax]O(n\log(n))[/mathjax] | [mathjax]O(n\log(n))[/mathjax] | [mathjax]O(n\log(n))[/mathjax] |
bucket sort (+ insertion sort) | [mathjax]O(n)[/mathjax] | [mathjax]O(n^2)[/mathjax] | [mathjax]O(n)[/mathjax] |
radix sort | [mathjax]O(nk)[/mathjax], k: max number of digits | [mathjax]O(nk)[/mathjax], k: max number of digits | [mathjax]O(nk)[/mathjax], k: max number of digits |
counting sort | [mathjax]O(n+k)[/mathjax], k: range of inputs | [mathjax]O(n+k)[/mathjax], k: range of inputs | [mathjax]O(n+k)[/mathjax], k: range of inputs |
Python语法杂项
python数组的[:]切片
>>> a=[1,2,3,4]
>>> a[:]
[1, 2, 3, 4]
>>> a[:-1]
[1, 2, 3]
>>> a[::-1]
[4, 3, 2, 1]
>>> a[::-2]
[4, 2]
>>> a[:2:1]
[1, 2]
>>> a[:1:-1]
[4, 3]
Python: mutable和immutable
补充:这张表可以用来记忆 python的参数传递机制 ,具体见: 🔗 [2022-02-07 - Truxton's blog] https://truxton2blog.com/2022-02-07/#Python函数的参数传递机制
Python set, list, tuple的区别
🔗 [python中set、list与tuple的区别 - 简书] https://www.jianshu.com/p/10db00d9a682
Python type hints
见:🔗 [typing — Support for type hints — Python 3.10.0 documentation] https://docs.python.org/3/library/typing.html
没有完成的内容
有关[mathjax]F(n)=F(n-1)+O(2^n)[/mathjax]的时间复杂度推导
(鸽了)