一篇掌握python魔法方法详解

本编研究下python的内置属性/魔法方法/魔法函数

这里说的内置属性,是指__xxx__的方法和属性,有的地方也称为魔法方法,叫法不一样。

本文概要

  • 1.__init____new__的顺序、使用?
  • 2.__getattribute__干嘛的?
  • 3.__call__作用,与callable内置函数有着怎样的关系呢?
  • 4.对象如何才能比较? __lt____gt__
  • 5.__getattr____setattr____delattr__何时触发?
  • 6.__add____iadd__区别,何时触发(++=)?什么时候会改变自身,什么时候会新建对象?
  • 7.__getitem____setitem____delitem__何时触发?
  • 8.迭代__iter____next__for-in循环的配合
  • 9.__mul____rmul____imul__何时触发?什么时候会改变自身,什么时候会新建对象?

如果上面几点问题都能回答上,那么可以跳过本篇文章了。本篇相关文章共三连弹。(端午节在家整理,有帮助记得点个👍,就是对我最大的肯定😘😘😘)

技巧:文章内容太多如何阅读?

  • 先看文章的目录(TOC),掌握文章整体脉络
  • 然后走马观花式,通篇阅读。如果文章有用,再细细研究;文章没用的直接忽略,找下篇。
    • 如果为了解决问题的,解决完了有时间再细读
    • 如果学习的,收藏起来,边学习,边敲代码实践 (别copy代码,自己敲)
  • 收藏完,下次用的时候,忘记如何使用了,到文章直接CTRL+F查找关键字

浩瀚的网络中,你我的相遇也是种缘分,看你天资聪慧、骨骼精奇,就送你一场大造化吧,能领悟多少就看你自己了。㊙传承之地🙇

1.常见魔法函数

常用专有属性说明触发方式
__init__构造初始化函数创建实例后,赋值时使用,在__new__
__new__生成实例所需属性创建实例时
__class__实例所在的类实例.__class__
__str__实例字符串表示,可读性print(类实例),如没实现,使用repr结果
__repr__实例字符串表示,准确性类实例 回车 或者 print(repr(类实例))
__del__析构del删除实例
__dict__实例自定义属性vars(实例.__dict__)
__doc__类文档,子类不继承help(类或实例)
__getattribute__属性访问拦截器访问实例属性时
__delattr__(s,name)删除name属性调用时
__gt__(self,other)判断self对象是否大于other对调用时
__setattr__(s,name,value)设置name属性调用时
__gt__(self,other)判断self对象是否大于other对象调用时
__lt__(slef,other)判断self对象是否小于other对象调用时
__ge__(slef,other)判断self对象是否大于或者等于other对象调用时
__le__(slef,other)判断self对象是否小于或者等于other对象调用时
__eq__(slef,other)判断self对象是否等于other对象调用时
__call__(self,\*args)把实例对象作为函数调用调用时

下面分别以object和list的内置属性(魔法方法)来讲解下,其他的有需要的可以留言补充。

2.object

使用dir()函数来可以打印出有哪些属性,下面会挑一些常用的演示

演示

class People:
    name: str

class MyTestCase(unittest.TestCase):
    def test_attr(self):
       print(dir(People))

源码坐标

如需要源码,可以查看。(关键代码都贴在文章里,不用看也行)

2-1.init

初始化时,调用__init__方法

演示

class People:
    name: str = 'iworkh'

    def __init__(self, name):
        print('call...__init__')
        self.name = name


class MyTestCase(unittest.TestCase):

    def test(self):
        people = People('沐雨云楼') # 创建对象时,调用__init__
        print(people.name)

2-2.new

__ new__ ()__ init__()之前被调用,用于生成实例对象.

利用这个方法和类属性的特性可以实现设计模式中的单例模式.单例模式是指创建唯一对象吗,单例模式设计的类只能实例化一个对象.

演示

class People:
    name: str = 'iworkh'

    def __new__(cls, *args, **kwargs):
        print('call...__new__')
        obj = object.__new__(cls)
        return obj

    def __init__(self, name):
        print('call...__init__')
        self.name = name


class MyTestCase(unittest.TestCase):

    def test(self):
        people = People('沐雨云楼')  # 创建对象先调用__new__,初始化时调用__init__
        print(people.name)

2-3.str

__ str__ ()用于表示对象代表的含义,返回一个字符串.实现了__ str__ ()方法.

  • 可以直接使用print语句输出对象,
  • 可以通过函数str()触发__ str__ ()的执行.这样就把对象和字符串关联起来,便于某些程序的实现,可以用这个字符串来表示某个类

演示

class People:
    name: str = 'iworkh'
    age: int = 20

    def __str__(self) -> str:
        print("call...__str__")
        return "{{'name': {}, 'age': {}}}".format(self.name, self.age)


class MyTestCase(unittest.TestCase):
    def test(self):
        people = People()
        print(people) # {'name': iworkh, 'age': 20}
        print(str(people)) # {'name': iworkh, 'age': 20}

2-4.doc

类文档,子类不会继承

help(类或实例)会触发

演示

class People:
    """
    人
    """
    name: str = 'iworkh'
    age: int = 20

    def say(self):
        """
        say method
        """
        print('say')


class MyTestCase(unittest.TestCase):
    def test(self):
        people = People()
        print(People.__doc__)  # 类: 人
        print(people.__doc__)  # 对象:人
        print(People.say.__doc__)  # 类:say method
        print(people.say.__doc__)  # 对象:say method
        print(help(People.say))  # 类:say method

2-5.dict

实例自定义属性,只有_init__和通过对象.xxx赋值的属性,才会显示

vars(实例)会触发

演示

class People:
    name: str = 'iworkh'
    age: int = 20
    admin: bool = True

    def __init__(self):
        self.admin = False

class MyTestCase(unittest.TestCase):
    def test(self):
        peo = People()
        print(peo.__dict__)  # {'admin': False},只有init和塞值的才会显示
        print(vars(peo))  # {'admin': False}
        # 塞值后
        peo.name = '沐雨云楼'
        print(peo.__dict__)  # 对象的__dict__ , {'admin': False, 'name': '沐雨云楼'}
        print(vars(peo))  # {'admin': False, 'name': '沐雨云楼'}
        print(People.__dict__)  # 类的__dict__, 很多,还包括__和自己定义的

2-6.getattribute

属性访问拦截器

演示

class People:
    name: str = 'iworkh'
    password = 'iworkh123'
    age: int = 20

    def __getattribute__(self, item):
        print("call .... __getattribute__")
        if item == 'password':
            return '保密'
        else:
            return object.__getattribute__(self, item)


class MyTestCase(unittest.TestCase):
    def test(self):
        peo = People()
        print(peo.name)  # iworkh
        print(peo.password)  # 保密

警告

不要在__getattribute__方法中调用self.xxxx,会死循环,而应该使用object.__getattribute__(self, item)

2-7.name

__name__ 返回类名

演示

class People:
    name: str = 'iworkh'


class MyTestCase(unittest.TestCase):

    def test_name(self):
        print(People.__name__)  # 类 People

2-8.属性操作

属性操作,无非就塞值、取值、删除属性

  • __setattr__ 设置属性,设置属性值时,触发这个方法 (对象.属性, setattr)
  • __getattr__ 取属性,当属性不存在时,才会触发这个方法(对象.属性,getattr)
  • __delattr__ 删除属性,删除属性值时,触发这个方法(delattr, del)

演示

class People:
    name: str = 'iworkh'
    password = 'iworkh123'

    def __getattr__(self, item):
        print('call...__getattr__')
        return 'no attribute'

    def __delattr__(self, item):
        print('call...__delattr__')
        object.__delattr__(self, item)

    def __setattr__(self, key, value):
        print('call...__setattr__')
        object.__setattr__(self, key, value)


class MyTestCase(unittest.TestCase):
    def test(self):
        peo = People()
        setattr(peo, 'name', '沐雨云楼')  # 塞值
        # 取值
        print(peo.name)  # 存在, 不会触发__getattr__
        print(peo.sex)  # 不存在,调用 __getattr__
        print(getattr(peo, 'name'))  # 存在, getattr方式 不会触发__getattr__
        print(getattr(peo, 'sex'))  # 不存在,getattr方式 调用 __getattr__
        # 删除
        peo.password = "test_password"  # 塞值
        del peo.password
        delattr(peo, 'name')

2-9.call

定义__call__类实例对象可以像调用普通函数那样,以对象名()的形式使用。

关于call的可以阅读下这篇文章: Python __call__详解

演示

class People:
    name: str = 'iworkh'
    age: int = 20

    def __call__(self, **kwargs):
        print('call...__call__')
        self.name = kwargs['name']
        self.age = kwargs['age']


class MyTestCase(unittest.TestCase):
    def test(self):
        peo1 = People()
        print(peo1.name, peo1.age, sep="......") # iworkh......20
        peo1(name='沐雨云楼', age=22) # 通过对象()方式就能修改值
        print(peo1.name, peo1.age, sep="......") # 沐雨云楼......22

思考:
如何取得类/对中所有属性或者方法?

  • dir:属性和方法都拿到
  • hasattr: 属性和方法都拿到
  • callable: 属性为False, 函数和方法是True

这有实现好的例子 python对象与dict互转

里面的obj_dict_tool.py类的to_dic方法

还可以看下这篇文章call方法,使用了__call__方式

2-10.repr

print的类如果没有__str__,那么就会调用__repr__

演示

class People:
    name: str = 'iworkh'
    age: int = 20

    def __str__(self) -> str:
        print("call...__str__")
        return "str --> {{'name': {}, 'age': {}}}".format(self.name, self.age)

    def __repr__(self):
        print("call...__repr__")
        return "repr --> {{'name': {}, 'age': {}}}".format(self.name, self.age)


class MyTestCase(unittest.TestCase):
    def test(self):
        people = People()
        print(people)
        print(str(people))

__str__代码注释后再试下

2-11.比较

对象与对象比较时,得需要比较的方法才能判断出大小,需要实现下面一些方法

  • __lt__: 小于 <
  • __gt__: 大于 >
  • __le__: 小于等于 <=
  • __ge__: 大于等于 >=
  • __eq__: 等于 =

当只定义了一个小于,没有定义大于时。在使用时也可以使用大于,会根据小于结果取反的。

演示

class People:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __le__(self, other):
        print('call ... le')
        return self.age <= other.age


class MyTestCase(unittest.TestCase):
    def test(self):
        people_san = People('三哥', 30)
        people_three = People('张三', 30)
        people_four = People('李四', 40)
        people_five = People('王五', 50)
        print(people_three <= people_four) # True
        print(people_five >= people_three) # True
        print(people_san >= people_three) # True
        print(people_san <= people_three) # True
        print(people_three >= people_four) # False

3.list

我们再来看下list的一些属性

属性说明
__add__+,返回一个新列表
__iadd__+=,原列表上操作
__contains__in,定义当使用成员测试运算符(in 或 not in)时的行为
__delitem__定义删除容器中指定元素的行为
__getitem__定义获取容器中指定元素的行为
__setitem__定义设置容器中指定元素的行为
__iter__for定义当迭代容器中的元素的行为
__next__for定义当迭代容器中的元素的行为
__len__定义当被 len() 调用时的行为
__mul__乘触发,返回新列表
__rmul__乘触发,右操作符,返回新列表
__imul__乘触发,改变自己
append在列表后面追加元素
extend在列表后面追加另一个列表的元素
insert指定位置插入元素
copy复制
countlist.count(data) 统计列表中出现data的次数
indexlist.count(data) 列表中元素的索引
poplist.pop(index) 弹出指定位置的元素
removelist.remove(obj) 删除列表中指定元素
clear清空list
reverse列表元素倒序输出
sortlist.sort(reverse=False)列表元素升序

下面只演示模仿方法

使用dir()函数来可以打印出有哪些属性,下面会挑一些常用的演示

演示

class MyTestCase(unittest.TestCase):
    def test_attr(self):
        print(dir([1,2,3]))

下面就来演示几个常用的魔法方法,以Alphabet类为例子,里面有个data属性为list类型,来存字母。

源码坐标

如需要源码,可以查看。(关键代码都贴在文章里,不用看也行)

3-1.添加

__add__ : 和+操作符对应,对象与对象相加,返回一个新列
__iadd__: 和+=操作符对应,添加一个元素,在原列表上操作

演示

class Alphabet:
    data = []

    def __init__(self, value) -> None:
        self.data = value

    def __add__(self, other):
        print('call...__add__')
        return Alphabet(self.data + other.data)

    def __iadd__(self, other):
        print("call...__iadd__")
        self.data += other
        return self

class MyTestCase(unittest.TestCase):
    def test(self):
        list_data = ['a', 'c', 'd']
        alphabet = Alphabet(list_data)
        print(alphabet.data)
        print("*" * 50)
        # + 与 __add__
        alphabet_add = Alphabet(['m', 'n'])
        alphabet_sum = alphabet + alphabet_add  # 返回一个新的对象
        print(alphabet.data)  # 原来的没有变 ['a', 'c', 'd']
        print(alphabet_sum.data)  # ['a', 'c', 'd', 'm', 'n']
        print("*" * 50)
        # += 与 __iadd__
        alphabet += 'x'
        print(alphabet.data) # 在原对象上操作 ['a', 'c', 'd', 'x']

3-2.包含

__contains__:是否包含

演示

class Alphabet:
    data = []

    def __init__(self, value) -> None:
        self.data = value

    def __contains__(self, item):
        print("call...__contains__")
        return item in self.data


class MyTestCase(unittest.TestCase):
    def test(self):
        list_data = ['a', 'c', 'd']
        alphabet = Alphabet(list_data)
        print(alphabet.data)
        print('a' in alphabet) # True
        print('x' in alphabet) # False

3-3.元素操作

__getitem__:取得元素
__setitem__:设置元素
__delitem__:删除元素

演示

class Alphabet:
    data = []

    def __init__(self, value) -> None:
        self.data = value

    def __getitem__(self, index):
        print('call...__getitem__')
        return self.data[index]

    def __setitem__(self, key, value):
        print('call...__setitem__')
        self.data[key] = value

    def __delitem__(self, key):
        print("call...__delitem__")
        delattr(self, key)


class MyTestCase(unittest.TestCase):
    def test(self):
        list_data = ['a', 'c', 'd']
        alphabet = Alphabet(list_data)
        print(alphabet.data)
        print("*" * 30) # ['a', 'c', 'd']
        # 取值
        print(alphabet[1])  # c 取值 会触发__getitem__
        # 设置值
        alphabet[1] = 'b'  # 设值 会触发__setitem__
        print(alphabet.data) # ['a', 'b', 'd']
        # 删除
        del alphabet['data']  # 删除 等价于 delattr(alphabet, 'data')  会触发__delitem__
        print(alphabet.data) # []

3-4.迭代

__iter__:迭代器,返回自己,然后for可以循环调用next方法
__next__: 每一次for循环都调用该方法(必须存在)

演示

class Alphabet:
    data = []
    index = -1

    def __init__(self, value) -> None:
        self.data = value

    def __iter__(self):
        print("call...__iter__")
        return self

    def __next__(self):
        print("call...__next__")
        self.index += 1
        if self.index >= len(self.data):
            raise StopIteration()
        else:
            return self.data[self.index]


class MyTestCase(unittest.TestCase):
    def test(self):
        list_data = ['a', 'c', 'd']
        alphabet = Alphabet(list_data)
        print(alphabet.data)
        # for循环 和 __iter__ 以及 __next__配合
        for item in alphabet:
            print(item)

3-5.重复

__mul__: 左操作符相乘,返回新的list,原list不变
__rmul__: 右操作符相乘,返回新的list,原list不变
__imul__: 改变原list

演示

class Alphabet:
    data = []

    def __init__(self, value) -> None:
        self.data = value

    def __mul__(self, other):
        print("call...__mul__")
        return Alphabet(self.data * other)

    def __rmul__(self, other):
        print("call...__rmul__")
        return Alphabet(other * self.data)

    def __imul__(self, other):
        print("call...__imul__")
        return self.data * other


class MyTestCase(unittest.TestCase):
    def test(self):
        list_data = ['a', 'c', 'd']
        alphabet = Alphabet(list_data)
        print(alphabet.data)
        # __mul__
        alphabet_3 = alphabet * 3
        print(alphabet.data)
        print(alphabet_3.data)  # ['a', 'c', 'd', 'a', 'c', 'd', 'a', 'c', 'd']
        print("*" * 50)
        # __rmul__
        alphabet_r3 = 3 * alphabet
        print(alphabet.data)
        print(alphabet_r3.data)  # ['a', 'c', 'd', 'a', 'c', 'd', 'a', 'c', 'd']
        print("*" * 50)
        # __imul__
        alphabet *= 3  # 改变了原对象
        print(alphabet)  # ['a', 'c', 'd', 'a', 'c', 'd', 'a', 'c', 'd']

3-6.len

__len__: 这个简单,但调用len(obj)会触发

演示

class Alphabet:
    data = []

    def __init__(self, value) -> None:
        self.data = value

    def __len__(self):
        return len(self.data)


class MyTestCase(unittest.TestCase):
    def test(self):
        list_data = ['a', 'c', 'd']
        alphabet = Alphabet(list_data)
        print(len(alphabet)) # 3

4.总结

内置属性(魔法方法)还有很多,只要记得一些常用的即可。

思考

  • 1.__init____new__的顺序、使用
  • 2.__getattribute__干嘛的?
  • 3.__call__作用,与callable内置函数有着怎样的关系呢?
  • 4.对象如何才能比较? __lt____gt__
  • 5.__getattr____setattr____delattr__何时触发?
  • 6.__add____iadd__区别,何时触发(++=)?什么时候会改变自身,什么时候会新建对象?
  • 7.__getitem____setitem____delitem__何时触发?
  • 8.迭代__iter____next__for-in循环的配合
  • 9.__mul____rmul____imul__何时触发?什么时候会改变自身,什么时候会新建对象?

如果上面几个问题能过回答上,那么恭喜您,本节内容的精髓您都掌握了。

到这内置函数和内置属性(魔法方法)都介绍完了,记得要配合 内置函数一起学习哦

5.推荐

能读到文章最后,首先得谢谢您对本文的肯定,你的肯定是对我们的最大鼓励。

你觉本文有帮助,那就点个👍
你有疑问,那就留下您的💬
怕把我弄丢了,那就把我⭐
电脑不方便看,那就把发到你📲

  • 7
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值