python 常用的魔法函数

简介

在实际项目中,我们其实并不会太多的使用魔法函数,但是一些方法或者函数均是有魔法函数演变而来的,且在一些面试过程中会遇到一些关于常见方法的实现,就会牵扯到相应的魔法函数,此处将介绍一些我认为比较常用的魔法函数。

__new __、__init __

__new__(cls[, ...]):
调用以创建一个 cls 类的新实例。__new__() 是一个静态方法 (因为是特例所以你不需要显式地声明),它会将所请求实例所属的类作为第一个参数。其余的参数会被传递给对象构造器表达式 (对类的调用)。__new__() 的返回值应为新对象实例 (通常是 cls 的实例)。

如果 __new__() 未返回一个 cls 的实例,则新实例的 __init__() 方法就不会被执行。

__new__() 的目的主要是允许不可变类型的子类 (例如 int, str 或 tuple) 定制实例创建过程。它也常会在自定义元类中被重载以便定制类创建过程。

__init__(self[, ...]):
在实例 (通过 __new__()) 被创建之后,返回调用者之前调用。其参数与传递给类构造器表达式的参数相同。一个基类如果有 __init__() 方法,则其所派生的类如果也有 __init__() 方法,就必须显式地调用它以确保实例基类部分的正确初始化;例如: super().init([args...]).

一个对象是由 __new__() 和 __init__() 协作构造完成的 (由 __new__() 创建,并由 __init__() 定制),__new__返回__init__所需的对象,也就是self。

class Init():

    def __init__(self, name):
        self.name = name

    def show_name(self):
        print(self.name)

i = Init('tom')
i.show_name()
tom

__str __ 与 __repr __

当直接查看对象的时候调用的是__repr__方法,对象需要转字符串的时候调用的是__str__方法,但是当字典列表等容器的时候调用的还是__repr__方法,可以通过str()和repr()来实现结果的输出,str输出的结果一般可读性相对较强,repr的结果输出相对较为准确,当调用str()时会调用 __str __ 当调用repr()时会调用 __repr __,一般而言,__repr __更适合开发者使用

class StrRepr():

    def __str__(self):
        return '__str__'

    def __repr__(self):
        return '__repr__'

strRepr = StrRepr()
print(str(strRepr))
print(repr(strRepr))

import datetime
print(str(datetime.datetime.today()))
print(repr(datetime.datetime.today()))
tom
__str__
__repr__
2022-06-08 13:50:11.131003
datetime.datetime(2022, 6, 8, 13, 50, 11, 131031)

由datetime可以看出,__repr____str__较为准确
在命令终端时执行时默认输出__repr__的返回,如下:

>>> class Test(object):
...     def __str__(self):
...         return "__str__"
...     def __repr__(self):
...         return "__repr__"
... 
>>> 
>>> t = Test()
>>> t
__repr__

__getitem __、__setitem __、__delitem __

主要针对于字典的key、value进行操作
getitem: 获取键的值
setitem:设置键的值
delitem:删除对应的键
以上的操作都是基于dict[ ]进行操作的

class GetItem():

    def __init__(self):
        self.data = {}

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

    def __delitem__(self, key):
        print('__delitem__')
        del self.data[key]

get = GetItem()
get['name'] = 'tom'
print(get['name'])
del get['name']
print(get['name'])
__setitem__
__getitem__
tom
__delitem__
__getitem__
Traceback (most recent call last):
  File "/home/ts/flask_study/常见的魔法函数/magic_method.py", line 62, in <module>
    print(get['name'])
  File "/home/ts/flask_study/常见的魔法函数/magic_method.py", line 48, in __getitem__
    return self.data[key]
KeyError: 'name'

__iter __、__next __

之前曾经记录过关于迭代器、可迭代对象、生成器这三个名词,这两个魔法函数__iter __、__next __就是迭代器的内部实现,可迭代对象则是__iter __的内部实现,生成器则不需要依赖这两者,取而代之的是yield方法。
下面实现了一个简单的range用法

class rangeDemo():

   def __init__(self, count):
       self.start = 0
       self.count = count

   def __iter__(self):
       return self

   def __next__(self):
       if self.start < self.count:
           self.start += 1
           return self.start
       else:
           raise StopIteration

for i in rangeDemo(10):
   print(i, end=',')

print()
n = rangeDemo(10)
print(next(n))
print(next(n))
print(next(n))
1,2,3,4,5,6,7,8,9,10,
1
2
3

当一个类或者对象是否可迭代或者说可遍历时,一般会实现__iter __方法。
__iter __一般返回本身,__next __一般返回下一个元素,超出则抛出异常

__call __

当一个类实例化一个对象后,调用对象时会触发__call __方法

class Call():

   def __init__(self):
       pass

   def __call__(self):
       print('__call__')
       pass

c = Call()
# 调用对象
c()

__enter __、__exit __

大家在读写文件时一般会用到with open,然而对应with来说,enter、__exit__就是其内部实现,__enter__返回其对象,方便后续进行操作,__exit__则是在结束前进行的操作,有点类似于try except finally。

class WithDemo():

   def __init__(self):
       pass

   def __enter__(self):
       # 必须要返回对象本身,供后面进行调用
       print('__enter__')
       return self

   def __exit__(self, exc_type, exc_val, exc_tb):
       print('__exit__')

   def run(self):
       print('test')

with WithDemo() as f:
   f.run()
__enter__
test
__exit__

__contains __

判断一个容器是否存在某个元素,可以使用in、not in进行判断的,一般都实现了__contains__。

class Contains():

   def __contains__(self, item):
       print('__contains__', item)

print(1 in Contains())

class Contains2():
   
   def __contains__(self, item):
       print('__contains__', item)
       return True

print(1 in Contains2())
__contains__ 1
False
__contains__ 1
True

由此可见,默认情况下__contains__会返回False

__setattr __、__getattr __

__ setattr__(self, key, value):设置属性,会将对应的值放入__dict__中,是否需要注册到__dict__中,会影响对应的属性的获取,在类中对属性进行赋值操作时,python会自动调用__setattr__()函数,来实现对属性的赋值。但是重写__setattr__()函数时要注意防止无限递归的情况出现,一般解决办法有两种,一是用通过super()调用__setatrr__()函数,二是利用字典操作对相应键直接赋值。
简单的说,__ setattr__()在属性赋值时被调用,并且将值存储到实例字典中,这个字典应该是self的__dict__属性。
即:在类实例的每个属性进行赋值时,都会首先调用__setattr__()方法,并在__setattr__()方法中将属性名和属性值添加到类实例的__dict__属性中。
__getattr __(self, item):访问不存在的属性

class Attr():

   def __init__(self):
       print(self.__dict__)

   def __setattr__(self, key, value):
       print('__setattr__')
       self.__dict__[key] = value
       pass

   def __getattr__(self, key):
       print('__getattr__')
       print(self.__dict__[key])


attr = Attr()
attr.name = 'tom'
print(attr.__dict__)
print(attr.name)
print(attr.age)
{}
__setattr__
{'name': 'tom'}
tom
__getattr__
Traceback (most recent call last):
 File "/home/ts/flask_study/常见的魔法函数/magic_method.py", line 162, in <module>
   print(attr.age)
 File "/home/ts/flask_study/常见的魔法函数/magic_method.py", line 155, in __getattr__
   print(self.__dict__[key])
KeyError: 'age'

总结:Python的实例属性的定义、获取和管理可以通过__setattr__()和__dict__配合进行,当然还有对应的__getattr__()方法,如上文所示。
__ setattr__(self, key, value)方法在类的属性赋值时被调用,并通常需要把属性名和属性值存储到self的__dict__字典中。

__getattribute __

属性拦截器:当这个类的属性被访问时(指的是__init__构造方法中的属性),会自动调用类的__getattribute__方法,注意不要在__getattribute__中调用本类中的方法和实例属性,会出现反反复复的情况。

class Attribute():

    def __init__(self, name, age):
        self.name = name
        self.age = age
        pass

    def __getattribute__(self, name):
        # 获取的是获取属性的key值,不是属性的值
        print('__getattribute__, name:{}'.format(name))
        if name == 'name':
            return True
        else:
            return 'no found'

attribute = Attribute('tom', 18)
print(attribute.name)
print(attribute.age)
attribute = Attribute('mike', 18)
print(attribute.name)
print(attribute.age)
__getattribute__, name:name
True
__getattribute__, name:age
no found
__getattribute__, name:name
True
__getattribute__, name:age
no found

__dir __

该方法会返回一个类的所有类属性及类方法,包括静态方法、类方法、实例方法等,不会返回实例属性(也就是__init__的属性)。可以通过dir来获取。
如下存在一个Test类

class Test():

    test = 1

    def __init__(self, name):
        self.name = name

    def run(self):
        print('run')
    
    @staticmethod
    def test():
        print('test')

    @classmethod
    def class_test(cls):
        print('class_test')

我们使用__dir__取获取

print(object.__dir__)
print(dir(object))

from test import Test
print(dir(Test))

<method '__dir__' of 'object' objects>
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
['__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_test', 'run', 'test']

__all __

通过在模块文件中设置 __ all__ 变量,当其它文件以“from 模块名 import *”的形式导入该模块时,该文件中只能使用 __ all__ 列表中指定的成员。
也就是说,只有以“from 模块名 import ”形式导入的模块,当该模块设有 __ all__ 变量时,只能导入该变量指定的成员,未指定的成员是无法导入的。
__ all__:如果存在__all__ 可以直接导入
直接使用__all__的列表中的所有成员(包括属性和函数)并使用,如果没有__all__,则默认__all __里是所有函数或属性。
当前存在一个包,里面内容如下,将run1、run2添加至__all__中

__all__ = ['run1', 'run2']

def run1():
   print('run1')
   pass

def run2():
   print('run2')
   pass

def run3():
   print('run3')
   pass

导入*进行操作

from test2 import *

run1()
run2()
run3()

显示run3没有被定义,因为导入包时,做了限制,不过只是限制了*

run1
run2
Traceback (most recent call last):
 File "/home/ts/flask_study/常见的魔法函数/magic_method.py", line 203, in <module>
   run3()
NameError: name 'run3' is not defined

由于__all __只会限制*的导入方式,因此可以采取导入到详细函数的方式来使用

from test2 import run3
run3()
run3

__slots __

限制绑定的属性
普通的设置属性,如上面的__setattr __所示,会将设置的属性放置在__dict __ 中,__getattr 也会从 dict__中拿取,详细如下:

class Student2(object):

  def __init__(self, name="mike"):
      self.name = name
      print(hasattr("name", ""))
      pass
  
  def run(self):
      print('run')

s = Student2()
print(dir(s))
print(hasattr(s, "name"))
s.name = "mike"
s.gender = "man"
print(s.name)
print(s.gender)
s.age = 3
print(s.age)

False
['__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', 'run']
True
mike
man
3
class Student(object):
  
  # 限制绑定的属性
  __slots__ = ["name", "gender"]

  def __init__(self, name="mike"):
      self.name = name
      print(hasattr("name", ""))
      pass
  
  def run(self):
      print('run')

s = Student()
print(dir(s))
print(hasattr(s, "name"))
s.name = "mike"
s.gender = "man"
print(s.name)
print(s.gender)
s.age = 3
print(s.age)
False
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'gender', 'name', 'run']
True
mike
man
Traceback (most recent call last):
File "/home/ts/flask_study/常见的魔法函数/magic_method.py", line 231, in <module>
  s.age = 3
AttributeError: 'Student' object has no attribute 'age'

由上方两个可以看出,当使用__slots __限制绑定属性后,__dict __属性消失了。
综上,__slots __限制的是类属性的设置及获取,适用于类属性固定的状态下,除此之外,使用__slots __能大大减小内存占用。

defaultskwdefaults

__defaults__:函数对象的参数的默认参数的值
__kwdefaults__: 函数对象的参数的关键字参数的值

def run(para=None, number=10, *, gender="man"):
    return (para, number)

print(run.__defaults__)
print(run.__kwdefaults__)

(None, 10)
{'gender': 'man'}

持续更新中

posted @ 2022-06-08 17:28  形同陌路love  阅读(406)  评论(0编辑  收藏  举报