Python中晦涩难懂的概念,定义类创建和使用实例,单下划线和双下划线,__init__构造函数等
定义类创建实例
举个例子,
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()方法后,创建的每个实例都有自己的属性,也方便直接调用类中的函数。
上一篇: 过敏性鼻炎 夏季如何应对
下一篇: 如果可以想象