欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python中晦涩难懂的概念,定义类创建和使用实例,单下划线和双下划线,__init__构造函数等

程序员文章站 2022-05-23 17:34:24
...

定义类创建实例
举个例子,

class Foo(object):
    def __init__(self, x, y=0):
        self.x = x
        self.y = y
foo = Foo(1,y=2)

对Foo的调用到底调用了什么函数或方法?
第一反应肯定是__init__方法,但仔细想想并不是正确答案,因为它没有返回一个对象,但是调用Foo(1,y=2)确实返回了一个对象,其实它的调用顺序是这样的:

  • 第一步,Foo(1,y=2)等价于Foo.call(1,y=2)
  • 第二步,既然Foo是一个type的实例,Foo.call(1,y=2)实际调用的是type.call(Foo,1,y=2)
  • 第三步,type.call(Foo,1,y=2)调用type.new(Foo,1,y=2),然后返回一个对象。
  • 第四步,obj随后通过调用obj.init(1,y=2)被初始化。
  • 第五步,obj被返回。

总的来说,__new__方法为对象分配了内存空间,构建它为一个“空"对象然后__init__方法被调用来初始化它。
让我们来看看__new__方法,它负责实际对象的创建方法,分配内存空间并返回该对象。用它来实现单例模式是很方便的。

"""
__new__方法实现单例模式
"""
class A(object):
    _instance = None
    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls, *args, **kwargs)
        return cls._instance
s1 = A()
s2 = A()
print(s1 is s2)  # True

再来看,如何创建类的属性

class A(object):  # 使用class定义类,所有的类都是从object类继承

    version = 1.0  # 类的属性直接在类的内部定义,当实例属性和类属性重名时,实例属性优先级高、
    
    def __init__(self,a,b):
        self.a = a
        self.b = b
        
if __name__ == '__main__':
    a = A(10,20)
    print(A.version)  # 直接通过类.属性访问
    print(a.version)  # 也可以通过实例.属性访问
    # 类的属性可以动态修改
    A.version = '1.3'
    print(A.version)
    # 类的属性一经修改,所有访问的属性值也随之修改
    print(a.version)
 

实例的创建
创建实例使用类名+(),类似函数调用的形式创建

class A(object):
	pass
a = A()  # 创建实例
a.name = 'frank'  # 创建实例属性

初始化实例属性

class A(object):
    version = 2.0  # 定义类属性
    def __init__(self,name,age):  # self代表实例,通过self访问实例对象的变量和函数
        self.name = name
        self.__age = age  # 实例的私有属性无法从外部访问,从类的内部是可以访问的
    # 定义实例方法
    def get_age(self):
        return self.__age  # 实例方法,定义在类内部,可以访问实例的私有属性__age
    # 定义类方法
    @classmethod
    def how_many(cls):  # 类方法的第一个参数为cls,cls.version相当于A.version
        return cls.version  # 类方法中无法调用任何实例的变量,只能获得类引用

p1 = A('frank',23)
print(p1.get_age())  # 实例方法的调用,self不需要显式传入

顺带引申一下单下划线双下划线的区别:

以单下划线开头(foo)的代表不能直接访问的类属性,需通过类提供的接口进行访问,那么以“”开头的名称都不会被导入,即不能用“from xxx import *”而导入,除非模块或包中的“all”列表显式地包含了它们;以双下划线开头的(__foo)代表类的私有成员,只有类本身能访问,其子类对象也不能访问到这个数据。

“单下划线” 开始的成员变量叫做保护变量,意思是只有类对象和子类对象自己能访问到这些变量;”双下划线” 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据。

举个例子,

class Foo(object):
    def __init__(self):
        pass
    def public_method(self):
        print('this is public method')
    def __fullprivate_method(self):
        print('this is full private method')
    def _halfprivate_method(self):
        print('this is half private method')

f = Foo()
print(f.public_method())  # OK
print(f._halfprivate_method())  # OK
# print(f.__fullprivate_method())  # AttributeError: 'Foo' object has no attribute '__fullprivate_method'
print(f._Foo__fullprivate_method())  # OK

f._halfprivate_method()可以直接访问,根据python的约定,应该将其视作private,而不要在外部使用它们。

以单下划线开头_foo的代表不能直接访问的类属性,需通过类提供的接口进行访问,不能用“from xxx import *”而导入;以双下划线开头的__foo代表类的私有成员;以双下划线开头和结尾的__foo__代表python里特殊方法专用的标识,如 init()代表类的构造函数。

__init__构造函数

在定义一个类时,什么时候用__init__函数,什么时候不用,用不用有什么区别?

首先__init__是为了初始化用的,但是初始化的时候不一定要用这个,直接定义也是可以的,比如

class A(object):
	test_a = '123'

而我们用__init__的好处在于可以接受任何参数并初始化

def __init__(self,a):
	test_a = a

这样类可以初始化一个动态的变量,更加灵活,直接test(‘123’)就将test_a初始化成123了
再举一个例子,如下

# 不用init方法定义类
class Rectangle(object):
    def getPeri(self,a,b):
        return (a+b)*2
    def getArea(self,a,b):
        return a*b
rec = Rectangle()
print(rec.getPeri(3,4))  # 14 
print(rec.getArea(3,4))  # 12
print(rec.__dict__)  # {}

从上例可以看到,没有定义__init__方法,也能得到周长和面积,但是通过rec.__dict__来看这个实例的属性,是空的,这就是没有定义__init__的原因。
并且,在实例化对象时,rec=Rectangle()参数为空,没有指定a,b的值,只有在调用函数的时候才指定,且类中定义的每个方法的参数都有a,b,有点多余了。
因此,需要在类中定义__init__方法,方便创建实例的时候,需要给实例绑定上属性,也方便类中的方法定义。
上述同样的例子,用__init__方法定义类,如下

# 使用__init__方法定义类
class Rectangle(object):
    def __init__(self,a,b):
        self.a = a
        self.b = b
    def getPeri(self):
        return (self.a+self.b)*2
    def getArea(self):
        return self.a*self.b

rec = Rectangle(3,4)
print(rec.getPeri())  # 14
print(rec.getArea())  # 12
print(rec.__dict__)  # {'a': 3, 'b': 4}

定义完init()方法后,创建的每个实例都有自己的属性,也方便直接调用类中的函数。