Python类-magic methods魔术方法, since 2022-04-19

(2022.04.19-20 Tues-Wed)
Python中的魔术方法,是可以定义在类中的特殊方法,可在特定的情况下调用。魔术方法以双下划线(underscore)包裹,形式如__call__, __new__等。本文整理常用的magic method和对应的功能,并给出实例。

magic methods列表

magic method的功能大致分为如下几类:

  • 构造与初始化
  • 运算符
  • 类型转换
  • 类表达
  • 控制属性access
  • 调用(callable objects)
  • 赋值
magic method description
__init__(self, [...) 初始化器,接收参数
__new__(cls, [...) 构造函数-创建类、实例化、返回类的实例
__del__(self) 析构函数-对象进行垃圾收回时调用
__cmp__(self, other) 比较运算符,分别返回1,0,-1对于self>other, self=other和self=other的情况
__eq__(self, other) == equality operator的行为
__ne__(self, other) !=
__lt__(self, other) <
__gt__(self, other) >
__le__(self, other) <=
__ge__(self, other) >=
__pos__(self) +some_object
__ne__(self) -some_object
__abs__(self) abs()
__invert__(self) invert运算符,反转
__round__(self) round()
__floor__(self) math.floor()
__ceil__(self) math.ceil()
__trunc__(self) math.trunc()
__add/radd__(self, other) 加法/反加操作 reflected addition
__sub/rsub__(self, other) 减法/反减
__mul/rmul__(self, other) 乘/反乘
__floordiv/rfloordiv__(self, other) //
__div/rdiv__(self, other) /
__mod/rmod__(self, other) %
__pow/rpow__ **
__and/or/xor__(self, other) &/ | / ^
__iadd__(self, other) 自加,其他运算符以此类推
__int__(self) 类型转换为int,其他的包括float, long, complex, oct, hex
__str__(self) 返回str,当前实例文本显示的内容,即print(this_ins),针对人显示
__repr__(self) 直接调用当前实例的显示内容,针对机器显示,>> this_ins返回的内容
__class__ 查看当前实例所属的类,不传递参数
__format__(self, formatstr) "Hello, {0:abc}!".format(a) would lead to the call a.__format__("abc")
__getattr__(self, attr) 定义访问不存在的属性attr的返回结果
__getattribute__(self, attr) 定义访问一个存在的属性attr的返回结果
__setattr__(self, attr, value) 定义设定一个属性attr的值成为value的时候的操作
__delattr__(self, attr) 定义执行del attr的操作
__call__(self, para) 像调用函数一样调用一个实例
__getitem__(self, key) 用key作为index访问的返回值,self[key]
__setitem__(self, key, value) 设定index为key的元素值为value,self[key] = value
__delitem__(self, key) 删除index为key的元素
__annotations__ 查看函数的注释

其他magic method包括可迭代对象的__iter__, __next__,上下文管理的__enter__, __exit__,类注释的__doc____slot__, __dict__,定义len()函数的方法__len__等。

说明和举例:

  • __init____new__的差别:
    这两个方法都在类的实例化时被创建。__new__被首先调用,__init__其次。__new__返回新的类实例,__init__为新的实例设置属性,无返回值。
class A(object): 
    def __new__(cls): 
         print("New method") 
         return super(A, cls).__new__(cls) 
   
    def __init__(self): 
        print("Init method ") 
>>> A() 
New method
Init method 
<__main__.A object at 0x1016a2b80>

(2022.04.28 Thu)
__new__覆盖初始类的__new__方法,用于实现工厂模式、单例模式、元编程(需要用__new__控制对象创建)。下面给出工厂模式的一个例子。

class Shape(object):
    def __init__(self):
        pass
    def draw(self):
        pass

class Rectangle(Shape):
    def __init__(self):
        print('This is a rectangle.')
    def draw(self):
        print('drawing a rectangle')

class Square(Shape):
    def __init__(self):
        print('This is a square.')
    def draw(self):
        print('drawing a square.')

class ShapeFactory(object):
    shapes = {'rectangle': Rectangle, 'square': Square}
    def __new__(cls, name):
        if name in ShapeFactory.shapes.keys():
            print('creating a new shape: {}'.format(name))
            return ShapeFactory.shapes[name]() #返回实例化的对象
        else:
            print('creating a shape')
            return Shape()

调用类ShapeFactory

>>> a = ShapeFactory('rectangle') # 这里的a是已经经过实例化的对象,不需要再加括号调用
creating a new shape: rectangle
This is a rectangle.
>>> a.draw()
drawing a rectangle
  • __init__()__init__.py的区别
    (2022.11.04 Fri)
    上面已经介绍到__init__方法用于为新的类实例设置属性,而一个 文件夹中的__init__.py文件则有完全不同的作用。

Python的模块(Module)指的是一个单独的Python文件,而包(package)指的是一个文件夹,该文件夹是模块的集合。

当用户使用import指令从一个文件夹引入module/package时,如果该文件夹本身含有__init__.py文件,则从该文件夹的目录下引入文件时,编译器将该文件夹当做package对待。

__init__.py文件用于将Python的包package初始化。

通常__init__.py文件为空,但可以为它增加功能。在导入一个包时,实际上是导入了该包所在文件夹内的__init__.py文件。这样我们可以在__init__.py文件中批量导入所需模块,而不再需要逐一导入。

当使用import指令因为文件夹时,会首先执行该文件夹下的__init__.py文件。如果该文件中也有导入命令,则执行。当用户使用from xxx import *这个指令时,会导入该文件夹内的所有module。如果控制该命令导出的内容,可在__init__.py文件中加入__all__=[xxx]的命令,用以限制*导出的方法。具体内容参考本文的__all__部分。

  • __getattr__, __getattribute__的差别
    __getattr__用于定义查询不存在的属性时的操作,而__getattribute__定义了查询存在的属性时的操作。默认情况下访问不存在的属性返回AttributeError: xx object has no attribute xx.
    注意当访问属性时,首先调用__getattribute__方法,如果访问的属性在实例中不存在,则再调用__getattr__方法。如下面案例所示
class attrc:
    var = 0
    def __getattr__(self, attr):
        print('get a non-existing attribute')
        return '1'
    def __getattribute__(self, attr):
        print('get an existing attribute')
        return super().__getattribute__(attr)

调用

>>> m = attrc()
>>> m.var 
get an existing attribute
0
>>> m.k #m中不含k这个属性
get an existing attribute
get a non-existing attribute
'1'
  • __call__
class cs:
    def __call__(self, val=None):
        if val:
            print('value = ', val)
        return 'call return: 1'

调用实例

>>> m = cs()
>>> m()
'call return: 1'
>>> m(99)
value =  99
'call return: 1'
  • __add__和继承list的案例
    设计一个新的数据类型,特征和list完全一致,除了加法操作由list的拼接改成对应元素相加。
class listLike(list):
    def __add__(self, al):
        if isinstance(al, list) or isinstance(al, tuple):
            pass
        else:
            return 'TypeError: add a list or tuple'
        if len(al) < len(self):
            return 'LengthError: specify a longer sequence'
        return [self[i]+al[i] for i in range(len(self))]

    def __radd__(self, al):
        return self.__add__(al)

调用

>>> a = listLike([1,2,3])
>>> a+[4,5,6]
[5, 7, 9]
>>> a
[1, 2, 3]
>>> [4,5,6]+ a
[5, 7, 9]
  • __annotations__查看函数注释
    (2022.05.27 Fri)
    Python 3.0加入了函数注释功能,比如下面例子中的func函数,输入的两个参数分别是a1a2类型分别是strint,默认值为1,函数返回类型为int。注意赋默认值的变量放在后面。
def func(a1:str, a2:int=1) ->int:
    """
    this is function help info
    """
    return 1

此时查看该函数的内置magic method/method decorated by @property __annotations__,会看到返回的字典其中包含了函数的输入输出类型。

>> func.__annotations__
{'a1': , 'a2': , 'return': }

另外,可以通过help函数查看该函数在定义之后的解释信息

>> help(func)
Help on function func in module __main__:

func(a1: str, a2: int = 1) -> int
    this is function help info

查看help信息结束,按q退出。

(2022.11.02 Wed)

  • __all__
    当使用importfrom指令(from xxx import *)从模块中导入方法/子模块时,导入的属性、方法、子类的特点是不以单下划线_开头的对象。

比如如下的Python文件

# test__all__.py

def func1():
    print(1)

def func2():
    print(2)

def _func3():
    print(3)

导入之后,查看内存中的对象

>> from test__all__ import *
>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', 
'__name__', '__package__', '__spec__', 'func1', 'func2']

没有下划线开头的对象func1func2被导入内存,以下划线开头的对象_func3没有被导入内存。

除此之外,可在test__all__模块中加入__all__变量,其值为列表,存储的是当前模块成员(属性、方法、子类)的名称。在模块中设置了__all__变量,当代码中通过from xxx import *导入模块时,只有__all__变量中指定的成员会被导入,即便该成员的名字是以下划线_开头。

# test__all__.py

def func1():
    print(1)

def func2():
    print(2)

def _func3():
    print(3)

__all__ = ['func1', '_func3']

导入

>> from test__all__ import *
>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', 
'__name__', '__package__', '__spec__', '_func3', 'func1']

变量__all__的设置是针对from xxx import *的命令,一旦采用其他方式导入,__all__的设置将失效,分别是import xxxfrom xxx import func2, _func3

>> import test__all__ as ta
>> ta.func2()
2
>> from test__all__ import func2, _func3
>> func2()
2
>> _func3()
3

Reference

1 rszalski点github点io/magicmethods/

你可能感兴趣的:(Python类-magic methods魔术方法, since 2022-04-19)