python魔法函数

1. 简介

魔法函数允许在类中自定义函数(函数名格式一般为__xx__),并绑定到类的特殊方法中。比如在类A中自定义__str__()函数,则在调用str(A())时,会自动调用__str__()函数,并返回相应的结果。在我们平时的使用中,可能经常使用__init__函数(构造函数)和__del__函数(析构函数)

  • Python中以双下划线__xx__开始和结束的函数(不可自己定义)为魔法函数
  • 调用类实例化的对象的方法时自动调用魔法函数

2. 魔法函数

2.1 __repr__函数

我们经常会直接输出类的实例化对象,例如:

class CLanguage:
    pass
clangs = CLanguage()
print(clangs)

输出:

<__main__.CLanguage object at 0x000001A7275221D0>

通常情况下,直接输出某个实例化对象,本意往往是想了解该对象的基本信息,例如该对象有哪些属性,它们的值各是多少等等。但默认情况下,我们得到的信息只会是“类名+object at+内存地址”。通过重写类的 __repr__() 方法可以自定义输出实例化对象时的信息。当我们打印输出某个实例化对象时,其调用的就是该对象的 repr() 方法,输出的是该方法的返回值

Python 中的每个类都包含 repr() 方法,因为 object 类包含 reper() 方法,而 Python 中所有的类都直接或间接继承自 object 类。默认情况下,repr() 会返回和调用者有关的 “类名+object at+内存地址”信息。当然,我们还可以通过在类中重写这个方法,从而实现当输出实例化对象时,输出我们想要的信息。

class CLanguage:
    def __init__(self):
        self.name = "C语言中文网"
        self.add = "http://c.biancheng.net"
    def __repr__(self):
        return "CLanguage[name="+ self.name +",add=" + self.add +"]"
clangs = CLanguage()
print(clangs)

输出

CLanguage[name=C语言中文网,add=http://c.biancheng.net]

执行 print(clangs) 等同于执行 print(clangs.__repr__())

2.2 __ str__()函数

打印一个实例化对象时,打印的其实时一个对象的地址。而通过__str__()函数就可以帮助我们打印对象中具体的属性值,或者你想得到的东西。在Python中调用print()打印实例化对象时会优先调用__str__()。如果__str__()中有返回值,就会打印其中的返回值。

class Cat:
    """定义一个猫类"""
 
    def __init__(self, new_name= "汤姆", new_age= 20):
        """在创建完对象之后 会自动调用, 它完成对象的初始化的功能"""
        self.name = new_name
        self.age = new_age  # 它是一个对象中的属性,在对象中存储,即只要这个对象还存在,那么这个变量就可以使用
        # num = 100  # 它是一个局部变量,当这个函数执行完之后,这个变量的空间就没有了,因此其他方法不能使用这个变量
 
    def __str__(self):
        """返回一个对象的描述信息"""
        # print(num)
        return "名字是:%s , 年龄是:%d" % (self.name, self.age)

    def __repr__(self) -> str:
        return "repr return None"
        
# 创建了一个对象
tom = Cat("汤姆", 30)
print(tom)

输出:

名字是:汤姆 , 年龄是:30

2.2.1 __repr__和__str__区别

class Test(object):
    def __init__(self, value='hello, world!'):
        self.data = value

>>> t = Test()
>>> t
<__main__.Test at 0x7fa91c307190>
>>> print(t)
<__main__.Test object at 0x7fa91c307190>
# 显示的是对象的内存地址
# ===================================================
# 重构__repr__
class TestRepr(Test):
    def __repr__(self):
        return 'TestRepr(%s)' % self.data

>>> tr = TestRepr()
>>> tr               # 直接终端显示,不print就是面向程序员
TestRepr(hello, world!)
>>> print(tr)         # print是面向程序员
TestRepr(hello, world!)

# 重构__repr__方法后,不管直接输出对象还是通过print打印的信息都按我们__repr__方法中定义的格式进行显示了

# ====================================================
# 重构__str__
calss TestStr(Test):
    def __str__(self):
        return '[Value: %s]' % self.data

>>> ts = TestStr()
>>> ts
<__main__.TestStr at 0x7fa91c314e50>
>>> print(ts)
[Value: hello, world!]

__repr__和__str__这两个方法都是用于显示的,__str__是面向用户的,而__repr__面向程序员。 打印操作会首先尝试__str__和str内置函数(print运行的内部等价形式),它通常应该返回一个友好的显示。

  • 重构__repr__ 后管是直接输出还是打印输出都是按照函数中定义的格式输出
  • 重构__str__后直接输出按照__repr__定义的输出格式(默认对象名称加地址),打印输出按照__str__定义的方式输出

2.3 __init__函数

所有类的超类object,有一个默认包含pass的__ init __()实现,这个函数会在对象初始化的时候自动调用,我们可以选择实现,也可以选择不实现,一般建议是实现的,不实现对象属性就不会被初始化。

__init__() 方法可以包含多个参数,但必须包含一个名为 self 的参数,且必须作为第一个参数。也就是说,类的构造方法最少也要有一个 self 参数,仅包含 self 参数的 __init__() 构造方法,又称为类的默认构造方法。

class TheFirstDemo:
        '''这是一个学习Python定义的第一个类'''

        # 构造方法
        def __init__(self):
                print("调用构造方法")
        # 下面定义了一个类属性
        add = 'http://c.biancheng.net'
        # 下面定义了一个say方法
        def say(self, content):
                print(content)
if __name__ == "__main__":
        result = TheFirstDemo()

输出:

调用构造方法
class CLanguage:
    '''这是一个学习Python定义的一个类'''
    def __init__(self,name,add):
        print(name,"的网址为:",add)

#创建 add 对象,并传递参数给构造函数
add = CLanguage("C语言中文网","http://c.biancheng.net")

输出

C语言中文网 的网址为: http://c.biancheng.net

可以看出__init__函数在类实例化的时候被自动调用,传参数的时候self不赋值

2.4 __call__函数

该方法的功能类似于在类中重载()运算符,使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用。作用:为了将类的实例对象变为可调用对象。

class CLanguage:
    # 定义__call__方法
    def __call__(self,name,add):
        print("调用__call__()方法",name,add)

clangs = CLanguage()
clangs("C语言中文网","http://c.biancheng.net")

输出

调用__call__()方法 C语言中文网 http://c.biancheng.net

通过在 CLanguage 类中实现 call() 方法,使的 clangs 实例对象变为了像函数一样的可调用对象。

对于可调用对象,实际上名称()可以理解为是名称.__call__()的简写

def myfunc(name):
    print("name:", name)

if __name__ == "__main__":
    name = "lihua"
    myfunc.__call__(name)

输出

name: lihua
  • 函数本身可以被调用
def func():
    pass
print(callable(func))
# 输出: True
  • 类本身可被调用
class Fun:
    def __init__(self):
        pass
print(callable(Fun))
# 输出: True
  • 类的实例化对象不能被调用
class Fun:
    def __init__(self):
        pass
a = Fun()
print(callable(a))
# 输出: False
  • 通过增加__call__()函数,将类实例化对象变为可调用
class Fun:
    def __init__(self):
        pass
    def __call__(self, *args, **kwargs):
        pass
a = Fun()
print(callable(a))
# 输出:False

2.5 __all__函数

python模块中的__all__,用于模块导入时限制(限制哪些可以导入哪些无法导入),如:from module import *。被导入模块若定义了__all__属性,则只有__all__内指定的属性、方法、类可被导入;若没定义,则导入模块内的所有公有属性,方法和类。

case 1

# aa.py
class A():
    def __init__(self,name,age):
        self.name=name
        self.age=age
class B():
    def __init__(self,name,id):
        self.name=name
        self.id=id
def fun():
    print("func() is run!")
def fun1():
    print("func1() is run!")
# bb.py
from aa import *
a=A('zhansan','18')
print(a.name,a.age)
b=B("lisi",1001)
print(b.name,b.id)
fun()
fun1()

由于aa.py中没有定义__all__属性,所以导入了aa.py中所有的公有属性

zhansan 18
lisi 1001
func() is run!
func1() is run!

case 2

# bb.py
__all__=('A','func')
class A():
    def __init__(self,name,age):
        self.name=name
        self.age=age
class B():
    def __init__(self,name,id):
        self.name=name
        self.id=id
def func():
    print("func() is run!")
def func1():
    print("func1() is run!")
# test_bb.py
from bb import *
a=A('zhansan','18')
print(a.name,a.age)
func()
#b=B("lisi",1001)
#NameError: name 'B' is not defined
#func1()
#NameError: name 'func1' is not defined 

由于bb.py中使用了__all__=('A','func'),所以在别的模块导入该模块时,只能导入__all__中指定的变量、方法、类

case 3

# bb.py
def func(): #模块中的public方法
    print('func() is run!')
def _func(): #模块中的protected方法
    print('_func() is run!')
def __func(): #模块中的private方法 
    print('__func() is run!')
from bb import *  #此方式只能导入公有的属性、方法、类【无法导入以单下划线开头(protected)或以双下划线开头(private)的属性、方法、类】
func()
#_func(). # NameError: name '_func' is not defined
#__func() # NameError: name '__func' is not defined
  • from bb import *方式只能导入公有的属性、方法、类。无法导入以单下划线开头protected或以双下划线开头private的属性、方法、类
  • 放入__all__中所有属性均可导入,即使是以下划线开头
  • 虽然_func()、__func()属于protected , private权限的,但是如果使用from bb import func,_func,__func方式,是可以直接导入访问的

2.6 __getitem__函数

__getitem__()主要作用是可以让对象实现迭代功能

import re
RE_WORD = re.compile(r'\w+')
class Sentence:
    def __init__(self, text):
        self.text = text
        self.words = RE_WORD.findall(text)  # re.findall函数返回一个字符串列表,里面的元素是正则表达式的全部非重叠匹配
    def __getitem__(self, index):
        return self.words[index]  
>>> s = Sentence('The time has come')
>>> for word in s:
            print(word)
    
    The
    time
    has
    come
>>> s[0]
    'The'
>>> s[1]
    'time'

示例 s可以正常迭代,但是没有定义getitem() 测试则会报错。解释器需要迭代对象x时, 会自动调用iter(x)方法。内置的 iter(x) 方法有以下作用:

  • 检查对象是否实现了__iter__方法,如果实现了就调用它,获取一个迭代器
  • 如果没有实现iter()方法, 但是实现了 __getitem__方法,Python会创建一个迭代器,尝试按顺序(从索引0开始,可以看到我们刚才是通过s[0]取值)获取元素
  • 如果尝试失败,Python抛出TypeError异常,通常会提示TypeError: '***' object is not iterable
class DataBase:
    '''Python 3 中的类'''

    def __init__(self, id, address):
        '''初始化方法'''
        self.id = id
        self.address = address
        self.d = {self.id: 1,
                  self.address: "192.168.10.10",
                  }
    def __getitem__(self, key):
        # return self.__dict__.get(key, "100")
        return self.d.get(key, "default")

data = DataBase(1, "192.168.2.11")
print(data["hi"])
print(data[data.id])
default
1
class DataBase:
    '''Python 3 中的类'''

    def __init__(self, id, address):
        '''初始化方法'''
        self.id = id
        self.address = address

    def __getitem__(self, key):
        return self.__dict__.get(key, "100")

data = DataBase(1, "192.168.2.11")
print(data["hi"])
print(data["id"])
100
1

参考**

Python高级语法:魔法函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值