魔术方法
特殊属性
查看属性
- 方法:
__dir__
- 意义:
返回类或者对象的所有成员名称列表
dir()
函数操作实例就是调用__dir__()
如果dir([obj])
参数obj包含方法__dir__()
,该方法将被调用。如果参数obj不包含__dir__()
,该方法将最大限度地收集属性信息
dir(obj)
对于不同类型的对象obj具有不同的行为:
- 如果对象是模块对象,返回的列表包含模块的属性名和变量名
- 如果对象是类型或者说是类对象,返回的列表包含类的属性名,及它的祖先类的属性名
- 如果是类的实例
- 有
__dir__
方法,返回可迭代对象的返回值 - 没有
__dir__
方法,则尽可能收集实例的属性名、类的属性和祖先类的属性名
- 有
- 如果obj不写,返回列表包含内容不同
- 在模块中,返回模块的属性和变量名
- 在函数中,返回本地作用域的变量名
- 在方法中,返回本地作用域的变量名
# animal.py
class Animal:
x = 123
def __init__(self, name):
self._name = name
self.__age = 10
self.weight = 20
print('animal Module\'s name = {}'.format(dir())) # 模块的属性
# cat.py
import animal
from animal import Animal
class Cat(Animal):
x = 'cat'
y = 'abcd'
class Dog(Animal):
def __dir__(self):
return ['dog'] # 必须返回可迭代对象
print('-------------')
print(1, 'Current Module\'s names = {}'.format(dir())) # 模块名词空间内的属性
print(2, 'animal Module\'s names = {}'.format(dir(animal))) # 指定模块名词空间内的属性
print('=============')
print(3, "object's __dict__ = {}".format(sorted(object.__dict__.keys()))) # object的字典
print(4, "Animal's dir() = {}".format(dir(Animal))) # 类Animal的dir()
print(5, "Cat's dir() = {}".format(dir(Cat))) # 类Cat的dir()
print('~~~~~~~~~~~~~~')
tom = Cat('tom')
print(6, sorted(dir(tom))) # 实例tom的属性、Cat类及所有祖先类的属性
print(7, sorted(tom.__dir__())) # 同上
# dir()的等价近似如下,__dict__字典中几乎包括了所有属性
print(8, sorted(set(tom.__dict__.keys()) | set(Cat.__dict__.keys() | set(object.__dict__.keys()))))
print(9, "Dog'sdir = {}".format(dir(Dog)))
dog = Dog('snoppy')
print(10, dir(dog))
print(11, dog.__dict__)
执行# cat.py
结果如下/
animal Module's name = ['Animal', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
-------------
1 Current Module's names = ['Animal', 'Cat', 'Dog', '__annotations__', '__builtins__', '__cached__',\
'__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'animal']
2 animal Module's names = ['Animal', '__builtins__', '__cached__',\
'__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
=============
3 object's __dict__ = ['__class__', '__delattr__', '__dir__',\
'__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',\
'__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', \
'__sizeof__', '__str__', '__subclasshook__']
4 Animal's dir() = ['__class__', '__delattr__', '__dict__', \
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',\
'__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__',\
'__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__',\
'__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'x']
5 Cat's dir() = ['__class__', '__delattr__', '__dict__', '__dir__', \
'__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', \
'__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', \
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',\
'__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'x', 'y']
~~~~~~~~~~~~~~
6 ['_Animal__age', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',\
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',\
'__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__',\
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',\
'__str__', '__subclasshook__', '__weakref__', '_name', 'weight', 'x', 'y']
7 ['_Animal__age', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', \
'__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', \
'__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__',\
'__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', \
'__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_name', 'weight', 'x', 'y']
8 ['_Animal__age', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__',\
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',\
'__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__',\
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__'\
, '__str__', '__subclasshook__', '_name', 'weight', 'x', 'y']
9 Dog's dir = ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', \
'__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__',\
'__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', \
'__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',\
'__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'x']
10 ['dog']
11 {'_name': 'snoppy', '_Animal__age': 10, 'weight': 20}
- dir()的测试如下
class Person:
def show(self):
a = 100
t = int(a)
print(1, dir())
print(2, locals())
def test(a=50, b=100):
c = 150
print(3, dir())
print(4, locals())
Person().show()
test()
print(5, dir())
print(6, sorted(locals().keys()))
print(7, sorted(globals().keys()))
# 执行结果如下
1 ['a', 'self', 't']
2 {'t': 100, 'a': 100, 'self': <__main__.Person object at 0x00000178C74449B0>}
3 ['a', 'b', 'c']
4 {'c': 150, 'b': 100, 'a': 50}
5 ['Person', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'test']
6 ['Person', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'test']
7 ['Person', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'test']
内建函数
- locals() 返回当前作用域中的变量字典
- globals() 当前模块全局变量的字典
魔术方法
分类
- 创建、初始化与销毁
__new__
__init__
与__del__
- 可视化
- hash
- bool
- 运算符重载
- 容器和大小
- 可调用对象
- 上下文管理
- 反射
- 描述器
- 其他杂项
实例化
__new__
- 实例化一个对象
- 该方法需要返回一个值,如果该值不是
cls
的实例,则不会调用__init__
- 该方法永远都是静态方法
class A:
def __new__(cls, *args, **kwargs):
print(cls)
print(args)
print(kwargs)
# return super().__new__(cls)
# return 1
return None
def __init__(self, name):
self.name = name
a = A()
print(a)
# 执行结果
<class '__main__.A'>
()
{}
None
__new__
方法很少使用,即使创建了该方法,也会使用return super().__new__(cls)
基类object的__new__
方法来创建实例并返回
可视化
__str__
- str()函数、format()函数、print()函数调用,需要返回对象的字符串表达。如果没有定义,就去调用
__repr__
方法返回字符串表达,如果__repr__
没有定义,直接返回对象的内存地址信息
__repr__
- 内建函数repr()对一个对象获取字符串表达
- 调用
__repr__
方法返回字符串表达,如果__repr__
也没有定义,就直接返回object的定义,就是显示内存地址信息
__bytes__
- bytes()函数调用,返回一个对象的bytes表达,即返回bytes对象
class A:
def __init__(self, name, age=19):
self.name = name
self.age = age
def __repr__(self):
return 'repr: {},{}'.format(self.name, self.age)
def __str__(self):
return 'str: {},{}'.format(self.name, self.age)
def __bytes__(self):
# return "{} is {}".format(self.name, self.age).encode()
import json
return json.dumps(self.__dict__).encode()
print(A('tom'))
print('{}'.format(A('tom')))
print([A('tom')])
print([str(A('tom'))])
print(bytes(A('tom')))
# 执行结果
str: tom,19
str: tom,19
[repr: tom,19]
['str: tom,19']
b'{"name": "tom", "age": 19}'
hash
__hash__
- 内建函数
hash()
调用的返回值,返回一个整数,如果定义个方法该类的实例就可hash
print(hash(1))
print(hash(100000))
print(hash('tom'))
print(hash(('tom',)))
# 执行结果
1
100000
3667610206148835051
2658757694414170484
class A:
def __init__(self, name, age=18):
self.name = name
def __hash__(self):
return 1
def __repr__(self):
return self.name
print(hash(A('tom'))) # 1
print((A('tom'), A('tom'))) # (tom, tom)
print([A('tom'), A('tom')]) # [tom, tom]
print('~~~~~~~~~~~~~~~~~~')
a1 = A('tom')
a2 = A('tom')
s = {a1, a2}
print(s) # {tom, tom}
print(hash(a1), hash(a1)) # 1 1
t1 = ('tom',)
t2 = ('tom',)
print(t1 is t2) # False
print(t1 == t2) # True
print({t1,t2}, hash(t1), hash(t2)) # {('tom',)} 7607324171405129534 7607324171405129534
print({('tom',), ('tom',)}) # {('tom',)}
print({'tom', 'tom'}) # {'tom'}
上例中,A的实例放在set中,他们hash值是相同的,并没有去重
class A:
def __init__(self, name, age=18):
self.name = name
def __hash__(self):
return 1
def __eq__(self, other):
return self.name == other.name
def __repr__(self):
return self.name
print(hash(A('tom'))) # 1
print((A('tom'), A('tom'))) # (tom, tom)
print([A('tom'), A('tom')]) # [tom, tom]
s = {A('tom'), A('tom')}
print(s) # {tom}
__eq__
- 对应==操作符,判断2个对象是否相等,返回bool值
- 定义了这个方法,如果不提供
__hash__
方法,那么实例将不可hash
总结
- 对整数取哈希采用的是取模算法
- hash是看内容是否相同;set去重 ,哈希值相等且内容相同
__hash__
方法只是返回一个hash值作为set的key,但是去重
,还需要__eq__
来判断2个对象是否相等- hash值相等,只是hash冲突,并不能说明两个对象是相等的
- 因此,一般来说提供
__hash__
方法是为了作为set或者dict的key,如果去重
要同时提供__eq__
方法 去重
需要提供__eq__
方法
list类实例为什么不可hash?
- 源码中有一句
__hash__ = None
,也就是如果调用__hash__()
相当于None()
,一定报错 - 所有类都继承object,而这个类是具有
__hash__()
方法的,如果一个类不能被hash,就把__hash__
设置为None
functolls.lru_chche
使用到的functools.HashedSwq
类继承自list,为什么可hash?
_HashedSeq
类提供了__hash__
方法,这个方法实际上计算的是元组的hahs值
练习
设计二维坐标类Point,使其成为可hash类型,并比较2个坐标的实例是否相等
from collections import Hashable
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __hash__(self):
return hash((self.x,self.y))
def __eq__(self, other):
return self.x == other.x and self.y == other.y
p1 = Point(4, 5)
p2 = Point(4, 5)
print(hash(p1)) # 3713084879518070856
print(hash(p2)) # 3713084879518070856
print(p1 is p2) # False
print(p1 == p2) # True
print(hex(id(p1)),hex(id(p2))) # 0x1b8e5bc8278 0x1b8e5bc8400
print(set((p1, p2))) # {<__main__.Point object at 0x000001B8E5BC8278>}
print(isinstance(p1, Hashable)) # True
bool
__bool__
内建函数bool(),或者对象放在逻辑表达式的位置,调用这个函数返回布尔值
没有定义__bool__()
,就找__len__()
返回长度,非0为真
如果__len__()
没有定义,那么所有实例都返回真
class A:pass
print(bool(A())) # True
if A():
print('Real A') # Real A
class B:
def __bool__(self):
return False
print(bool(B)) # True
print(bool(B())) # False
if B():
print('Real B')
class C:
def __len__(self):
return 0
print(bool(C())) # False
if C():
print('Real C')
运算符重载
operator模块提供以下的特殊方法,可以将类的实例使用下面的操作符来操作
运算符 | 特殊方法 | 含义 |
---|---|---|
<,<=,==,>,>=,!= | __lt__ ,__le__ ,__eq__ , __gt__ ,__ge__ ,__ne__ | 比较运算符 |
+, -, *, /, %, //, **, divmod | __add__ ,__sub__ ,__mul__ ,__truediv__ ,__mod__ ,__floordiv__ ,__pow__ ,__divmod__ | 算数运算符, 移位、位运算也有对应的方法 |
+=, -=,*=,/=,%=,//=,**= | __iadd__ ,__isub__ ,__imul__ , __itruediv__ ,__impd__ ,__ifloordiv__ ,__ipow__ | in-place 就地修改 |
- 实现A类的2个实例相减
class A:
def __init__(self,name, age=18):
self.name = name
self.age = age
def __sub__(self, other):
return self.age - other.age
def __isub__(self, other): # 如果没有定义__isub__,则会调用__sub__
return A(self.name, self - other)
tom = A('tom')
jack = A('jack', 10)
print(tom - jack) # 8
print(jack - tom, jack.__sub__(tom)) # -8 -8
print(id(tom)) 2333399746152
tom -= jack
print(tom.age, id(tom)) # 8 2333399746320
__isub__
方法定义,一般会in-place就地来修改自身- 如果没有定义
__isub__
方法,则会调用__sub__
练习
完成Point类设计,实现判断点相等的方法,并完成向量的加法
A(X1,Y1) B(X2,Y2) , 则A+B=(X1+X2, Y1+Y2), A-B= (X1-X2, Y1-Y2)
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
def add(self,other):
return (self.x + other.x, self.y + other.y)
def __str__(self):
return '<Point: {},{}>'.format(self.x, self.y)
p1 = Point(1,1)
p2 = Point(1,1)
print(p1.add(p2)) # (2, 2)
# 运算符重载
print(p1 + p2) # <Point: 2,2>
print(p1 == p2) # True
运算符重载应用场景
- 往往是用面向对象实现的类,需要做大量的运算,而运算符是这种运算在数学上最常见的表达方式。例如,上例中的对+进行了运算符重载,实现了Point类的二元操作,重新定义为Point + Point
- 提供运算符重载,比直接提供加法方法要更加合适该领域内使用者的习惯
- int类,几乎实现了所有操作符,可以作为参考
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
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)
jack = Person('jack', 16)
print(tom > jack) # True
print(tom < jack) # False
print(tom >= jack) # True
print(tom <= jack) # False
print(tom == jack) # False
print(tom != jack) # True
__eq__
等于可以推断不等于
__gt__
大于可以推断小于
__ge__
大于等于可以推断小于等于
容器相关方法
运算符 | 意义 |
---|---|
__len__ | 内建函数len() ,返回对象的长度(>=0的整数),如果把对象当做容器类型看,就如同list或者dictbool() 函数调用的时候,如果没有__bool() 方法,则会看__len()__ 方法是否存在,存在返回非0为真 |
__iter__ | 迭代容器时,调用,返回一个新的迭代器对象 |
__contains__ | in成员运算符,没有实现,就调用__iter__ 方法遍历 |
__getitem__ | 实现self[key] 访问。序列对象,key接受整数位索引,或者切片对于set和dict,key为hashable。key不存在引发KeyError异常 |
__setitem__ | 和__getitem__ 的访问类似,是设置值的方法 |
__missing__ | 字典或其子类使用__getitem__() 调用时,key不存在执行该方法 |
class A(dict):
def __missing__(self, key):
print('Missing key :', key)
return 0
a = A()
print(a['k'])
# 执行结果
Missing key : k
0
练习
将购物车类改造成方便操作的容器类
class Cart:
def __init__(self):
self.items = []
def __len__(self):
return len(self.items)
def additem(self, item):
self.items.append(item)
def __iter__(self):
# yield from self.items
return iter(self.items)
def __getitem__(self, index): # 索引访问
return self.items[index]
def __setitem__(self, key, value): # 索引赋值
self.items[key] = value
def __str__(self):
return str(self.items)
def __add__(self, other):
self.items.append(other)
return self
cart = Cart()
cart.additem(1)
cart.additem('abc')
cart.additem(3)
# 长度、bool
print(len(cart)) # 3
print(bool(cart)) # True
# 迭代
for x in cart:
print(x) # 1 abc 3
# in
print(3 in cart) True
print(2 in cart) False
# 索引操作
print(cart[1]) abc
cart[1] = 'xyz'
print(cart) # [1, 'xyz', 3]
# 链式编程实现加法
print(cart + 4 + 5 + 6) # [1, 'xyz', 3, 4, 5, 6]
print(cart.__add__(17).__add__(18)) # [1, 'xyz', 3, 4, 5, 6, 17, 18]
可调用对象
Python中一切皆对象,函数也不例外
def foo():
print(foo.__module__, foo.__name__)
foo() # __main__ foo
# 等价于
foo.__call__() # __main__ foo
- 函数即对象,对象foo加上(),就是调用此函数对象的
__call__()
方法
可调用对象
方法 | 意义 |
---|---|
__call__ | 类中定义一个该方法,实例就可以像函数一样调用 |
可调用对象,定义一个类,并实例化得到其实例,将实例像函数一样调用
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __call__(self, *args, **kwargs):
return "<Point {}:{}>".format(self.x, self.y)
p = Point(4, 5)
print(p)
print(p())
# 累加
class Adder:
def __call__(self, *args):
ret = 0
for x in args:
ret += x
self.ret = ret
return ret
adder = Adder()
print(adder(4, 5, 6))
print(adder.ret)
# 执行结果
<__main__.Point object at 0x000001B1A49F8630>
<Point 4:5>
15
15
练习
定义一个斐波那契数列的类,方便调用,计算第n项
增加迭代的方法、返回容器长度、支持索引的方法
class Fib:
def __init__(self):
self.items = [0, 1, 1]
def __call__(self, index):
if index < 0:
raise IndexError('Wrong Index')
if index < len(self.items):
return self.items[index]
for i in range(len(self.items), index+1):
self.items.append(self.items[i-1] + self.items[i-2])
return self.items[index]
print(Fib()(101)) # 573147844013817084101
在上例基础上,增加迭代的方法、返回容器长度、支持索引的方法
class Fib:
def __init__(self):
self.items = [0, 1, 1]
def __call__(self, index):
return self[index]
def __iter__(self):
return iter(self.items)
def __len__(self):
return len(self.items)
def __getitem__(self, index):
if index < 0:
raise IndexError('Wrong Index')
if index < len(self.items):
return self.items[index]
for i in range(len(self.items), index+1):
self.items.append(self.items[i-1] + self.items[i-2])
return self.items[index]
def __str__(self):
return str(self.items)
__repr__ = __str__
fib = Fib()
print(fib(5), len(fib)) # 5 6
print(fib(10), len(fib)) # 55 11
for x in fib:
print(x)
# 0
# 1
# 1
# 2
# 3
# 5
# 8
# 13
# 21
# 34
# 55
print(fib[5], fib[6]) # 5 8
可以看出使用类来实现斐波那契数列也是非常好的实现,还可以缓存数据,便于检索