查看属性 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

参考