Python 魔术方法 详解

Python 魔术方法

  • 1、特殊属性
  • 2、查看属性
    • 2.1 示例 1
  • 3、魔术方法
    • 3.1 实例化 `__new__`
    • 3.2 可视化
    • 3.3 `hash`
    • 3.4 `__bool__`
    • 3.5 运算符重载
      • 3.5.1 运算符重载应用场景 1
      • 3.5.2 运算符重载应用场景 2
    • 3.6 容器相关魔术方法
    • 3.7 可调用对象
    • 3.7.1 斐波那契数列

1、特殊属性

Python 魔术方法 详解_第1张图片

2、查看属性

  • __dir__ 返回类或者对象的所有成员名称列表

  • dir()函数操作实例就是调用__dir__()

  • 如果dir([obj])参数obj包含方法__dir__(),该方法将被调用

  • 如果参数obj不包含__dir__(),该方法将最大限度地收集属性信息

  • dir(obj) 对于不同类型的对象obj具有不同的行为:

    1 如果对象是模块对象,返回的列表包含模块的属性名和变量名

    2 如果对象是类型或者说是类对象,返回的列表包含类的属性名,及它的祖先类的属性名

    3 如果是类的实例
    __dir__方法,返回可迭代对象的返回值
    没有 __dir__方法,则尽可能收集实例的属性名、类的属性和祖先类的属性名

    4 如果obj不写,返回列表包含内容不同
    在模块中,返回模块的属性和变量名
    在函数中,返回本地作用域的变量名
    在方法中,返回本地作用域的变量名

  • locals() 返回当前作用域中的变量字典

  • globals() 当前模块全局变量的字典

2.1 示例 1

class Animal:
    x = 123
    def __init__(self, name):
        self._name = name
        self.__age = 10
        self.weight = 20
dir(Animal) 
['__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']
dir(Animal('Sybil'))
['_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']
class Cat(Animal): 
    y = 'abc'

dir(Cat)
['__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']

3、魔术方法

3.1 实例化 __new__

  • 1 实例化一个对象;构造实例的时候,先调用__new__方法;返回的是一个实例,然后继续调用__init__方法,进行出厂配置
  • 2 该方法需要返回一个值,如果该值不是cls的实例,则不会调用__init__
  • 3 该方法永远都是静态方法,第一个参数cls需要手动输入,不会自动注入任何参数
  • 4 __init__方法很少使用,即使创建了该方法,也会使用return super().__new__(cls)基类object__new__方法来创建实例并返回
class A:
    def __new__(cls, *args, **kwargs):
        print('new')
        print(cls)
        print(args)
        print(kwargs)
        return super().__new__(cls)
        # return None
    
    def __init__(self, name):
        print('init')
        self.name = name
        
a = A('name')
new
<class '__main__.A'>
('name',)
{}
init

3.2 可视化

  • __str__:str() format() print()函数调用,需要返回对象的字符串表达。如果没有定义,就去调用__repr__方法返回字符串表达。如果__repr__没有定义,就直接返回对象的内存地址
  • __repr__:内建函数repr()对一个对象获取字符串表达。如果__repr__没有定义,就直接返回对象的内存地址
  • __bytes__:bytes()函数调用,返回一个对象的bytes表达,即返回bytes对象
  • 注意不能通过判断是否带引号来判断输出值的类型,类型判断要使用type或者isinstance
  • 直接作用对象 调用 str,但是间接作用也可以使用 str
  • 间接作用 优先调用 repr 例如:[] ()
  • 如果没定义repr,就会调基类的repr
  • 不能根据打印形式来判断数据类型,打印出来的全是给人看的,并不用真实数据类型
class A:
    def __init__(self, name, age=18):
        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'))  # str: tom,18  # print函数使用__str__
print('{}'.format(A('tom')))  # str: tom,18  # format函数使用__str__
print(repr(A('tom')))  # repr: tom,18
print([A('tom')])  # [repr: tom,18] # []使用__str__,但其内部使用__repr__
print([str(A('tom'))])  # ['str: tom,18']  # []使用__str__,其中的元素使用str()函数也带哦用__str__
print('str:a,1')  # str:a,1  # 字符串直接输出没有引号
s = '1'
print(s)  # 1
s1 = 'a'
print(s1)  # a
print([s1], (s,))  # ['a'] ('1',)  # 字符串在基本数据类型内部输出有引号
print({s, 'a'})  # {'1', 'a'}
print(bytes(A('tom')))  # b'tom is 18'

3.3 hash

  • __hash__:1 内建函数hash()调用的返回值,返回一个整数。2 如果定义这个方法该类的实例就可hash
  • __eq__:1 对应==操作符,判断2个对象是否相等,返回bool值。2 定义了这个方法,如果不提供__hash__方法,那么实例将不可hash
  • __hash__:方法只是返回一个hash值作为setkey,但是去重,还需要__eq__来判断2个对象是否相等。
  • 注意事项:hash值相等,只是hash冲突,不能说明两个对象是相等的。因此,一般来说提供__hash__方法是为了作为set或者dictkey,如果去重,要同时提供__eq__方法。
  • 不可hash对象isinstance(p1, collections.Hashable)一定为False
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}
print((A('tom'), A('tom')))  # (tom, tom)
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

    def __str__(self):
        return "Point:({},{})".format(self.x, self.y)

    def __repr__(self):  # set 里面显示的时候调用的时 repr
        return "Point:({},{})".format(self.x, self.y)

p1 = Point(4, 5)
p2 = Point(4, 5)
print(p1, p2)
print(hash(p1))
print(hash(p2))
print(p1 is p2)
print(p1 == p2)
print(hex(id(p1)), hex(id(p2)))
print(set((p1, p2)))
print(isinstance(p1, Hashable))
Point:(4,5) Point:(4,5)
-1009709641759730766
-1009709641759730766
False
True
0x1eaf14f7f10 0x1eaf14c98b0
{Point:(4,5)}
True

3.4 __bool__

  • 1 内建函数 bool(),或者对象放在逻辑表达式的位置,调用这个函数返回布尔值
  • 2 没有定义 __bool__(),就找__len__()返回长度,非0为真
  • 3 如果__len__()也没有定义,那么所有实例都返回真
class A: pass
print(1, bool(A()))

if A():
    print(2, 'Real A')
    
class B:
    def __bool__(self):
        return False

print(3, bool(B))
print(4, bool(B()))

if B():
    print(5, 'Real B')

class C:
    def __len__(self):
        return 0

print(6, bool(C()))
if not C():
    print(7, 'False C')
print(8, bool(C))
1 True
2 Real A
3 True
4 False
6 False
7 False C
8 True

3.5 运算符重载

  • Python中运算符重载的特殊函数

Python 魔术方法 详解_第2张图片

  • Python中的比较运算符重载

Python 魔术方法 详解_第3张图片

  • 其它运算符

在这里插入图片描述

# 就地修改
class A:
    def __init__(self ,age):
        self.age = age
    
    def __sub__(self, other):
        return self.age - other.age
    
    def __isub__(self, other):
        self.age -= other.age  # int的-=和实例的-=不一样,不会递归
        return self
    
a1 = A(20)
a2 = A(12)
print(id(a1), a1.age, a1)
a1 -= a2
print(id(a1), a1.age, a1)
# 2761171921408 20 <__main__.A object at 0x00000282E2ABEA00>
# 2761171921408 8 <__main__.A object at 0x00000282E2ABEA00>

3.5.1 运算符重载应用场景 1

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__
        self.age = self.age - other.age  # __isub__方法定义,一般会in-place就地来修改自身
        return self
        # return A(self.name, self - other)
    
    def __str__(self):
        return '[name = {}, age = {}]'.format(self.name, self.age)
        
tom = A('tom')
jerry = A('jerry', 16)
print(tom - jerry)  # 2
print(jerry - tom, jerry.__sub__(tom))  # -2 -2
print(id(tom))  # 2457466099360
tom -= jerry
print(tom.age, id(tom))  # 2 2457466099360
print(tom)  # [name = tom, age = 2]

3.5.2 运算符重载应用场景 2

# 运算符重载应用场景
# 比较大小需要实现6种方法, __lt__、__le__、__eq__、__gt__、__ge__
# 一般只需要用三种方法就行:__eq__、__gt__、__ge__
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', 18)
jerry = Person('jerry', 20)

print(tom > jerry)   # False
print(tom >= jerry)  # False
print(tom != jerry)  # True
print(tom < jerry)   # True
print(tom <= jerry)  # True
print(tom == jerry)  # False

3.6 容器相关魔术方法

__len__
1 内建函数len(),返回对象的长度(>=0的整数)
  如果把对象当作容器类型看,就如同list或者dict
2 bool()函数调用的时候,如果没有__bool__()方法
  则会看__len__()方法是否存在,存在返回非0为真

__iter__
1 迭代容器时,调用,返回一个新的迭代器

__contains__
1 in成员运算符,没有实现,就调用__iter__方法遍历

__getitem__
1 实现self[key]访问。序列对象,key接受整数为索引,或为切片
2 对于setdict,key为hashable。key不存在引发KeyError异常

__setitem__
1 和__getitem__的访问类似,是设置值的方法

__missing__
1 字典或其子类使用__getitem__()调用时,key不存在执行该方法
# 偷梁换柱
class A(dict):
    def __missing__(self, key):  # 字典及其子类才会有此属性
        print(key, 'missing ~~~~~~~~~~~~')
        value = 1000
        self.__dict__[key] = value
        return value

a = A()
print(a['t'])
print(a.__dict__)

# t missing ~~~~~~~~~~~~
# 1000
# {'t': 1000}
# 注意递归问题
class A:
    def __init__(self):
        self.items = []
    
    def add(self, item):
        self.items.append(item)
        return self
        
    def __setitem__(self, index, value):
        self.items[index] = value
        # self[index] = value   # 注意递归问题
    
    def __getitem__(self, index):  # 注意递归问题
        return self.items[index]
        # return self[index]
        
    def __iter__(self):
        yield from self.items
        # return iter(self.items)
    
a = A()
a.add(1).add(2).add(3)
print(a)
print(a[1])
# 模拟购物车类
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):
        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('a')
cart.additem('c')

print(cart)  # [1, 'a', 'c']
print(len(cart))  # 3
for x in cart:
    print(x, end = ' ')  # 1 a c 
print(3 in cart)  # False
print(1 in cart)  # True
print(cart[1])  # a
cart[1] = 'xyz'
print(cart)  # [1, 'xyz', 'c']
print(cart + 4 + 'aaa' + 'ppp')  # [1, 'xyz', 'c', 4, 'aaa', 'ppp']
print(cart.__add__('iii'))  # [1, 'xyz', 'c', 4, 'aaa', 'ppp', 'iii']
print(cart.items)  # [1, 'xyz', 'c', 4, 'aaa', 'ppp', 'iii']

3.7 可调用对象

Python中一切皆对象,函数也不例外。

__call__:类中定义一个该方法,实例就可以像函数一样调用

def foo():
    print(foo.__module__, foo.__name__)
    
foo()  # __main__ foo
# 等价于
foo.__call__()  # __main__ foo
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))  # 13

3.7.1 斐波那契数列

# 逐步优化
class Fib:
    def __init__(self):
        self.a = 1
        self.b = 1
    
    def __call__(self, index):
        if index < 0:
            raise IndexError()
        elif index == 0:
            return 0
        elif index < 3:
            return 1
        for i in range(index - 2):
            self.a, self.b = self.b, self.a + self.b
        return self.b
    
fib = Fib()
fib(35)  # 9227465
# 保存计算过的各个结果
class Fib2:
    def __init__(self):
        self.items = [0, 1, 1]        
        
    def __call__(self, index):
        if index < 0:
            raise IndexError()
        
        if index < len(self.items):
            return self.items[index]
        import time
        time.sleep(1)
        for i in range(len(self.items), index+1):
            self.items.append(self.items[i - 1] + self.items[i - 2])
        return self.items[index]
# 继续进行容器化
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), index+1):
            self.items.append(self.items[i-2] + self.items[i-1])
        return self.items[index]
    
    def __str__(self):
        return str(self.items)
    
    __repr__ = __str__
    
fib = Fib()
print(fib(5), len(fib))  # 5 6
print(fib.items)  # [0, 1, 1, 2, 3, 5]
print(fib(101))  # 573147844013817084101
print(fib[100] + fib[99])  # 573147844013817084101
print(fib[101])  # 573147844013817084101

你可能感兴趣的:(Python,python)