21. 自定义类与双下划线方法

2021/11/5 23:11:26

本文主要是介绍21. 自定义类与双下划线方法,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

类的内置方法

class People:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def __str__(self):
        return '<姓名:%s 年龄:%s>' %(self.name,self.age) 
'''
需要注意的是,str方法返回值必须是字符串
双下划线方法能够在满足条件时自动执行,str方法能够在执行str函数或者print方法时打印返回值
p=People('lilei',18)
print(p)  # 触发p.__str__(),将返回值打印出来
	<Name:lili Age:18>
'''

# 同理del方法是在del 对象时自动触发

class People:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def __str__(self):
        return '<姓名:%s 年龄:%s>' %(self.name,self.age) 

p=People('lilei',18)

其他的方法介绍:

# __repr__方法:
1.在执行repr()方法或者是交互式解释器的时候会打印其返回值
2.如果str方法没有被定义,那么打印时也会打印repr的返回值
3.返回值也必须是字符串,否则会报错
def __repr__(self):
	return '返回repr'

# __del__方法:一般用不上,因为python自带的垃圾回收机机制能有效地回收内存
1.执行del对象时触发内部代码
def __del__(self):
	print('执行该部分的代码')

# __len__方法:
1.执行len(对象)时执行内部代码
class A:
    def __init__(self):
        self.a = 1
        self.b = 2

    def __len__(self):
        return len(self.__dict__)
a = A()
print(len(a))

# __new__方法:
1.只有继承于object的新式类才能有__new__方法,__new__方法在创建类实例对象时由Python解释器自动调用,一般不用自己定义,Python默认调用该类的直接父类的__new__方法来构造该类的实例,如果该类的父类也没有重写__new__,那么将一直按此规矩追溯至object的__new__方法
2.__new__至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供;__new__必须要有返回值,返回实例化出来的实例,可以return父类new出来的实例,或直接是object的new出来的实例。
class Person(object):
    def __init__(self):
        print("__init__")
        self.name="张三"
    def __new__(cls):
        print('__new__')
        ob = object.__new__(cls)  # ob为Person实例对象
        print(ob)
        return ob
p1=Person()
3.p1=Person()该语句主要做了以下工作:
首先调用Person的__new__方法,该方法通过object.__new__(cls)创建了Person实例对象,并返回。最后调用了该Person实例对象的__init__方法进行初始化
4.简单的可以理解为父类实例化时得到该类,然后该类进行init初始化,如果该类本身返回的并不是父类实例化的列本身,那么将不会进行实例化
class Dog(object):
    def __init__(self):
        self.name="旺财"
        print("__init__")

class Person(object):
    def __init__(self):
        self.name="张三"
        print("__init__")
    def __new__(cls):
        print('__new__')
        ob = object.__new__(Dog)  # 父类实例化的是Dog类,不会执行People类的init方法
        ob.__init__()
        return ob  # 返回的是DOG类的创建,接下来进行DOG类的init初始化,产生对象

# __call__方法:该方法能够让类的实例对象像函数一样被调用
1.类实例化得到对象,如果类中实现了call方法,那么对象也能传参进行过实例化
class People:
    # 定义__call__方法
    def __call__(self,name):
        print("调用__call__()方法",name)

p1 = People()
p1("李白")  # 由于p1的父类实现了call方法,使得p1也能够进行实例化
p1.__call__("李白")
# call方法弥补hasattr的不足
hasattr() 函数能够查找类的实例对象中是否包含指定名称的属性或者方法,但该函数有一个缺陷,即它无法判断该指定的名称,到底是类属性还是类方法
要解决这个问题,我们可以借助可调用对象的概念,类实例对象包含的方法,其实也属于可调用对象,但类属性却不是
class People:
    def __init__ (self):
        self.name = "李白"
    def say(self):
        print("你好")
p1 = People()
if hasattr(p1,"name"):
    print(hasattr(p1.name,"__call__"))  # false
if hasattr(p1,"say"):
    print(hasattr(p1.say,"__call__"))  # true
# call、init、new方法比较
class A(object):
    def __init__(self, x):
        print 'x in __init__', x
    def __new__(cls, y):
        print 'y in __new__', y
        return super(A, cls).__new__(cls)
    def __call__(self, z):
        print 'z in __call__', z

A('123')('abc')
y in __new__ 123
x in __init__ 123
z in __call__ abc  # 对象进行实例化的时候才会调用类的call方法
    
    
# __item__系列(了解一下就行)
class Fool:
    def __init__(self,name):
        self.name=name
    def __getitem__(self, item):
        print(self.__dict__[item])
    def __setitem__(self, key, value):
        self.__dict__[key]=value
    def __delitem__(self, key):
        print('del obj[key]时,我执行')
        self.__dict__.pop(key)
    def __delattr__(self, item):
        print('del obj.key时,我执行')
        self.__dict__.pop(item)

f1=Fool('sb')
f1['age']=18
f1['age1']=19
del f1.age1
del f1['age']
f1['name']='alex'
print(f1.__dict__)

# 上下文管理器相关__enter__  __exit__
class A:
    
    def __init__(self, text):
        self.text = text
    
    def __enter__(self):  # 开启上下文管理器对象时触发此方法
        self.text = self.text + '您来啦'
        return self  # 将实例化的对象返回f1
    
    def __exit__(self, exc_type, exc_val, exc_tb):  # 执行完上下文管理器对象f1时触发此方法
        self.text = self.text + '拜拜'
        
with A('大爷') as f1:
    print(f1.text)
print(f1.text)

自定义文件管理器
class Diycontextor:
    def __init__(self,name,mode):
        self.name = name
        self.mode = mode
 
    def __enter__(self):
        print "Hi enter here!!"
        self.filehander = open(self.name,self.mode)
        return self.filehander
 
    def __exit__(self,*para):
        print "Hi exit here"
        self.filehander.close()
 
 
with Diycontextor('py_ana.py','r') as f:
    for i in f:
        print i

下面的内容就当拓展了:hash和eq方法

__eq__和__hash__方法:
判断(==)时实际上比较的是__eq__里面的内容

class Item:
  def __init__(self, val):
      self.val = val
  def __eq__(self, other):
      return self.val == other.val

first = Item('hello')
second = Item('hello)
print(first == second)  # True

如果不实现__eq__方法,那么自定义类型会调用默认的__eq__方法, 通过默认方法进行比较的相等条件相当严格,只有自己和自己比才会返回True

class Item:
  def __init__(self, val):
      self.val = val

first = Item('hello')
second = Item('hello')
print(first == second)  # False

因此,在需要进行自定义类型比较的时候,建议实现__eq__方法

谈及__eq__方法,就不得不说一下__hash__方法,也就是我们常说的哈希,什么是哈希呢,下面是官方文档的解释:
如果一个对象在其生命周期内有一个固定不变的哈希值 (这需要__hash__()方法) 且可以与其他对象进行比较操作 (这需要__eq__()方法) ,那么这个对象就是可哈希对象 (hashable) 。可哈希对象必须有相同的哈希值才算作相等。
由于字典 (dict) 的键 (key) 和集合 (set) 内部使用到了哈希值,所以只有可哈希 (hashable) 对象才能被用作字典的键和集合的元素。
所有python内置的不可变对象都是可哈希的,同时,可变容器 (比如:列表 (list) 或者字典 (dict) ) 都是不可哈希的。用户自定义的类的实例默认情况下都是可哈希的;它们跟其它对象都不相等 (除了它们自己) ,它们的哈希值来自id()方法。

Python中的对象分为可变和不可变对象,我们从另一个角度来看,可以分为不可哈希对象和可哈希对象。而不可哈希对象无法作为哈希集合的元素使用(这个hashable collections值得是set、frozenset和dict)。有时候,我们使用列表或者自定义对象作为字典的键,或者使用set进行元素去重的时候,会遇到unhashable type: xxx之类的问题,这类问题出现的原因就是字典的键或者集合中的元素类型为不可哈希类型。

下面讲述四种情况,深入理解一下两者的区别:
1. 只自定义__eq__
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __eq__(self, other):
        print(self.name, '使用了__eq__方法')
        return self.__dict__ == other.__dict__
 
person1 = Person('A', 20)
person2 = Person('B', 20)
person3 = Person('C', 30)
person4 = Person('A', 20)
 
print(person1 == person4)  # A使用了__eq__方法 true
print(person2 == person3)  # B使用了__eq__方法false
上面的例子说明,对象在进行双等号比较时,会调用内部的__eq__方法进行比较

2. 重写__eq__()方法不重写__hash__()方法,那么这个类无法作为哈希集合的元素使用(这个hashable collections指的是set、frozenset和dict)

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

    def __eq__(self, other):
        print(self.name, '使用了__eq__方法')
        return self.__dict__ == other.__dict__
 
person1 = Person('zs', 20)
person2 = Person('ls', 20)
person3 = Person('ww', 30)
person4 = Person('zs', 20)
 
set1 = {person1, person2, person3, person4}  # 内部元素要求是可哈希对象,简单的说就是__hash__方法有返回值
print(set1)
# 结果报错,因为重写__eq__()方法后会默认把__hash__赋为None(文档上面有说)
class A:
    def __eq__(self, other):
        pass
a = A()
print(a.__hash__)  # None
hash(a)
# TypeError: unhashable type: 'A'

3. 如果自定义类重写了__hash__()方法没有重写__eq__()方法
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
    def __hash__(self):
        print(self.name, '使用了__hash__方法')
        return hash(self.name)

person1 = Person('zs', 20)
person2 = Person('ls', 20)
person3 = Person('ww', 30)
person4 = Person('zs', 20)

set1 = {person1, person2, person3, person4}
print(set1)
不报错,但不合理———person1与person4是同一个人,却都被添加到集合,违背了集合(元素不能有重复)的理念:

但是,为什么person4也会被添加进去呢,主要是因为当发现hash出的值相同时,就需要__eq__进行下一步判断。我们重写了__hash__却没重写__eq__,默认调用了object的__eq__,而默认的__eq__比较的是对象地址,person1与person4地址不同,所以将最后的person4加到了集合

4. hash与eq都重写

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
    def __hash__(self):
        print(self.name, '使用了__hash__方法')
        return hash(self.name)
 
    def __eq__(self, other):
        print(self.name, '使用了__eq__方法')
        return self.__dict__.get("name") == 

person1 = Person('zs', 20)
person2 = Person('ls', 20)
person3 = Person('ww', 30)
person4 = Person('zs', 20)
 
set1 = {person1, person2, person3, person4}
print(set1)  # 报错,person1和person4是一样的不能被添加到集合中

原理:因为要加入集合,所以每个person都会调用__hash__,比较每个对象内容中的name属性。当hash(self.name)值不同时,name肯定不同,所以就不会再调用,而当hash(self.name)值相同时,我们不能盲目认为不能加入集合,因为不同的name可能hash出一样的值(算法内部可能会出现相同),这时候就会再调用我们重写的__eq__帮忙来进一步判断所有内容是否相同了
'''
总结:
	eq方法是执行双等号判断时自定触发,比较里面的值,返回true或false
	hash方法是判断是不是可哈希对象,能否作为集合或者字典的键
	只定义eq方法时会自动将hash返回None(即变成不可哈希对象)
	只定义hash时,虽然是可哈希对象,但是当当两个对象的哈希值一样时,会自动调用对象的eq方法进行进一步判断,eq结果不等时依旧会被判定为可哈希对象,作为集合或字典的键
'''

方法与函数的区别

如何确定函数与方法

  1. 通过打印函数(方法)名
def func():
    pass
print(func)   # <function func at 0x00000260A2E690D0>

class A:
    def func(self):
        pass 
print(A.func)  # <function A.func at 0x0000026E65AE9C80>
obj = A()
print(obj.func)  # <bound method A.func of <__main__.A object at 0x00000230BAD4C9E8>>
  1. 通过types模块验证
from types import FunctionType
from types import MethodType

def func():
    pass


class A:
    def func(self):
        pass

obj = A()


print(isinstance(func,FunctionType))  # True
print(isinstance(A.func,FunctionType))  # True
print(isinstance(obj.func,FunctionType))  # False
print(isinstance(obj.func,MethodType))  # True

需要注意的是,静态方法实际上是函数

from types import FunctionType
from types import MethodType

class A:
    
    def func(self):
        pass
    
    @classmethod
    def func1(self):
        pass
    
    @staticmethod
    def func2(self):
        pass
obj = A()

print(isinstance(A.func2,FunctionType))  # True
# print(isinstance(obj.func2,FunctionType))  # True

函数与方法的区别(了解)

  1. 函数的是显式传递数据的。如我们要指明为len()函数传递一些要处理数据
  2. 函数则跟对象无关
  3. 方法中的数据则是隐式传递的
  4. 方法可以操作类内部的数据
  5. 方法跟对象是关联的。如我们在用strip()方法是,是不是都是要通过str对象调用,比如我们有字符串s,然后s.strip()这样调用。是的,strip()方法属于str对象


这篇关于21. 自定义类与双下划线方法的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程