Week 14
本周学习主要内容包括魔术方法
查看属性
dir(obj)对于不同类型的对象obj具有不同的方法:
- 如果对象是模块对象,返回列表包括模块的属性名和变量名
- 如果对象是类型或者是类对象,返回的列表包含类的属性名,及他的祖先类的属性名
- 如果是类的实例:
- 有 __ dir __方法,返回可迭代对象的返回值
- 没有 __ dir __方法,则尽可能收集实例的属性名、类的属性和祖先类(object)的属性名
- 如果obj不写,返回列表包含内容不同
- 在模块中,返回模块的属性和变量名
- 在函数中,返回本地作用域的变量名
- 在方法中,返回本地作用域的变量名(同函数)
__ dir 方法:意义是返回类或对象的所有成员的名称列表;dir()函数操作实例就是调用 dir __()
如果dir([obj])参数obj包含方法__ dir (),该方法将被调用。如果参数obj不包含 dir __(),该方法将最大限度的收集属性信息
print('-' * 30)
#import t3 # 模块名,加载模块,执行模块
#from t3 import Animal #导入模块,同一模块只导入一次就够了
print('=' * 30)
class Cat(Animal):
x = 'abc'
y = 'cat class'
#print("module's name = {},{}".format(t3.__name__, dir(t3))) #和l2执行的结果一样
print("module's name = {},{}".format(__name__, dir()))
print('*' * 30)
print(dir(Animal))
print(dir(Cat)) #尽可能收集/搜集属性
# Python是动态语言,dir对象有什么属性
tom = Cat('tom') #实例
print(tom)
print(dir(tom))
print(sorted(tom.__dict__.keys() | Cat.__dict__.keys()
| Animal.__dict__.keys()
| object.__dict__.keys()
))
print('&&&&&&&&&&&&&&&&&&')
class Dog(Animal):
def __dir__(self): #这个魔术方法self,和当前实例有关
return [123] #返回一个可迭代对象(必须!!),里面应该是字符串
dog = Dog('snoopy')
print(dog)
print(dir(dog))
print(dir(Dog))
内建函数:
- locals()返回当前作用域中的变量字典
- globals()当前模块全局变量的字典
魔术方法
- 分类:
- 创建、初始化和销毁:
- __ new __ (静态方法)
- __ init __ (实例方法)和 __ del __
- 可视化
- __ hash __:
- 集合的去重原理
- __ bool __
- 运算符重载
- 容器和大小
- 可调用对象
- 上下文管理
- 反射
- 描述器
- 其他杂项
- 创建、初始化和销毁:
实例化
__ new __ 方法(静态方法):
- 实例化一个对象
- 该方法需要返回一个值,如果该值不是cls的实例,则不会调用__ init __
- 该方法永远都是静态方法
- 很少使用,即使创建,也会用return super().__ new __ (cls)基类object的 __ new __ 方法来创建实例并返回
class A:
def __new__(cls, *args, **kwargs): #new方法是什么方法?-- 静态方法,没有自动注入效果,要手动注入(必须!!)
print('A new ~~~~~~~~')
print(cls) #cls当前类
print(args)
args = ('jerry',)
print(kwargs)
kwargs['age'] = 30
#return cls(*args,**kwargs) #不行,new方法递归了 #cls(*args,**kwargs) => A('tom',age=20)
#return []
#return '123'
#return object.__new__(cls) #使用cls A类对象,模板,构建一个实例
return super().__new__(cls) #等价同上,推荐这种写法
def __init__(self,name,age):
print('A init____')
self.name = name
self.age = age
t = A('tom',age=20) # new init
print(t,type(t))
print(t.name) #tom
print(t.age) #20
可视化
方法 | 意义 |
---|---|
__str__ | str()函数、format()函数、print()函数调用,需要返回对象的字符串表示。如果没有定义,就去调用 __ repr __ 方法返回字符串表达,如果 __ repr __ 没有定义,就直接返回对象的内存地址信息 |
__repr__ | 内建函数repr()对一个对象获取字符串表达。调用 __ repr __ 方法返回字符串表达,如果 __ repr __ 没有定义,就直接返回object的定义,即内存地址信息 |
__bytes__ | bytes()函数调用,返回一个对象的bytes表达,即返回bytes对象 |
class Person:
def __init__(self,name,age=18):
self.name = name
self.age = age
def __str__(self):
return "str 我是Person的实例,我的名字叫{}".format(self.name) #对实例的表达展示 / 可视化
def __repr__(self):
return "repr 我是Person的实例,我的名字叫{}".format(self.name) #对实例的表达展示 / 可视化
def __bytes__(self):
#return "bytes 我是Person的实例,我的名字叫{}".format(self.name).encode() #对实例的表达展示 / 可视化
return str(self).encode()
#__str__ __repr__ __bytes__
a = Person('tom')
print(a) #print直接作用于这个实例
print(str(a)) #str 作用在一个实例上
print("str.format {}".format(a))
print('-'*30)
print([a],(a,),{a})
print('='*30)
print(bytes(a))
print([a],[str(a)]) #str(a)是强制类型转换 #type(a) = <class '__main__.Person'> #type(str(a)) = <class 'str'>
print(repr(a),a)
-----------------------------------------------
str 我是Person的实例,我的名字叫tom
str 我是Person的实例,我的名字叫tom
str.format str 我是Person的实例,我的名字叫tom
------------------------------
[repr 我是Person的实例,我的名字叫tom] (repr 我是Person的实例,我的名字叫tom,) {repr 我是Person的实例,我的名字叫tom}
==============================
b'str \xe6\x88\x91\xe6\x98\xafPerson\xe7\x9a\x84\xe5\xae\x9e\xe4\xbe\x8b,\xe6\x88\x91\xe7\x9a\x84\xe5\x90\x8d\xe5\xad\x97\xe5\x8f\xabtom'
[repr 我是Person的实例,我的名字叫tom] ['str 我是Person的实例,我的名字叫tom']
repr 我是Person的实例,我的名字叫tom str 我是Person的实例,我的名字叫tom
Hash
- __ hash __ :内建函数hash()调用的返回值,返回一个整数,如果定义这个方法该类的实例就可hash
- __ eq __ :对应 == 操作符,判断两个对象内容是否相等,返回bool值,定义了这个方法,如果不提供 __ hash __ 方法,那么实例将不可hash
- hash值相等,知识hash冲突,不能说明两个对象相等
- 一般来说提供 __ hash __ 方法是为了作为set或者dict的key,但去重还需要同时提供__ eq __ 方法来判断两个对象是否相等
- 不可hash对象isinstance(p1,collections.Hashbale)一定为False
hash及集合去重原理:
class Person:
def __init__(self,name):
self.name = name
def __hash__(self):
return 100
#return hash(self.name)
def __repr__(self):
return self.name
def __eq__(self, other):
return self.name == other #self.__eq__(other)
tom1 = Person('tom')
tom2 = Person('tom')
print(0,id(tom1),id(tom2)) #内存地址不同 #0 2144389526536 2144389525960
print(2,hash(tom1)) #2 3163522554436153082
print(3,hash(tom2)) #3 3163522554436153082
#hash值相同,hash地址相同,has冲突,看内容是否相等
s = {tom2,tom1} #print(tom1 == tom2) #False #tom1.__eq__(tom2)
print(s) #tom
#set去重原理,第一步必须求hash,hash冲突才需要去重
#第二步,如果有必要去重,去重时比较内容
#去重原则:先到先占,与顺序有关
jerry = Person('jerry')
s = {jerry,tom1,tom2,jerry}
#s = {tom1,tom2,jerry} #tom
#s = {jerry,tom1,tom2} #jerry
print(s) #{tom, jerry}
bool
- __ bool __ :
- 内建函数bool(),或者对象放在逻辑表达式的位置,调用这个函数返回布尔值
- 没有定义 __ bool __ (),就找 __ len __ ()返回长度,非0为真
- 如果 __ len __()也没有定义,那么所有实例都返回真
class A: pass
print(bool(A))
print(bool(A()))
print('-'*30)
class B:
def __bool__(self):
print('bool~~~~~~~~~')
return bool(0)
print(bool(B))
print(bool(B()))
print('='*30)
class C(list):
def __len__(self):
return 0
print(bool(C))
c = C()
c.append(123)
print(bool(c)) #False
print(c) #[123]
---------------------------------------------
True
True
------------------------------
True
bool~~~~~~~~~
False
==============================
True
False
[123]
运算符重载
operator模块提供以下特殊方法可以将类的实例使用下面的操作符来操作:
运算符 | 特殊方法 | 含义 |
---|---|---|
<, <=, ==, >, >=, != | __lt__, __le__, __eq__, __gt__, __ge__, __ne__ | 比较运算符 |
+, -, *, /, %, //, **, divmod | __add__, __sub__, __mul__, __truediv__, __mod__, __floordiv__, __pow__, __divmod__ | 算术运算符,移位、位运算也有对应的方法 |
+=, -=, *=, /=, %=, //=, **= | __iadd__, __isub__, __imul__, __itruediv__, __imod__, __ifloordiv__, __ipow__ |
class Person:
def __init__(self,name,age=18):
self.name = name
self.age = age
def __sub__(self, other): #双目
print('sub~~~~~~~~~~')
return self.age - other.age
def __isub__(self, other):
print('isub~~~~')
#return Person(self.name,self.age - other.age) #返回一个全新的实例
#self. age = self.age - other.age
self.age -= other.age
return self
def __repr__(self):
return "<Person {} {}>".format(self.name,self.age)
tom = Person('tom',20)
print(1,tom,id(tom))
jerry = Person('jerry')
print(2,jerry,id(jerry))
print(tom - jerry) #tom.__sub__(jerry)
print(tom,jerry)
print('-'*30)
tom -= jerry #就地修改,还有覆盖的过程 #tom = tom,地址不变
#tom = tom-jerry => tom.__sub__(jerry) 返回结果同上
print(tom,type(tom)) #tom变了,tom=2,
print(3,tom,id(tom)) #此时tom类型不变了,地址也不变
----------------------------------------------------
1 <Person tom 20> 2144430252296
2 <Person jerry 18> 2144429425224
sub~~~~~~~~~~
2
<Person tom 20> <Person jerry 18>
------------------------------
isub~~~~
<Person tom 2> <class '__main__.Person'>
3 <Person tom 2> 2144429095176
运算符重载应用场景
- 用面向对象实现的类需要做大量运算,而运算符是这种运算在数学上最常见的表达方法,往往提供运算符重载比直接提供加法方法更适合该领域内使用者的习惯
- int类,几乎实现了所有操作符,可以作为参考
@functools.total_ordering装饰器
__lt__, __le__, __eq__, __gt__, __ge__
是比较大小必须实现的方法,但全部写完太麻烦,使用@functools.total_ordering装饰器可以简化代码- 但要求
__eq__
必须实现,其他方法__lt__, __le__ , __gt__, __ge__
实现其一 - 但装饰器可能会带来问题,最好还是自己创建所需方法,少用这个装饰器
__eq__
等于可以推断不等于__gt__
大于可以推断小于__ge__
大于等于可以推断小于等于
from functools import total_ordering
#@total_ordering #这个装饰器可以不用
class Person:
def __init__(self,name,age=18):
self.name = name
self.age = age
def __eq__(self, other): #eq,ne必须提供一个等或不等
return self.age == other.age
def __gt__(self, other): #> <
return self.age > other.age
def __ge__(self, other): #>= <=
return self.age >= other.age
tom = Person('tom',20)
jerry = Person('jerry')
print(tom == jerry)
print(tom != jerry)
print(tom > jerry)
print(tom < jerry)
print(tom >= jerry)
print(tom <= jerry)
容器相关方法
方法 | 意义 |
---|---|
__len__ | 内建函数len(),返回对象的长度(>=0的整数),如果把对象当作容器类型看,就如同list或者dict。bool()函数调用的时候如果没有 __ bool __ ()的方法,则会看__ len __ ()方法是否存在,存在返回非0为真 |
__iter__ | 迭代容器时,调用,返回一个新的迭代器对象 |
__contains__ | in成员运算符,没有实现,就调用__ iter __方法遍历 |
__getitem__ | 实现 self [key] 访问。序列对象,key接受整数位索引,或者切片。对于set和dict,key位hashable。key不存在引发KeyError异常 |
__setitem__ | 和__ getitem __ 类似,是设置值方法 |
__missing__ | 字典或其子类使用__ getitem __ () 调用时,key不存在执行该方法 |
改造购物车为一种Pythonic写法
class Cart: #Pythonic
def __init__(self):
self.__items = []
def additem(self,item):
return self + item
def __add__(self, other):
self.__items.append(other)
return self
def __len__(self):
return len(self.__items)
def __str__(self):
return str(self.__items)
def __getitem__(self, index):
print(index)
return self.__items[index]
def __setitem__(self, index, value):
self.__items[index] = value
def __iter__(self): #-> 迭代器
#return iter(self.__items)
#yield from self.__items #语法糖,同下
for x in self.__items:
yield x #三种写法等价
#方便操作的容器类转换:
cart = Cart()
print(len(cart))
print(bool(cart))
cart.additem(1)
cart.additem('abc')
cart.additem(3)
cart.additem(4).additem(5).additem(6)
print(len(cart),bool(cart))
print(cart)
print(cart[1])
cart[1] = 'xyz'
print(cart)
print(cart + 7 + 8 + 9) #(链式编程) #[1, 'xyz', 3, 7, 8, 9]
print('-'*30)
#自身变成iter对象可以用for循环
for x in cart:
print(x)
print(1 in cart)
print('abc' in cart)
print('='*30)
可调用对象
- 函数即对象,对象加上(),就是调用此函数对象的方法
- __call__方法:类中定义一个该方法,实力就可以像函数一样调用
- 可调用对象:定义一个类,并实例化得到其实例,将实例像函数一样调用
class Point:
def __init__(self,x,y):
self.x = x
self.y = y
def __call__(self):
print("<{} {}:{}>".format(__class__.__name__,self.x,self.y))
Point(4,5)() #可调用类似柯里化
#调用多次:
class Adder():
def __init__(self):
self.result = 0
def __call__(self, *args):
self.result = sum(args)
return self.result
adder = Adder()
print(adder.result)
print(adder(4,5,6,7))
print(adder.result)
#可调用对象:函数(方法),类,类的实例(可或不可,看提供call方法没有)
- 斐波那契数列:
class Fib:
def __init__(self):
self.items = [0,1,1] #fib(0), fib(1),fibe(2)
def __len__(self):
return len(self.items)
def __call__(self, index): #index 3
return self[index]
def __str__(self):
return "{}".format(self.items)
__repr__ = __str__
def __iter__(self):
return iter(self.items)
def __getitem__(self, index):
if index < 0:
raise ValueError('Wrong index. Not negative index {}'.format(index))
for i in range(len(self),index+1):
self.items.append(self.items[i-1]+self.items[i-2])
return self.items[index]
fib = Fib()
print(len(fib))
print(fib(5))
print(fib)
for x in enumerate(fib): #可迭代对象
print(x)
print(fib[5])
上下文管理
- 文件IO操作可以对文件对象使用上下文管理,with…as语法
- 仿照文件操作写一个类进行上下文管理时:
- 提示属性错误,需要__ exit __ / __ enter __ 属性
上下文管理对象
- 当一个对象同时实现__ exit __ () 和 __ enter __ ()方法,他就属于上下文管理的对象
方法 | 意义 |
---|---|
__enter__ | 进入于此对象相关的上下文,如果存在该方法,with语法会把该方法的返回值作为绑定到as子句中指定的变量上 |
__exit__ | 退出与此对象相关的上下文 |
import time
class Point:
def __init__(self):
print('1 init~~~~~~~~~~~~')
print('2 init over~~~~~~~~')
def __enter__(self):
print('3 enter~~~~~~~~~~')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('6 exit~~~~~~~~~')
print(exc_type)
print(exc_val)
print(exc_tb)
print('6 ~~~~~~~~~~')
#return ''
#return 123
#return True
#return False
#return [1] #exit的返回值等效为True 压制异常
p1 = Point()
#with操作会调用实例的__enter__, as子句后的标识符会得到__enter__返回值
#f = p1.++enter__() f = p1
with p1 as f:
print('with~~~~~~~~~~~~~~~')
- 实例化对象的时候,不会调用enter,进入with语句块调用__ enter __ 方法,然后执行语句,最后离开with语句块时调用__ exit __ 方法
- with可以开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作。with并不开启一个新的作用域
上下文管理的安全性
异常对上下文影响:
import time
class Point:
def __init__(self):
print('1 init~~~~~~~~~~~~')
time.sleep(5)
print('2 init over~~~~~~~~')
def __enter__(self):
print('3 enter~~~~~~~~~~')
def __exit__(self, exc_type, exc_val, exc_tb):
print('6 exit~~~~~~~~~')
with Point(): #with 操作实例的enter
print('4 with block start **********')
time.sleep(5)
1/0
import sys
sys.exit(100)
print('5 with block end **********')
#上下文有两个魔术方法, __enter__和__exit__
print(7,'=' * 30)
- enter和exit照常运行,上下文管理是安全的
with语句
- with语法,会调用with后对象的__ enter __ 方法,如果有as,则将该方法的返回值赋值给as子句的变量
方法的参数
- __ enter __ 方法没有其他参数
- __ exit __ 方法有三个参数:
- exc_type, _exc_value, traceback
- 三个参数都与异常有关
- 如果该上下文退出时没有异常,三个参数都为None
- 如果有异常,参数意义如下:
- exc_type:异常类型
- exc_value:异常值
- traceback:异常的追踪信息
- __ exit __ :该方法返回一个等效True的值,则压制异常;否则继续抛出异常