查看属性 dir() __dir__
实例化__new__
实例化对象的本质
可视化__str__
__repr__
__bytes__
类实例化可视化的本质
哈希 hash __hash__
可哈希的本质
bool __bool__
等效 True,False 的本质
运算符重载 __add__
__gt__
__iadd__
等
容器化 __len__
__iter__
__contains__
__getitem__
__setitem__
__missing__
可调用对象 __call__
我们已经认识的有__init__
__del__
__name__
、__class__
、__doc__
、__mro__
、__dict__
、__bases__
等,请查看之前的学习笔记
查看属性dir()
- dir()返回的是列表
- 结论1:dir()在模块内,返回模块的属性和变量名
- 结论2:在函数 / 方法内,返回本地作用域的变量名
验证示例
# 例1:认识dir(),在t1.py中
class Animal:
def __init__(self, name):
self.name = name
x = 100
print('dir in module t1', dir()) # Animal,x
------------------------------------
dir in module t1 ['Animal', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'x']
结论 1:在模块内,返回模块的属性和变量名
- locals() 返回当前作用域中的变量字典
- globals() 返回当前模块全局变量的字典
例2:在t2.py中导入模块t1
import t1
print(1, "t1's dir():", dir()) # t1等
# print(t1.Animal,t1.x)
from t1 import Animal
print(2, "t1 animal dir():", dir()) # Animal, t1等
# print(Animal,t1.Animal,t1.x)
# print(x) # 报错
def show_dir(a=1, b=2): # 函数中
c = 100
print(3, "show_dir_func's dir():", dir()) # a,b,c
print(3.1, "locals() equels show_dir_func's dir() :", sorted(locals().keys())) # locals()
print(3.2, "gloals() equels t2's dir():", sorted(globals().keys())) # globals()
class Dog(Animal):
x = 1
y = 2
def __dir__(self):
return ['bingo'] # 必须返回可迭代对象
class Cat(Animal):
def __init__(self, name):
super().__init__(name)
self.a = 100
def show_dir(self, age = 100): # 类方法中
print(4, "show_dir_func's dir() in class:", dir()) # self,age
show_dir()
Cat('poppy').show_dir()
print(5, "t2's dir():", dir()) # t2模块名词空间内的属性,包含Animal,Cat,Dog,t1,test等
print(6, "locals() equels t2's dir():", sorted(locals().keys()))
print(7, "globals() equels t2's dir():", sorted(globals().keys()))
----------------------------------------------------------------------------
dir in module t1 ['Animal', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'x']
1 t1's dir(): ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 't1']
2 t1 animal dir(): ['Animal', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 't1']
3 show_dir_func's dir(): ['a', 'b', 'c']
3.1 locals() equels show_dir_func's dir() : ['a', 'b', 'c']
3.2 gloals() equels t2's dir(): ['Animal', 'Cat', 'Dog', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'show_dir', 't1']
4 show_dir_func's dir() in class: ['age', 'self']
5 t2's dir(): ['Animal', 'Cat', 'Dog', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'show_dir', 't1']
6 locals() equels t2's dir(): ['Animal', 'Cat', 'Dog', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'show_dir', 't1']
7 globals() equels t2's dir(): ['Animal', 'Cat', 'Dog', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'show_dir', 't1']
结论 2:在函数中,返回本地作用域的变量名;在类方法中返回本地作用域的变量名
dir(obj)
和__dir__
- 如果 obj 是模块对象,返回的列表包含模块的属性名和变量名
- 如果 obj 是类,返回的列表包含类的属性名和父类的属性名(不论是否包含
__dir__
) - 如果 obj 是类的实例,且类中没有
__dir__
方法,返回的列表包含类的属性名和父类的属性名及类实例的属性名 - 如果 obj 是类的实例,且类中有
__dir__
方法,则返回可迭代对象的返回值
# 例3,在例2的基础上
print(1.1, dir(t1)) # 指定模块名词空间内的属性,返回列表包含模块的属性名和变量名
bingo = Dog('bingo')
miao = Cat('miao')
print("class's dir(obj):", dir(Cat)) # 返回列表包含类和父类的属性名
print("class instance miao's dir(obj):", dir(miao)) # 返回列表包含类,父类,类实例的属性名
print("class Dog's dir(obj):", dir(Dog)) # 不论是否包含__dir__
print("class instance bingo(include __dir__)'s dir(obj):", dir(bingo)) # 返回可迭代对象,等价于print(bingo.__dir__())
------------------------------------------------------------------------
dir in module t1 ['Animal', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'x']
1.1 ['Animal', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'x']
class's dir(obj): ['__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__']
class instance miao's dir(obj): ['__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__', 'a', 'name']
class Dog's dir(obj): ['__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']
class instance bingo(include __dir__)'s dir(obj): ['bingo']
# 例4
print(1, "class's dir(obj):", dir(Cat))
print(2, "class's dir(obj):", sorted(Cat.__dict__.keys() | set(Animal.__dict__.keys()) | set(object.__dict__.keys()))) # 字典的keys是类set对象,不强转也可
print(3, "class instance miao's dir(obj):", dir(miao))
print(4, "class instance miao's dir(obj):",sorted(set(dir(Cat)) | set(miao.__dict__.keys())))
dir()和__dict__
的区别
只要是有属性的数据对象(不一定是面向对象的对象实例,而是指具有数据类型的数据对象),都可以通过__dict__
和dir()来显示数据对象的相关属性。
区别:
__dict__
可以看作是数据对象的名称空间,所以只包含自己的属性,且可以直接增、删、改、查__dict__,是一个字典,键为属性名,值为属性值- dir()是Python提供的一个API函数,返回的是list,dir()函数会显示一个对象的所有属性(包括从父类中继承的属性)。
- dir()包括
__dict__
中的属性,__dict__
是dir()的子集 - 并不是所有对象都拥有
__dict__
属性。许多内建类型就没有__dict__
属性,如list,此时就需要用dir()来列出对象的所有属性
实例化__new__
- 实例化一个对象
- 该方法需要返回一个值,如果该值不是 cls 实例,则不会调用
__init__
- 该方法是静态方法 staticmethod,
def __new__(cls, *args, **kwargs):
#例1: 如果返回值不是cls实例,则不会调用__init__
class Person:
def __new__(cls, *args, **kwargs):
return 'abc'
def __init__(self, name):
self.name = name
a = Person() # name不给都不会报错
print(a)
------------------
abc
实例化对象的本质
# 例2:理解实例化过程
class Person:
def __new__(cls, *args, **kwargs):
print(1, cls)
print(2, args)
print(3, kwargs)
# cls.age = 100 # 对类增加属性,不建议
ret = super().__new__(cls)
ret.age = 18 # 对实例增加属性,不建议
return ret
# return super().__new__(cls) # 建议这样做即可,只能调用父类的__new__,其实该魔术方法用的少,这里仅用于理解实例化过程
def __init__(self, name):
self.name = name
a = Person('jerry')
print(4, Person.__dict__)
print(5, a)
print(6, a.__dict__)
总结:__new__
和__init__
这两个方法合起来类似于 Java 和 C++ 等程序设计语言中的构造方法,用于创建并初始化一个对象,实例化(创建)一个类对象时,最先被调用的是__new__
方法来创建完对象并将该对象传递给__init__
方法中的 self 参数,然后初始化对象状态。这两个方法都是在实例化对象时被自动调用的,不需要程序显式调用
单例模式
单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在
当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。比如,某个程序的配置信息存放在一个文件中,客户端通过一个 Appconfig 的类来读取配置文件的信息。如果在程序运行期间,有很多地方都在使用配置文件的内容,也就说,很多地方都需要创建Appconfig 对象的实例,这样子就会导致系统中存在很多 Appconfig 的实例对象,而这种会严重浪费内存资源。尤其在配置文件内容很多的情况下。事实上 ,类似 Appconfig 这样的类我们希望在程序运行期间只存在一个实例对象
- 使用
__new__
方法实现
class MySingleton:
__obj = None
__init_flag = True
def __new__(cls, *args, **kwargs):
if not cls.__obj:
cls.__obj = super().__new__(cls)
# cls.__obj = object.__new__(cls)
return cls.__obj
def __init__(self, name):
if MySingleton.__init_flag:
print('init....')
self.name = name
MySingleton.__init_flag = False
a = MySingleton('a')
b = MySingleton('b')
print(a)
print(b)
###############输出结果##############
init....
<__main__.MySingleton object at 0x10a479bb0>
<__main__.MySingleton object at 0x10a479bb0>
可视化
- 根据需要调用的魔术方法,按照mro顺序去查找对应的魔术方法
__str__
,__repr__
,__bytes__
__str__
- str() 函数、format() 函数、print() 函数调用,需要返回对象的字符串表达
- 如果没有定义,就去调用
__repr__
方法返回字符串表达,如果__repr__
没有定义,就直接返回对象的内存地址信息
__repr__
- 内建函数 repr() 对一个对象获取字符串表达
- 调用
__repr__
方法返回字符串表达,如果__repr__
也没有定义,就直接返回 object 的定义就是显示内存地址信息 - 根据以上结论,有时为了方便起见,如果需要可视化,在类中只定义
__repr__
(重写,覆盖父类的方法)
__bytes__
- bytes() 函数调用,返回一个对象的 bytes 表达,即返回 bytes 对象
验证示例
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# 尝试注释掉某个函数,查看调用情况,来验证以上结论
def __str__(self):
return 'str : {}_{}'.format(self.name, self.age)
def __repr__(self):
return 'repr : {}_{}'.format(self.name, self.age)
def __bytes__(self):
return 'bytes : {}_{}'.format(self.name, self.age).encode()
j = Person('jerry', 18)
print(j)
print('{}'.format(j))
print(str(j))
print(repr(j))
print([j]) # 调用__repr__
print(bytes(j))
哈希hash
认识hash
print(hash(1), hash(100), hash(1000000)) # 整数的hash算法为%2**64(与超大数取模)
print(hash('abc'))
print(hash('abc')) # 幂等性
print(hash(('abc',)))
----------------------------
1 100 1000000
8554086053665499051
8554086053665499051
6000808437421665588
可哈希的本质
__hash__
:内建函数 hash() 的返回值- 如果类中定义了这个方法,则该类可 hash
# 例2:类中定义了__hash__
class Person:
def __init__(self, name, age = 18):
self.name = name
self.age = age
def __hash__(self):
# return 'abc' # TypeError: __hash__ method should return an integer
return 1
def __repr__(self):
return '<Person: {} {}>'.format(self.name, self.age)
j1 = Person('jerry')
j2 = Person('jerry')
print(Person('jerry'), hash(Person('jerry')), sep='\n')
---------------------------------------------------
<Person: jerry 18>
1
set去重的本质
- set中的元素必须可hash,且去重如
print({1, 1}) # 结果为{1} 即能够去重
- set中内容相同且hash值相同,则去重
# 接上例2,验证set去重的本质
print(j1 is j2) # 内存地址是否相同id(j1), id(j2)
print(j1 == j2) # 内容是否相同,Person类没有==调用的魔术方法,调用父类object的,查看源码object的==就是is判断内存地址
print({j1, j2}) # 那仅相同hash值的Person实例呢?不去重,因此去重不仅需要hash值相等而且需要内容相同
----------------------------------------------------------------
{1}
False
False
{<Person: jerry 18>, <Person: jerry 18>}
__hash__
方法作为返回一个 hash 值作为 set 的 key,但去重还需要__eq__
来判断两个对象是否相等,内容相同且 hash 值相同,则去重- hash 值相同,只是 hash 冲突,不能说明这两个对象是相等的
__eq__
对应==
操作符,判断两个对象是否相等,返回 bool
# 例3:在例2 Person类中加入魔术方法__eq__使得j1,j2内容相等(==)
class Person:
def __init__(self, name, age = 18):
self.name = name
self.age = age
def __hash__(self):
# return 'abc' # TypeError: __hash__ method should return an integer
return 1
# import random
# return random.randint() # 构造返回不同hash值
def __repr__(self):
return '<Person: {} {}>'.format(self.name, self.age)
def __eq__(self, other):
return self.age == other.age and self.name == other.name
j1 = Person('jerry')
j2 = Person('jerry')
print(j1 == j2)
print(hash(j1) == hash(j2))
# 结论:内容相同且hash值相同,则去重
print({j1, j2})
---------------------------------------------
True
True
{<Person: jerry 18>}
- list 为什么不可 hash 呢?,查看list源码,list 类中
__hash__ = None
# 例4:构造不可hash的类
import collections
class Person:
def __init__(self, name, age = 18):
self.name = name
self.age = age
__hash__ = None
j = Person('jerry')
# print(hash(j)) # TypeError: unhashable type: 'Person'
print(isinstance(j,collections.Hashable)) # 判断是否可hash
----------
False
bool和__bool__
- 内建函数 bool(),返回布尔值
- 如果没有定义
__bool__()
则查找__len__()
,对应的类实例,如果__len__()
返回长度非 0 则为真 - 如果
__len__()
也没定义,则所有的实例返回为真 - 为什么空列表等效为 False 呢,list 类源码,list 未定义
__bool__
,仅定义了__len__:Return len(self)
。同理,空字典,空字符串,空元组,空集合都等效为 False
class A:
pass
print(bool(A)) # 类为真
print(bool(A())) # __bool__和__len__均未定义,则实例都为真
class B:
def __bool__(self):
# return 1 # __bool__ should return bool, returned int
# return bool(1)
return False
print(bool(B))
print(bool(B()))
class C:
def __len__(self):
return 100
# return 0
print(bool(C))
print(bool(C()))
print(len(C()))
print(bool([]))
if not []: # 相当于隐式调用了bool([]),查看list类源码,list未定义__bool__,仅定义了__len__:Return len(self)
print('~~~~~~~~~')
---------------------------------
True
True
True
False
True
True
100
False
~~~~~~~~~
运算符重载
运算符重载也就是覆盖父类的相关魔术方法
运算符 | 方法 | 备注 |
---|---|---|
<, <=, ==, >, >=, != |
__lt__ , __le__ , __eq__ , __gt__ , __ge__ , __ne__ |
比较运算符 |
+, -, *, /, %, //, **, divmod |
divmod__add__ , __sub__ , __mul__ , __truediv__ , __mod__ , __floordiv__ , __pow__ , __divmod__ |
算数运算符 |
+=, -=, *=, /=, %=, //=, **= |
__iadd__ , __isub__ , __imul__ , __itruediv__ , __imod__ , __ifloordiv__ , __ipow__ |
inplace_ 就地修改 |
- 如果没有定义
__ixxx__
方法,则会调用__xxx__
,例如没有定义__iadd__
方法,则会查找被调用__add__
方法
l1 = [1]
print(id(l1))
l1 = l1 + [2]
print(id(l1))
l2 = [1]
print(id(l2))
l2 += [2] # 就地修改,内存地址不变
print(id(l2))
class Person:
def __init__(self, age):
self.age = age
def __add__(self, other):
return self.age + other.age
def __iadd__(self, other):
self.age += other.age
return self # 一般建议同类型相加减,返回同类型
j = Person(20)
t = Person(18)
jt = j + t # j.__add__(t)
print(jt)
print(id(j))
j += t # j.__iadd__(t)
print(id(j)) # 内存地址不变
- 应用场景:用面向对象实现的类,需要做大量的运算,而运算符是这种运算在数学上最常见的表达方式。提供运算符重载,比直接提供加法方法要更加适合该领域内使用者的习惯。
__lt__
,__le__
,__eq__
,__ne__
,__gt__
,__ge__
是比较大小必须实现的方法,但是 “全部写完太麻烦”(其实只需要实现__eq__
、__gt__
、__ge__
,或另外三个(为其反),不需要实现),使用@functools.total_ordering
装饰器就可以 “大大” 简化代码- 但是要求
__eq__
必须实现,其它方法__lt__
,__le__
,__gt__
,__ge__
实现其一,也就是说其实至少需要实现两魔术方法,其实也没多少简化呀 - 仅做了解即可
from functools import total_ordering
@total_ordering
class A:
def __init__(self, value):
self.value = value
def __eq__(self, other): # 只实现该方法则报错ValueError: must define at least one ordering operation: < > <= >=
return self.value == other.value
def __gt__(self, other):
return self.value > other.value
a = A(18)
b = A(20)
print(a == b, a != b, a > b, a < b, a >= b, a <= b)
--------------------------------------------------------
False True False True False True
容器化
容器化即将类伪装成一个容器,需要实现一些相关的魔术方法
方法 | 意义 | 备注 |
---|---|---|
__len__ |
1. 内建函数 len() 调用,返回对象的长度(>=0 的整数)2. bool() 函数调用的时,如果没有__bool__() 方法,则会查找__len__() 方法是否存在,存在返回非 0 则为真(可参考如上 bool 章节的介绍) |
** |
__iter__ |
迭代容器时调用,返回一个新的迭代器对象 | ** |
__contains__ |
in 成员运算符,若没有实现,就调用__iter__ 方法遍历 |
* |
__getitem__ |
1. 实现self[key] (索引访问)访问。序列化对象,key 接受整数为索引,或者切片。2. 对于 set 和 dict,key 必须为 hashable。key 不存在则引发 KeyError 异常 |
** |
__setitem__ |
和__getitem__ 的访问类似,是设置值的方法 |
** |
__missing__ |
字典或其子类使用__getitem__() 调用时,key 不存在时执行该方法 |
# 先看一下__missing__,用于字典,有get和setdefault方法可替代
class A(dict):
def __missing__(self, key):
value = 1000
self.__dict__[key] = value
return value
a = A()
print(a['abc'])
print(a.__dict__)
------------------
1000
{'abc': 1000}
伪装成一个容器,那就希望可以像如下方式调用
a = A()
a.add(1)
a.add(2)
# a.add(1).add(2)
# a + 1 + 2
a[0]
a[1] = 100
for i in a:
print(1 in a)
- 利用以上魔术方法,来容器化一个类
class A:
def __init__(self):
self.items = [] #用list来伪装
def add(self, value):
self.items.append(value)
return self #可实现链式相加
def __add__(self, other):
return self.add(other)
def __len__(self):
return len(self.items)
def __getitem__(self, item): #这里的item即list的index
# print(item)
return self.items[item]
def __setitem__(self, key, value): #这里的key即list的index
print(key, value)
self.items[key] = value
def __iter__(self):
# return 123 #报错,必须返回迭代器iterator TypeError: iter() returned non-iterator of type 'int'
# return iter(self.items)
# for i in self.items:
# yield i
yield from self.items
def __repr__(self):
return 'a: class {} {}'.format(self.__class__.__name__, self.items)
a = A()
a.add(1)
a.add(2)
a.add(3).add(4)
a + 5 + 6
print(a)
print(len(a))
print(bool(a))
print(a[4])
a[0] = 100
print(a)
for i in a:
print(i)
print(7 in a) # __contains__可不实现,若未实现,查找__iter__
--------------------------------------------------------------
a: class A [1, 2, 3, 4, 5, 6]
6
True
5
0 100
a: class A [100, 2, 3, 4, 5, 6]
100
2
3
4
5
6
False
可迭代对象、迭代器、生成器的区别本质
- 可参看之前的文章可迭代对象、迭代器、生成器的区别
可调用对象__call__
- 函数可调用
def fn():
print('fn call')
print(callable(fn))
fn()
fn.__call__()
----------------
True
fn call
fn call
- 可调用对象,在类中定义该方法,类的实例可像函数一样调用
class A:
def __call__(self, *args, **kwargs):
print('A instance call')
a = A()
print(callable(a))
a() # a为A的实例,a()即实例的调用
a.__call__()
-------------------
True
A instance call
A instance call
# 实现一个累加器
class Adder():
def __init__(self):
self.result = 0
def __call__(self, *args, **kwargs):
# print(args) # 元组
# print(**kwargs) # 字典
for i in args:
self.result += i
return self.result
add = Adder()
print(add(1,2,3)) #add实例为可调用对象
print(add.result)
-------------------
6
6
参考
- magedu
- python的dir()和__dict__属性的区别