Python中的神奇方法

298 阅读9分钟

Python编程语言中的魔法方法是专门为面向对象设计的。我们创建的每个类都有自己的魔法方法。Python的标准解释器将这些方法分配给我们在其内部创建的每个类。因此,在这篇文章中,我们将详细了解如何调用和使用魔法方法以获得更好的编程方法。让编码的乐趣开始吧!

刷新OOP知识

在我们进入正题之前,让我们先了解和磨练一下OOP概念的知识。我们将只看到基础知识。所以,面向对象编程是一种将数据成员和成员函数包围在一个用户定义的实体中的方式,称为

类是持有特定数据项的东西,这些数据项相互关联并以特定的方式进行交流。我们使用对象来访问属性和成员函数。对象是一个类的实例。在任何编程语言中,当我们创建一个类时,内存是不会被分配的,但它实际上是在我们创建其实例(即对象)时被创建的。

例子

动物是一个类的类型。在这里面,我们包括所有居住在地球上的生物。因此,每个人都有自己的生活方式、食物和住所。动物只是定义了所有这些的蓝图。例如,**猫是动物类的对象。它有四条腿,吃老鼠,生活在房屋或灌木丛中。**同样,老虎有四条腿,但它杀死并吃掉许多动物,所以我们说老虎吃肉,它住在森林里。

使用Python的代码示例

class Animal:
    def __init__(self, legs, food, shelter):
        self.legs = legs
        self.food = food
        self.shelter = shelter
        
    def showAnimal(self):
        print("The animal has {} legs: ".format(self.legs))
        print("The animal eats: {}".format(self.food))
        print("The animal lives in: {}".format(self.shelter))
        
cat = Animal(4, "Mouse", "House")
tiger = Animal(4, "Meat", "Forest")
cat.showAnimal()
tiger.showAnimal()

输出

The animal has 4 legs: 
The animal eats: Mouse
The animal lives in: House
The animal has 4 legs: 
The animal eats: Meat
The animal lives in: Forest

解释

  1. 动物类包含腿的数量、食物和住所等属性。
  2. 当我们创建一个实例并在构造函数中插入这些值时,它们的行为差异是很明显的。
  3. 所以,同一类的对象可以根据数值的行为而有所不同。

所以,在上面的例子中,我们有一个类是动物。Python有一组方法,即Dunder方法,负责持有一个类的属性、数据成员和成员函数。

定义:当我们创建一个对象时,Python解释器会在代码执行的后端调用特殊的函数。它们被称为魔法方法或Dunder方法。

为什么我们说Dunder?因为它们的名字位于双下划线之间。当我们创建一个类的对象时,它们会进行一系列的计算,就像魔术一样。那么,我们如何检查它们是什么,标准类中有多少个?使用下面的步骤来找到它们;

  1. 创建一个样本类。
  2. 创建它的对象。
  3. 使用**dir()**函数,将对象插入其中。
  4. 这个函数会打印出所有的Magic方法以及分配给该类的数据成员和成员函数的列表。

代码

输出。

__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__
food
legs
shelter
showAnimal

你看到的双下划线的名字都是魔法方法。 其余的属性是由用户定义的。我们可以看到__init__() ,它是Python中任何类的构造函数,也是一个神奇的方法。让我们逐一看看它们的用途。为了理解它们的功能,总是尝试覆盖这些函数。

需要注意的是,对于用户定义的任何一个类,都有一些默认的魔法方法与之相对应。

一些魔法方法的使用和实现

在这一节中,我们将看到一些神奇方法的使用和实现,并使用这些方法来编写一个更好的OOP设计

1.1. __new__()。

这个方法帮助构造函数__init__()方法来为一个类创建对象。因此,当我们创建一个类的实例时,Python解释器首先调用__new__()方法,之后调用__init__()方法。它们相互配合,共同工作。

  1. 当程序员选择创建一个对象时,__new__()被调用,接受对象的名字。
  2. 然后调用__init__(),其中包括自我在内的参数被插入到对象中,这反过来帮助我们修改类的属性。

代码

class Sample:
    def __new__(self, parameter):
        print("new invoked", parameter)
        return super().__new__(self)
        
    def __init__(self, parameter):
        print("init invoked", parameter)
        
obj = Sample("a")

输出

new invoked a
init invoked a

解释

  1. 首先,我们创建一个名为Sample的类。
  2. 然后通过创建__new__()方法来重写它。然后,像往常一样,自我参数来,之后给出一个简单的参数。
  3. 用带有自我参数的__new__() 函数返回一个super()函数,以获得我们对该方法所做的定制的权限。
  4. 然后,用同样的做法调用带有一些参数的__init__() 函数。
  5. 之后,创建一个样本类的对象。
  6. 现在,当我们运行代码时,解释器首先调用__new__(),然后调用__init__()方法。

2.2.__init__()。

Python是一种面向对象的编程语言。所以,类必须有一个构造函数。这个要求是通过__init__()方法来实现的。当我们创建一个类并想给它一些初始参数时。初始化方法为我们完成了这项任务。

代码

class Sample:        
    def __init__(self, parameter):
        print("init invoked", parameter)

obj = Sample("a")

输出

解释。

  1. 创建/重写 __init__() 方法。插入self参数,向解释器表明这是一个类方法。
  2. 插入必要的参数。
  3. 然后使用print()函数打印该参数。
  4. 之后,创建一个对象。
  5. 当我们运行代码时,我们得到的输出是 "init invoked a",这说明解释器调用init()并打印了该参数。

3.3. __str__()

这个方法帮助我们根据我们的要求来显示对象。因此,让我们说,当我们创建一个对象并试图打印它时。print()函数会显示该对象的内存位置。如果我们想修改,我们可以这样做。__str__()函数给出了该对象的一个很好的表示。

代码 (在使用 __str__() 之前)

class Student:
    def __init__(self, name, roll_no):
        self.name = name
        self.roll_no = roll_no
        
stud_1 = Student("Suresh", 1)
print(stud_1) 

输出

<__main__.Student object at 0x0000023E2CF37CA0>

代码 (使用 __str__()之后):

class Student:
    def __init__(self, name, roll_no):
        self.name = name
        self.roll_no = roll_no
        
    def __str__(self):
        return ("{} {}".format(self.name, self.roll_no))
        
stud_1 = Student("Suresh", 1)
print(stud_1) 

输出

酷吧!现在我们也可以使用类似的方法。我们可以根据我们的需要来格式化对象。

4.4. __repr__()

类似于 __str__() ,我们可以使用 __repr__ 函数来装饰对象。代码类似于 __str__() 的实现。

class Student:
    def __init__(self, name, roll_no):
        self.name = name
        self.roll_no = roll_no
        
    def __repr__(self):
        print("repr invoked")
        return ("{} {}".format(self.name, self.roll_no))
        
stud_1 = Student("Suresh", 1)
print(stud_1) 

输出

5.__sizeof__()

当我们创建一个类时,解释器从不给它分配内存。它将内存分配给对象。如果我们想知道分配给该对象的内存,那么我们可以调用或覆盖 __sizeof__() 函数并传递我们的对象。这也会返回一个 list =, tuple, dictionary 对象的大小。

代码

class Student:
    def __init__(self, name, roll_no):
        self.name = name
        self.roll_no = roll_no
        
stud_1 = Student("Suresh", 1)
print("Size of student class object: ", stud_1.__sizeof__()) 

list_1 = [1, 2, 3, 4]
tup_1 = (1, 2, 3, 4, 5)
dict_1 = {"a":1, "b":2, "c":3, "d":4}
print("Size of list: ", list_1.__sizeof__())
print("Size of tuple: ", tup_1.__sizeof__())
print("Size of dictionary: ", dict_1.__sizeof__())

输出

Size of student class object:  32
Size of list object:  104
Size of tuple object:  64
Size of dictionary object:  216

6.__add__()

这个神奇的方法与它的名字特别相似。它添加两个变量。对于一个整数,它返回总和;对于一个字符串,它返回它们的连接结果。

代码

class Numbers:
    def __init__(self, a, b):
        self.a = a
        self.b = b
        
    def __add__(self):
        print("__add__ invoked")
        return self.a + self.b

num = Numbers(3, 4)
num_2 = Numbers("a", "b")
print(num.__add__())
print(num_2.__add__())

输出

__add__ invoked
7
__add__ invoked
ab

7.__reduce__()

这个神奇的方法返回一个类的所有参数的集合或字典,并以key: value格式返回它们的值。这可以直接使用对象名称与点运算符来调用。因此,当我们创建一个类并将其实例化并赋予一些值时。该函数将以在声明类时给出的参数名称来返回它。

代码

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.sal = salary
        
emp = Employee("Shrinivas", 150000)
print(emp.__reduce__())

输出

(<function _reconstructor at 0x0000023E22892EE0>, (<class '__main__.Employee'>, <class 'object'>, None), {'name': 'Shrinivas', 'sal': 150000})

代码 (在覆盖了__reduce__()之后)

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.sal = salary
        
    def __reduce__(self):
        return self.name, self.sal
        
emp = Employee("Shrinivas", 150000)
print(emp.__reduce__())


输出

解释

当我们覆盖并试图返回参数时,我们只得到它们在一个集合中的值。

8.8. __hash__()。

__hash__()函数返回存储在堆内存中的对象的特定哈希值。我们可以覆盖它或者使用对象名称来调用它。哈希值对于获取计算机中任何随机元素的内存地址非常有用。所有的编程语言都使用哈希值,以达到简单化和内存分配的目的。

代码

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.sal = salary
    
    def __hash__(self):
        return super().__hash__()
        
emp = Employee("Shrinivas", 150000)
print(emp.__hash__())


输出

代码:

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.sal = salary
    
emp = Employee("Shrinivas", 150000)
print(emp.__hash__())


输出:代码:输出。

9.__getattribute__(name)

这个函数返回一个类的属性值,如果它存在的话。我们需要调用该函数,并使用self 关键字传递我们分配给类参数的属性。例如,如果我们将salary 的值分配给self.sal ,我们需要在 __getattribute__() 函数中调用sal

代码

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.sal = salary
        
    def __getattribute__(self, name):
        return super().__getattribute__(name)
        
emp = Employee("Ravi", 500000)
print(emp.__getattribute__("sal"))

输出

解释

在这个函数中,"self.sal " 被分配到Employee类的工资 参数中。该函数将其值作为存在于该类中的属性返回。如果它不存在,该函数返回一个错误信息。

10.__setattr__(name, value)。

顾名思义,这个神奇的方法在我们定义对象时帮助我们改变一个属性的值。不需要覆盖**__getattribute__() 和 __setattr__()**函数。只要使用创建的对象来调用它们。

代码

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.sal = salary

        
emp = Employee("Ravi", 500000)
emp.__setattr__("name", "Suresh")
emp.__setattr__("sal":600000)
print("The changed name of the employee is: ", emp.__getattribute__("name"))
print("The changed salary of the employee is: ", emp.__getattribute__("sal"))

        

输出

The changed name of the employee is: Suresh
The changed salary of the employee is: 600000

解释

  1. __setattr__()需要两个参数。
    1. 属性的名称
    2. 它的新值
  2. 然后它把这个特定的值分配给这个属性。
  3. 之后,为了检查分配给它的值,使用雇员对象和点运算符调用 __getattrbute__() 函数。 emp.__getattribute("name")。

要注意的是:这两个函数代替了Python中类的getter和setter方法。

总结

所以,我们看到了Python中一些神奇方法的深入实现。我希望这能帮助我们,使编程变得更容易。它们被证明是有助于快速实现和使用的代码片段。祝你的Python编程愉快🐍🐍😎。