第八章-面向对象(下)(8.4-8.8)

第八章 - 面向对象(下) (8.4-8.8)


8.4 类的约束


约束是对类的约束。

用一个例子说话:我们来实现一个网站的支付功能,如下:

class QQpay:
    def pay(self,money):
        print('使用qq支付%s元' % money)

class Alipay:
    def pay(self,money):
        print('使用阿里支付%s元' % money)

a = Alipay()
a.pay(100)

b = QQpay()
b.pay(200)

所以此时我们要用到对类的约束,对类的约束有两种:

1. 提取⽗类,然后在⽗类中定义好⽅法,在这个⽅法中什么都不⽤⼲,就抛⼀个异常就可以了,这样所有的⼦类都必须重写这个⽅法。否则,访问的时候就会报错。

2. 使⽤元类来描述⽗类,在元类中给出⼀个抽象⽅法,这样⼦类就不得不给出抽象⽅法的具体实现,也可以起到约束的效果。

先用第一种方式解决:

class Payment:
    """
    此类什么都不做,就是制定一个标准,谁继承我,必须定义我里面的方法。
    """
    def pay(self,money):
        raise Exception("你没有实现pay方法")

class QQpay(Payment):
    def pay(self,money):
        print('使用qq支付%s元' % money)

class Alipay(Payment):
    def pay(self,money):
        print('使用阿里支付%s元' % money)

class Wechatpay(Payment):
    def fuqian(self,money):
        print('使用微信支付%s元' % money)


def pay(obj,money):
    obj.pay(money)

a = Alipay()
b = QQpay()
c = Wechatpay()
n
pay(a,100)
pay(b,200)
pay(c,300)

第二种方式:引入抽象类的概念处理。

from abc import ABCMeta,abstractmethod

class Payment(metaclass=ABCMeta):    # 抽象类 接口类  规范和约束  metaclass指定的是一个元类
    @abstractmethod
    def pay(self):pass  # 抽象方法

class Alipay(Payment):
    def pay(self,money):
        print('使用支付宝支付了%s元'%money)

class QQpay(Payment):
    def pay(self,money):
        print('使用qq支付了%s元'%money)

class Wechatpay(Payment):
    # def pay(self,money):
    #     print('使用微信支付了%s元'%money)
    def recharge(self):pass

def pay(a,money):
    a.pay(money)

a = Alipay()
a.pay(100)
pay(a,100)    # 归一化设计:不管是哪一个类的对象,都调用同一个函数去完成相似的功能
q = QQpay()
q.pay(100)
pay(q,100)
w = Wechatpay()
pay(w,100)   # 到用的时候才会报错



# 抽象类和接口类做的事情 :建立规范
# 制定一个类的metaclass是ABCMeta,
# 那么这个类就变成了一个抽象类(接口类)
# 这个类的主要功能就是建立一个规范

总结: 约束. 其实就是⽗类对⼦类进⾏约束. ⼦类必须要写xxx⽅法. 在python中约束的⽅式和⽅法有两种:

  1. 使⽤抽象类和抽象⽅法, 由于该⽅案来源是java和c#. 所以使⽤频率还是很少的

  2. 使⽤⼈为抛出异常的⽅案. 并且尽量抛出的是NotImplementError. 这样比较专业, ⽽且错误比较明确.(推荐)


8.5 isinstance 与 issubclass


8.5.1 isinstance


isinstance(obj,cls) 检查是否obj是否是类 cls 的对象

class Foo(object):
     pass
  
obj = Foo()

# 判断a是否是b类(或者b类的派生类)实例化的对象
isinstance(obj, Foo)

8.5.2 issubclass


issubclass(sub, super) 检查sub类是否是 super 类的派生类

class Foo(object):
    pass
 
class Bar(Foo):
    pass

#  判断a类是否是b类(或者b的派生类)的派生类
issubclass(Bar, Foo)
  • 那么 list str tuple dict等这些类与 Iterble类 的关系是什么?
from collections import Iterable

print(isinstance([1,2,3], list))  # True
print(isinstance([1,2,3], Iterable))  # True
print(issubclass(list,Iterable))  # True

# 由上面的例子可得,这些可迭代的数据类型,list str tuple dict等 都是 Iterable的子类。

8.5.3 元类


按照Python的一切皆对象理论,类其实也是一个对象,那么类这个对象是从哪里实例化出来的呢?

print(type('abc'))
print(type(True))
print(type(100))
print(type([1, 2, 3]))
print(type({'name': '张三'}))
print(type((1,2,3)))
print(type(object))

class A:
    pass

print(isinstance(object,type))
print(isinstance(A, type))

type元类是获取该对象从属于的类,而type类比较特殊,Python原则是:一切皆对象,其实类也可以理解为'对象',而type元类又称作构建类,python中大多数内置的类(包括object)以及自己定义的类,都是由type元类创造的。

而type类与object类之间的关系比较独特:object是type类的实例,而type类是object类的子类,这种关系比较神奇无法使用python的代码表述,因为定义其中一个之前另一个必须存在。所以这个只作为了解。


8.6 反射


8.6.1 反射概念


反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。


8.6.2 面向对象反射


python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)

四个可以实现自省的函数

函数 含义
hasattr() 判断是否有这个功能
getattr() 获取这个功能
setattr() 增加/修改这个功能
delattr() 删除这个功能

下列方法适用于类和对象(一切皆对象,类本身也是一个对象)

class Foo:
    f = '类的静态变量'
    def __init__(self,name,age):
        self.name=name
        self.age=age

    def say_hi(self):
        print('hi,%s'%self.name)

obj=Foo('egon',73)

#检测是否含有某属性
print(hasattr(obj,'name'))
print(hasattr(obj,'say_hi'))

#获取属性
n=getattr(obj,'name')
print(n)
func=getattr(obj,'say_hi')
func()

print(getattr(obj,'aaaaaaaa','不存在啊')) #报错

#设置属性
setattr(obj,'sb',True)
setattr(obj,'show_name',lambda self:self.name+'sb')
print(obj.__dict__)
print(obj.show_name(obj))

#删除属性
delattr(obj,'age')
delattr(obj,'show_name')
delattr(obj,'show_name111')#不存在,则报错

print(obj.__dict__)

# 对实例化对象的示例
class Foo(object):
 
    staticField = "old boy"
 
    def __init__(self):
        self.name = 'wupeiqi'
 
    def func(self):
        return 'func'
 
    @staticmethod
    def bar():
        return 'bar'
 
print(getattr(Foo, 'staticField')) 
print(getattr(Foo, 'func')) 
print(getattr(Foo, 'bar')) 
import sys


def s1():
    print 's1'


def s2():
    print 's2'


this_module = sys.modules[__name__]

hasattr(this_module, 's1')
getattr(this_module, 's2')
#一个模块中的代码
def test():
    print('from the test')
"""
程序目录:
    module_test.py
    index.py
 
当前文件:
    index.py
"""
# 另一个模块中的代码
import module_test as obj

#obj.test()

print(hasattr(obj,'test'))

getattr(obj,'test')()

其他模块的示例

反射的应用:

了解了反射的四个函数。那么反射到底有什么用呢?它的应用场景是什么呢?

现在让我们打开浏览器,访问一个网站,你单击登录就跳转到登录界面,你单击注册就跳转到注册界面,等等,其实你单击的其实是一个个的链接,每一个链接都会有一个函数或者方法来处理。

class User:
    def login(self):
        print('欢迎来到登录页面')
    
    def register(self):
        print('欢迎来到注册页面')
    
    def save(self):
        print('欢迎来到存储页面')


while 1:
    choose = input('>>>').strip()
    if choose == 'login':
        obj = User()
        obj.login()
    
    elif choose == 'register':
        obj = User()
        obj.register()
        
    elif choose == 'save':
        obj = User()
        obj.save()
        
class User:
    def login(self):
        print('欢迎来到登录页面')
    
    def register(self):
        print('欢迎来到注册页面')
    
    def save(self):
        print('欢迎来到存储页面')

user = User()
while 1:
    choose = input('>>>').strip()
    if hasattr(user,choose):
        func = getattr(user,choose)
        func()
    else:
        print('输入错误。。。。')

8.7 函数与方法


8.7.1 函数与方法的区别


函数和方法的不同:

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

我们或许在日常中会口语化称呼函数和方法时不严谨,但是我们心中要知道二者之间的区别。

在其他语言中,如Java中只有方法,C中只有函数,C++么,则取决于是否在类中。

  • 通过打印函数(方法)名确定
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>>
  • 通过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

8.8 双下方法


定义:双下方法是特殊方法,他是解释器提供的,由双下划线加方法名加双下划线 __方法名__ 的具有特殊意义的方法,双下方法主要是python源码程序员使用的,我们在开发中尽量不要使用双下方法,但是深入研究双下方法,更有益于我们阅读源码。

调用:不同的双下方法有不同的触发方式,就好比盗墓时触发的机关一样,不知不觉就触发了双下方法。


8.8.1 __init__()构造方法


在创建类时,我们可以手动添加一个 __init__() 方法,从形式上看,__ init __() 是一个函数。事实上,它是一个特殊的函数,该方法是一个特殊的类实例方法,称为构造方法(或构造函数)。

__init__ 方法是一个特殊的方法(init是单词初始化initialization的省略形式),构造方法用于创建对象时使用,每当创建一个类的实例对象时,Python 解释器都会自动调用它。Python 类中,手动添加构造方法的语法格式如下:

class 类:
    def __init__(self, 参数):
        self.属性 = 参数     ...

对象 = 类(参数)

注意,此方法的方法名中,开头和结尾各有 2 个下划线,且中间不能有空格。Python 中很多这种以双下划线开头、双下划线结尾的方法,都具有特殊的意义。

另外,__init__() 方法可以包含多个参数,但必须包含一个名为 self 的参数,且必须作为第一个参数。也就是说,类的构造方法最少也要有一个 self 参数。例如,仍以 TheFirstDemo 类为例,添加构造方法的代码如下所示:

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

    #构造方法
    def __init__(self):
        print("调用构造方法")
    
    # 下面定义了一个类属性
    add = '这是类的属性'
    
    # 下面定义了一个say方法
    def say(self, content):
        print(content)

# 实例化
zhangsan = TheFirstDemo()
# 调用构造方法

注意,即便不手动为类添加任何构造方法,Python 也会自动为类添加一个仅包含 self 参数的构造方法。

仅包含 self 参数的 __init__() 构造方法,又称为类的默认构造方法。

显然,在创建 zhangsan 这个对象时,隐式调用了我们手动创建的 __init__() 构造方法。

不仅如此,在 __init__() 构造方法中,除了 self 参数外,还可以自定义一些参数,参数之间使用逗号“,”进行分割。例如,下面的代码在创建 __init__() 方法时,额外指定了 2 个参数:

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

#创建 add 对象,并传递参数给构造函数
add = CLanguage("百度","http://www.baidu.com")

注意,由于创建对象时会调用类的构造方法,如果构造函数有多个参数时,需要手动传递参数,传递方式如代码中所示(后续章节会做详细讲解)。

可以看到,虽然构造方法中有 selfnameadd 3 个参数,但实际需要传参的仅有 name 和 add,也就是说,self 不需要手动传递参数。

  • self参数

在定义类的过程中,无论是显式创建类的构造方法,还是向类中添加实例方法,都要求将 self 参数作为方法的第一个参数。例如,定义一个 Person 类:

class Person:
    def __init__(self):
        print("正在执行构造方法")

    # 定义一个study()实例方法
    def study(self,name):
        print(name,"正在学Python")

那么,self 到底扮演着什么样的角色呢?

事实上,Python 只是规定,无论是构造方法还是实例方法,最少要包含一个参数,并没有规定该参数的具体名称。之所以将其命名为 self,只是程序员之间约定俗成的一种习惯,遵守这个约定,可以使我们编写的代码具有更好的可读性(大家一看到 self,就知道它的作用)。

那么,self 参数的具体作用是什么呢?打个比方,如果把类比作造房子的图纸,那么类实例化后的对象是真正可以住的房子。根据一张图纸(类),我们可以设计出成千上万的房子(类对象),每个房子长相都是类似的(都有相同的类变量和类方法),但它们都有各自的主人,那么如何对它们进行区分呢?

当然是通过 self 参数,它就相当于每个房子的门钥匙,可以保证每个房子的主人仅能进入自己的房子(每个类对象只能调用自己的类变量和类方法)。

如果你接触过其他面向对象的编程语言(例如 C++),其实 Python 类方法中的 self 参数就相当于 C++ 中的 this 指针。

也就是说,同一个类可以产生多个对象,当某个对象调用类方法时,该对象会把自身的引用作为第一个参数自动传给该方法,换句话说,Python 会自动绑定类方法的第一个参数指向调用该方法的对象。如此,Python解释器就能知道到底要操作哪个对象的方法了。

因此,程序在调用实例方法和构造方法时,不需要手动为第一个参数传值。例如,更改前面的 Person 类,如下所示:

class Person:
    def __init__(self):
        print("正在执行构造方法")

    # 定义一个study()实例方法
    def study(self):
        print(self,"正在学Python")

zhangsan = Person()
zhangsan.study()
lisi = Person()
lisi.study()

上面代码中,study() 中的 self 代表该方法的调用者,即谁调用该方法,那么 self 就代表谁。因此,该程序的运行结果为:

正在执行构造方法
<__main__.Person object at 0x0000021ADD7D21D0> 正在学Python
正在执行构造方法
<__main__.Person object at 0x0000021ADD7D2E48> 正在学Python

另外,对于构造函数中的 self 参数,其代表的是当前正在初始化的类对象。举个例子:

    class Person:
        name = "xxx"
        def __init__(self,name):
            self.name=name
    zhangsan = Person("zhangsan")
    print(zhangsan.name)
    lisi = Person("lisi")
    print(lisi.name)

运行结果为:

zhangsan
lisi

可以看到,zhangsan 在进行初始化时,调用的构造函数中 self 代表的是 zhangsan;而 lisi 在进行初始化时,调用的构造函数中 self 代表的是 lisi。

值得一提的是,除了类对象可以直接调用类方法,还有一种函数调用的方式,例如:

    class Person:
        def who(self):
            print(self)
    zhangsan = Person()
    #第一种方式
    zhangsan.who()
    #第二种方式
    who = zhangsan.who
    who()#通过 who 变量调用zhangsan对象中的 who() 方法

运行结果为:

<__main__.Person object at 0x0000025C26F021D0>
<__main__.Person object at 0x0000025C26F021D0>

显然,无论采用哪种方法,self 所表示的都是实际调用该方法的对象。

总之,无论是类中的构造函数还是普通的类方法,实际调用它们的谁,则第一个参数 self 就代表谁。

8.8.2 __new__()


__new__() 方法主要存在于Python2的新式类和Python3中。它是负责创建类实例的静态方法。

当Python实例化一个对象时,首先调用 __new__() 方法构造一个类的实例,并为其分配对应类型的内存空间,该实例的内存地址就是它的唯一标识符。然后再调用 __init__() 方法对实例进行初始化,通常是对该实例的属性进行初始化。

实例1:先调用 __new__() 方法再调用 __init__() 方法

class Person(object):
    
    def __new__(cls):
        print("__new__ called")
        return super().__new__(cls)
    
    def __init__(self):
        print("__init__ called")

a = Person()
# __new__ called
# __init__ called

实例2:__new__() 方法构造一个类实例,并将该实例传递给自身的 __init__() 方法,即 __init__() 方法的self参数。

class Person(object):
    
    def __new__(cls):
        print("__new__ called")
        instance = super().__new__(cls)
        print(type(instance))
        print(instance)
        print(id(instance))
        return instance
    
    def __init__(self):
        print("__init__ called")
        print(id(self))

b = Person()
__new__ called
<class '__main__.Person'>
<__main__.Person object at 0x1093c1580>
4449899904
__init__ called
4449899904

实例3:如果 __new__() 方法不返回任何实例的话,__init__() 方法将不会被调用。

class Person(object):
    
    def __new__(cls):
        print("__new__ called")

    def __init__(self):
        print("__init__ called")

c = Person()
__new__ called

实例4:如果 __new__() 方法返回一个其他类的实例的话,那它自身的 __init__() 方法将不会被调用。而且, __new__() 方法将会初始化一个其他类的对象。

class Animal(object):

    def __init__(self):
        pass

class Person(object):
    
    def __new__(cls):
        print("__new__ called")
        return Animal()

    def __init__(self):
        print("__init__ called")

d = Person()
print(type(d))
print(d)
__new__ called
<class '__main__.Animal'>
<__main__.Animal object at 0x10fea3550>

实例5:如果重写 __new__() 方法时,除了cls参数外不再设置其他参数的话,将无法用 __init__() 方法来设置初始化参数。

class Person(object):
    
    def __new__(cls):
        print("__new__ called")
        instance = super().__new__(cls)
        return instance
    
    def __init__(self, name):
        print("__init__ called")
        self.name = name

e = Person("Eric")
print(e.name)
Traceback (most recent call last):
  File "example.py", line 102, in <module>
    e = Person("Eric")
TypeError: __new__() takes 1 positional argument but 2 were given

实例6:在重写 __new__() 方法时,需要在参数中加入 *args , **kwargs ,或者显式地加入对应的参数,才能通过 __init__() 方法初始化参数。

class Person(object):
    
    def __new__(cls, *args,**kwargs):  # Or def __new__(cls, name)
        print("__new__ called")
        instance = super().__new__(cls)
        return instance
    
    def __init__(self, name):
        print("__init__ called")
        self.name = name

e = Person("Eric")
print(e.name)
__new__ called
__init__ called
Eric
  • 单例模式

单例模式是一种常用的软件设计模式。

在它的核心结构中只包含一个实例对象被称为单例类的特殊类。

通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。

【采用单例模式动机、原因】

对于系统中的某些类来说,只有一个实例很重要。
例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。
如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;
如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。

如何保证一个类只有一个实例并且这个实例易于被访问呢?
定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。

【单例模式优缺点】

【优点】

一、实例控制

单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。

二、灵活性

因为类控制了实例化过程,所以类可以灵活更改实例化过程。

【缺点】

一、开销

虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。

二、可能的开发混淆

使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。

三、对象生存期

不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用
class Singleton:
    def __new__(cls, *args, **kw):
        if not hasattr(cls, '_instance'):
            cls._instance = object.__new__(cls)
        return cls._instance

one = Singleton()
two = Singleton()

two.a = 3
print(one.a)
# 3
# one和two完全相同,可以用id(), ==, is检测
print(id(one))
# 29097904
print(id(two))
# 29097904
print(one == two)
# True
print(one is two)

8.8.3 __del__() 析构方法


析构方法,当对象在内存中被释放时,自动触发执行。

注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。

__del__() 方法称为 "析构方法",用于实现对象被销毁时所需的操作。比如:释放对象占用的资源,例如:打开的文件资源、网络连接等。

Python 实现自动的垃圾回收,当对象没有被引用时(引用计数为 0),由垃圾回收器调用__del__()方法。

我们也可以通过 del 语句删除对象,从而保证调用 __del__() 方法。系统会自动提供 __del__() 方法,一般不需要自定义析构方法。

python的构造方法/构造函数:

    用于初始化类的内容部状态,Python提供的构造函数式 __init__() ,也就是当该类被实例化的时候就会执行该函数,

    __init__() 方法是可选的,如果不提供,Python 会给出默认的__init__方法。

python的析构方法/析构函数:
    
    __del__()就是一个析构函数了,当使用del 删除对象时,会调用他本身的析构函数,另外当对象在某个作用域中调用完毕,
    
    在跳出其作用域的同时析构函数也会被调用一次,这样可以用来释放内存空间。  


    __del__() 也是可选的,如果不提供,则Python 会在后台提供默认析构函数

如果要显式的调用析构函数,可以使用del关键字: del obj

class Foo:

    def __del__(self):
        print('执行我啦')

f1=Foo()
del f1
print('------->')

#输出结果
# 执行我啦
# ------->

8.8.4 __str__()


如果一个类中定义了 __str__() 方法,那么在打印对象时,默认输出该方法的返回值。将__str__() 返回的内容以字符串形式输出。

  • 通过 print() 函数触发
class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def __str__(self):
        return '姓名是{},年龄是{}'.format(self.name,self.age)
        

one = Person('小红',20)
print(one)

如上所示,定义一个Person类,创建对象one后使用print输出对象实例时,默认打印对象的内存地址。

上述代码使用了__str__()方法,当调用print()函数时,会找到实例对象one的__str__()方法,输出__str__()方法的返回值内容。

  • 通过 str() 函数触发
class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def __str__(self):
        return '姓名是{},年龄是{}'.format(self.name,self.age)


one = Person('小红',20)
words = str(one)
print(worlds,type(words))

使用str()函数触发__str__()方法输出的内容与直接调用 print() 函数触发的结果看似相同,但是过程是不一样的。

总结:

  • 使用 print() 函数触发:触发时,会自动寻找实例对象的方法,若没有则直接按照默认内容输出,有则输出__str__()方法的返回值。
  • 使用 str() 函数触发:如上述代码所示,使用str()触发时,worlds类型是str,是一个字符串,打印输出的是一个字符串words。

8.8.6 __repr__() 打印,转换


通常情况下,直接输出某个实例化对象,本意往往是想了解该对象的基本信息,例如该对象有哪些属性,它们的值各是多少等等。但默认情况下,我们得到的信息只会是“类名+object at+内存地址”,对我们了解该实例化对象帮助不大。

那么,有没有可能自定义输出实例化对象时的信息呢?答案是肯定,通过重写类的 __repr__() 方法即可。事实上,当我们输出某个实例化对象时,其调用的就是该对象的 __repr__() 方法,输出的是该方法的返回值。

__init__(self) 的性质一样,Python 中的每个类都包含 __repr__() 方法,因为 object 类包含 __reper__() 方法,而 Python 中所有的类都直接或间接继承自 object 类。

默认情况下,__repr__() 会返回和调用者有关的 “类名+object at+内存地址”信息。当然,我们还可以通过在类中重写这个方法,从而实现当输出实例化对象时,输出我们想要的信息。

class Person:
    def __init__(self):
        self.name = "张三"
        self.add = "www.zhangsan.com"

    def __repr__(self):
        return "信息:[name="+ self.name +",add=" + self.add +"]"

p1 = Person()
print(p1)

8.8.7 __len__() 获取长度


首先 __len__() 的作用是返回容器中元素的个数,要想使 len() 函数成功执行,必须要在类中定义 __len__() 。而 len() 的执行指的是在命令窗口输入 len() ,而在程序中一般情况下即使不定义 __len__() 程序中的 len() 函数也能成功执行。个人认为其实二者并没有多大差别,主要还是看在命令窗口的行为,你是输入的是len(对象)还是len(对象.属性)。

class B:
    def __len__(self):
        print(666)

b = B()
len(b) # len 一个对象就会触发 __len__方法。

class A:
    def __init__(self):
        self.a = 1
        self.b = 2

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

__len__() 函数,*args是可变长度的参数,它接收到数据后打包成元组,再利用for循环将数据传递给列表。当在命令窗口写入len(c1)时,由于定义的__len__()的返回值是返回具体的实例对象的列表长度,所以在命令窗口直接输入len(c1)就可以输出列表的长度。

如果没有定义 __len__() ,那在命令窗口输入len(c1)会提示出错,显然错误原因是因为CountList类中并没有len()的定义。因为len()必须接收一个具体的实例化对象的属性,如果改成len(c1.values)也能成功执行!

对于类而言,len()函数是没有办法直接计算类的长度的,因为在类中包含着众多的属性以及方法,是一种抽象的实体。

如果在类中没有定义 __len__() 方法来指明程序到底该计算哪个属性的长度时,在终端我们必须采用len(对象.属性)才能得到我们想要的结果。如果终端直接采用len(对象)的方法,显然程序会报错,提示类并没有len()方法。类不同于字符串、元组、列表、字典等数据类型,对于后者,它们是实实在在能通过len()方法直接计算出长度,是具体的实体。

但是就封装性而言,还是定义 __len__() 比较好,因为用户无需知道具体的属性是什么,只需要一个实例化对象即可。如果属性是私有的,那么无疑定义 __len__() 是最好的办法。


8.8.8 __dir__()


前面在介绍 Python 内置函数时,提到了 dir() 函数,通过此函数可以某个对象拥有的所有的属性名和方法名,该函数会返回一个包含有所有属性名和方法名的有序列表。

举个例子:

class CLanguage:
    def __init__ (self,):
        self.name = "C语言中文网"
        self.add = "http://c.biancheng.net"
    def say():
        pass

clangs = CLanguage()
print(dir(clangs))

程序运行结果为:

['__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__', 'add', 'name', 'say']

*注意: 通过 dir() 函数,不仅仅输出本类中新添加的属性名和方法(最后 3 个),还会输出从父类(这里为 object 类)继承得到的属性名和方法名。

值得一提的是,dir() 函数的内部实现,其实是在调用参数对象 __dir__() 方法的基础上,对该方法返回的属性名和方法名做了排序。

所以,除了使用 dir() 函数,我们完全可以自行调用该对象具有的 __dir__() 方法:

class Zs:
    def __init__ (self,):
        self.name = "张三"
        self.add = "www.zhangsan.com"

    def say():
        pass

clangs = Zs()
print(clangs.__dir__())

程序运行结果为:

['name', 'add', '__module__', '__init__', 'say', '__dict__', '__weakref__', '__doc__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']

显然,使用 __dir__() 方法和 dir() 函数输出的数据是相同,仅仅顺序不同。


8.8.9 __dict__()


在 Python 类的内部,无论是类属性还是实例属性,都是以字典的形式进行存储的,其中属性名作为键,而值作为该键对应的值。

为了方便用户查看类中包含哪些属性,Python 类提供了 __dict__() 属性。需要注意的一点是,该属性可以用类名或者类的实例对象来调用,用类名直接调用 __dict__(),会输出该由类中所有类属性组成的字典;而使用类的实例对象调用 __dict__(),会输出由类中所有实例属性组成的字典。

举个例子:

class Zs:
    a = 1
    b = 2
    def __init__ (self):
        self.name = "张三"
        self.add = "www.zhangsan.com"

#通过类名调用__dict__
print(Zs.__dict__)

#通过类实例对象调用 __dict__
clangs = Zs()
print(clangs.__dict__)

程序输出结果为:

{'__module__': '__main__', 'a': 1, 'b': 2, '__init__': <function CLanguage.__init__ at 0x0000022C69833E18>, '__dict__': <attribute '__dict__' of 'CLanguage' objects>, '__weakref__': <attribute '__weakref__' of 'CLanguage' objects>, '__doc__': None}
{'name': '张三', 'add': 'www.zhangsan.com'}

不仅如此,对于具有继承关系的父类和子类来说,父类有自己的 __dict__,同样子类也有自己的 __dict__,它不会包含父类的 __dict__。例如:

通过子类直接调用的 __dict__ 中,并没有包含父类中属性;同样,通过子类对象调用的 __dict__,也没有包含父类对象拥有的 name 和 add 实例属性。

除此之外,借助由类实例对象调用 __dict__ 属性获取的字典,可以使用字典的方式对其中实例属性的值进行修改,例如:

注意,无法通过类似的方式修改类变量的值。
class CLanguage:
    a = "aaa"
    b = 2
    def __init__ (self):
        self.name = "C语言中文网"
        self.add = "http://c.biancheng.net"
#通过类实例对象调用 __dict__
clangs = CLanguage()
print(clangs.__dict__)
clangs.__dict__['name'] = "Python教程"
print(clangs.name)

程序运行结果为:

{'name': 'C语言中文网', 'add': 'http://c.biancheng.net'}
Python教程

8.8.10 __call__()


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

注:构造方法 __new__() 的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

class Foo:

    def __init__(self):
        pass
    
    def __call__(self, *args, **kwargs):

        print('__call__')


obj = Foo() # 执行 __init__
obj()       # 执行 __call__

8.8.11 __eq__()


class A:
    def __init__(self):
        self.a = 1
        self.b = 2

    def __eq__(self,obj):
        if  self.a == obj.a and self.b == obj.b:
            return True
a = A()
b = A()
print(a == b)

8.8.12 __item__() 系列


__getitem__() __setitem__() __delitem__()

class Foo:
    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=Foo('sb')

# 添加属性
f1['age']=18
f1['age1']=19

# 删除对象属性
del f1.age1
del f1['age']

f1['name']='alex'
print(f1.__dict__)

8.8.13 __hash__()


hash() 调用对象会返回 __hash__ 方法 return 的值,必须返回 integer整数

class A:
    def __init__(self):
        self.a = 1
        self.b = 2

    def __hash__(self):
        return hash(str(self.a)+str(self.b))

a = A()
print(hash(a))

8.8.14 上下文管理器相关


__enter__ __exit__

# 如果想要对一个类的对象进行with  as 的操作 不行。
class A:
    def __init__(self, text):
        self.text = text

with A('大爷') as f1:
    print(f1.text)
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
posted @ 2023-07-04 20:22  WNAG_zw  阅读(19)  评论(0)    收藏  举报