Python 类中的常用内置方法__init__和__new__(一)

查看对象的所有属性和方法 — dir

在 Python 中一切皆对象,我们可以通过内置函数 dir() 来获取对象的所有属性和方法。

>>> class Student:
...     pass
...
>>> dir(Student)
['__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__']
>>>

注意:以双下划线开头和结尾(__方法名__)的格式的方法是 Python 针对对象提供的内置属性或方法。

类中四个常用的内置方法:

__new__创建对象时,被自动调用。
__init__对象被初始化时,自动调用。
__del__删除对象或对象被从内存中销毁前,自动调用。
__str__print 打印实例对象时自动调用。返回对象的描述信息。

这几个内置函数都不需要手动调用。

__new__() 创建对象时调用

在使用 类名() 创建实例对象时,python 解释器会首先自动调用 __new__() 方法为对象在内存中分配内存空间,然后返回对象的引用。Python 解释器获得对象的引用后,将这个引用作为第一个参数传给 __init__() 方法。

__new__ 是由 object 类提供的内置静态方法,所以在调用的时候要主动给 cls 传递参数

注意:重写 __new__ 方法一定要 retun super().__new__(cls),否则 Python 解释器得不到分配了空间的对象引用,就不会调用对象的初始化方法 __init__()。

class Person(object):
    def __new__(cls, *args, **kwargs):  # 创建对象时,__new__被自动调用
        print('__new__被调用执行了,cls的id值为{0}'.format(id(cls)))
        # 为对象分配内存空间
        obj = super().__new__(cls)  # Person传给了object类,在object中创建一个对象obj
        print('创建的对象的id值为{0}'.format(id(obj)))
        # 返回对象的引用
        return obj  # 返回给self

    def __init__(self, name, age):  # 为创建的对象赋值
        print('__init__被调用了,self 的id值为{0}'.format(id(self)))
        self.name = name
        self.age = age


print('object这个类对象的id为{0}'.format(id(object)))
print('Person这个类对象的id为{0}'.format(id(Person)))

print('-------------------------------------------------------------')
p1 = Person('张三', 20) # 先执行赋值号右侧的代码,自动调用__new__,把Person传给了cls   __init__初始化方法执行完成后,再把self赋值给p1
print('-------------------------------------------------------------')
print('p1这个Person的实例对象的id为{0}'.format(id(p1)))
图片[1]-Python 类中的常用内置方法__init__和__new__(一)-尤尤'blog

单例设计模式

单例设计模式:类创建的对象,在系统中只有唯一一个实例,也就是每次执行 类名(),返回的对象地址都相同。例如音乐播放器,每次只能播放一首歌。

重写 __new__ 方法是为了对分配空间的方法进行改造,使得我们在使用 类名() 创建对象的时候,无论执行多少次,在内存中只会创建出一个实例对象,以达到单例设计模式的目的。

单例设计模式代码演示:

我们需要达到的目的是:无论使用 类名() 创建多少次,得到的永远是创建的第一个实例对象。所以我们需要这样做:

首先我们需要一个用于记录单例对象的引用的类属性,且初始值为 None;

然后在 __new__() 方法中判断类属性,如果类属性为 None,说明该类在内存中还没有创建实例对象,那么就需要调用父类的 __new__() 方法来分配内存空间;

如果类属性不为 None,说明内存中以及有一个对象了,那么不再调用父类的方法来分配内存空间,直接返回类属性中记录的对象的引用。

class MusicPlayer(object):

    # 类属性:记录单例对象的引用
    instance = None

    def __new__(cls, *args, **kwargs):
        # 如果instance=None,说明内存中还没有实例对象,那么就需要调用父类的方法分配内存空间
        if cls.instance is None:
            cls.instance = super().__new__(cls)  # 存储父类方法返回的对象的引用
        return cls.instance

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


music1 = MusicPlayer('白龙马')
print(music1, music1.name)
music2 = MusicPlayer('拔萝卜')
print(music2, music2.name)
music3 = MusicPlayer('三字经')
print(music3, music3.name)
图片[2]-Python 类中的常用内置方法__init__和__new__(一)-尤尤'blog

从上面的结果可以看出:使用 类名() 创建对象时,虽然得到的是第一个对象的引用,但是初始化方法每创建一个实例对象都会被执行。

解决这个问题很简单,设置一个类属性 init_flag 用来判断初始化方法是否被执行过,初始值为 False。

class MusicPlayer(object):

    # 类属性:记录单例对象的引用
    instance = None
    init_flag = False   # 用来标记初始化方法是否被执行过

    def __new__(cls, *args, **kwargs):
        # 如果instance=None,说明内存中还没有实例对象,那么就需要调用父类的方法分配内存空间
        if cls.instance is None:
            cls.instance = super().__new__(cls)  # 存储父类方法返回的对象的引用
        return cls.instance

    def __init__(self, name):
        # 判断初始化方法是否被执行过
        if MusicPlayer.init_flag is False:
            # 初始化实例属性
            self.name = name
            # 更改初始化标志
            MusicPlayer.init_flag = True


music1 = MusicPlayer('白龙马')
print(music1, music1.name)
music2 = MusicPlayer('拔萝卜')
print(music2, music2.name)
music3 = MusicPlayer('三字经')
print(music3, music3.name)

__init__() 初始化方法(构造方法)

在创建实例对象时,python 解释器会自动为对象在内存中分配内存空间,并通过初始化方法( __init__(),前后都是两个下划线)为对象的属性设置初始值。

也就是创建实例对象时,为对象分配完内存空间后,自动调用 __init__() 方法。

__init__ () 是对象的内置方法,专门用于定义一个类中有哪些属性。它的第一个参数同样是 self,表示实例对象的引用。

只要创建实例对象,就会自动调用 __init__() 方法,我们通过重写这个方法来完成初始化:

class Person:
    def __init__(self):
        print('我是初始化方法。')


# 使用类名()创建对象的时候,会自动调用__init__()初始化方法。
Tom = Person()
图片[3]-Python 类中的常用内置方法__init__和__new__(一)-尤尤'blog

1)如果想要这个类创建的对象默认都拥有这个属性值,可以通过 self.属性名 = 初始化值 来定义属性:

class Person:
    def __init__(self):
        # print('我是初始化方法。')
        self.name = 'Tom'  # name的初始值固定了


Tom = Person()
print(Tom.name)  # Tom
a = Person()
print(a.name)  # Tom
图片[4]-Python 类中的常用内置方法__init__和__new__(一)-尤尤'blog

可以看到:使用 Person 类创建出来的对象都有一个实例属性为 name,且属性值都是 ‘Tom’。

我们可以看到,利用这种方法初始化属性,对象的属性值就固定了,要想对象的属性值灵活变化,可以通过参数传递来完成。

注意:

在定义属性时,如果我们不知道该给某一个属性设置什么初始值,可以设置为 None(表示什么都没有)。

None 表示一个空对象,没有方法和属性,是一个特殊的常量。

class Demo:
    def __init__(self):
        self.test = None


d = Demo()
print(d.test)   # None

2)使用参数来设置属性的初始值,让每个对象都自己的属性值:(位置参数)

我们对 __init__() 进行重写,将希望设置的属性值,定义为 __init__() 的参数。然后在方法内部通过 self.属性 = 形参 来接收外部传递的参数。

这样就可以在创建对象时,以参数的形式将对象特有的属性传递到方法内部(语法格式:类名(属性1, 属性2…)),使得创建出来的对象更加灵活多样。

class Person:
    def __init__(self, name, age):
        self.name = name  # self.name是实例属性,将局部变量name的值赋给实例属性,习惯上是实例属性名和局部变量名设置成一样的
        self.age = age


Tom = Person('Tom', 21)
Alice = Person('Alice', 19)

print(f'{Tom.name}今年{Tom.age}岁。')  # Tom今年21岁。
print(f'{Alice.name}今年{Alice.age}岁。')  # Alice今年19岁。
图片[5]-Python 类中的常用内置方法__init__和__new__(一)-尤尤'blog

使用 Person 创建对象时,每一个参数都必须要传参(除了 self 参数),否则程序会报错。

class Person:
    def __init__(self, name, age):
        self.name = name  # self.name是实例属性,将局部变量name的值赋给实例属性,习惯上是实例属性名和局部变量名设置成一样的
        self.age = age


Bob = Person()
图片[6]-Python 类中的常用内置方法__init__和__new__(一)-尤尤'blog

先前学函数的时候,为避免忘记传值而导致的错误,我们给函数设置默认值来避免这个错误。

3)默认值参数

我们可以给形参设置默认值,这样创建对象的时候无论传不传参,都不会报错。

class Person:
    def __init__(self, name='张三', age=999):
        self.name = name
        self.age = age


Tom = Person('Tom', 21)
print(f'{Tom.name}今年{Tom.age}岁。')  # Tom今年21岁。

# 不传参的话,程序不会报错,使用默认值
Alice = Person()
print(f'{Alice.name}今年{Alice.age}岁。')  # 张三今年999岁。
图片[7]-Python 类中的常用内置方法__init__和__new__(一)-尤尤'blog

根据结果可以看到,给形参设置默认值后,在使用 类名() 创建对象时,即使不传参,程序也不会报错,它会使用形参的默认值。

© 版权声明
THE END
喜欢就支持一下吧
点赞17 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容