python编程 基础入门学习笔记七——面向对象编程
本节笔记
1.类的成员:字段、方法和属性
2.类成员的修饰符
3.类的特殊成员
1.类的成员:字段、方法和属性
类的成员主要分为三大类:
字段:静态字段、普通字段
方法:类方法、静态方法、普通方法
属性:普通属性
1)字段
#-*- coding:utf-8 -*- class Student: #静态字段 school_address="上海" def __init__(self,name): #普通字段 self.name=name #直接访问静态字段 print(Student.school_address) #直接访问普通字段 obj=Student("Jack") print(obj.name)
由上述代码可以看出:静态字段通过类来访问,普通字段通过对象来访问。
本质上两者的区别是:它们在内存中保存的位置不同,
静态字段属于类,在内存中只保存一份
普通字段属于对象,在每个对象中都要保存一份
问题:什么时候使用静态字段呢?
通过类创建对象的时候,如果每个对象都具有相同的字段,那么就使用静态字段。
2)方法
#-*-coding=utf-8-*- class ClassName: def __init__(self,name): self.name=name def func(self): '''定义普通方法,至少有一个self参数''' print("普通方法",self) @classmethod def class_func(cls): '''定义类方法,至少有一个cls参数''' print("类方法",cls) @staticmethod def static_func(): '''定义静态方法,无默认参数''' print("静态方法") #调用普通方法 f=ClassName("Rose") f.func() #调用类方法 ClassName.class_func() #调用静态方法 ClassName.static_func()
由上述代码可以看出:普通方法、类方法、静态方法在内存中都属于类,即在内存中均保存一份。
三者的不同在于它们的调用方式:
普通方法,第一个参数需要是self,它表示一个具体的实例本身;执行时,自动将调用该方法的对象赋值给self。
类方法(classmethod),它的第一个参数不是self,是cls,它表示这个类本身;执行时,自动将调用该方法的类赋值给cls。。
静态方法(staticmethod),无视这个self,而将这个方法当成一个普通的函数使用。
3)属性
#-*- coding=utf-8 -*- class ClassName: def __init__(self): pass def func(self): print("普通方法",self) #定义属性 @property def property_func(self): print("属性") #调用属性 p=ClassName() p.property_func
由上述代码可以看出:python中的属性就是普通方法的变种,如果python中没有属性,方法完全可以替代其功能。
需要注意的是:
定义时,在普通函数的基础上添加@property装饰器,且这个普通函数仅有一个self参数。
调用时,不要加括号,即 p.property_func。
属性存在的意义是——访问属性时,可以制造出和访问字段完全相同的假象。
属性的功能,在属性内部进行一系列的逻辑计算,最终将计算结果返回。
python3.x以后只有新式类,不涉及经典类,所以这里只讨论新式类的@property装饰器。
#-*- coding:utf-8 -*- #Author:'Yang' class Goods: def __init__(self): self.original_price=100 self.dicount=0.75 @property def price(self): print("@property") new_price=self.original_price*self.dicount return new_price @price.setter def price(self,value): print("@price.setter") self.original_price=value @price.deleter def price(self): print("@price.deleter ") del self.original_price obj=Goods() orig=obj.price #获取商品价格 print(orig) after=obj.price=123 #修改商品原价 print(after) del obj.price #删除商品原价 obj.price #会报错:AttributeError: 'Goods' object has no attribute 'original_price',因为上一句已经进行了删除
再来说说属性的两种定义,
一种是,在方法上应用装饰器 @property,这种方式刚才已经接触过了;
另一种是,在类中定义值为property的对象的静态字段
#-*- coding=utf-8 -*- class ClassName: def __init__(self): pass def func(self): # print("普通方法",self) return True #定义属性 flags=property(func) #调用属性 p=ClassName() result=p.flags#自动调用func方法,并获取方法的返回值 print(result)
#-*- coding:utf-8 -*- #Author:'Yang' class Goods: def __init__(self): self.original_price=100 self.dicount=0.75 def get_price(self): # print("@property") new_price=self.original_price*self.dicount return new_price def set_price(self,value): # print("@price.setter") self.original_price=value def del_price(self): # print("@price.deleter ") del self.original_price PRICE=property(get_price,set_price,del_price) obj=Goods() print(obj.PRICE) #获取商品价格 obj.PRICE=123 #修改商品原价 print(obj.PRICE) del obj.PRICE #删除商品原价 obj.PRICE #会报错:AttributeError: 'Goods' object has no attribute 'original_price',因为上一句已经进行了删除
2.类成员的修饰符
对每一个类而言,都有两种形式:
公有成员,在任何地方都能访问;私有成员命名时,前两个字符是下划线。
私有成员,只有在类的内部才能访问。
先来说说静态字段:
公有静态字段——类可以直接访问;类内部可以访问;派生类中也可以访问。
#-*- coding=utf-8 -*- class C: name="公有静态字段" def func(self): print("func",C.name) class D(C): def show(self): print("show",C.name) C.name #类可以访问 obj=C() obj.func() #类的内部可以访问 obj_son=D() obj_son.show() #派生类中也可以访问
私有静态字段——仅仅类内部可以访问。
#-*- coding:utf-8 -*- class C: __name="私有静态字段" def func(self): print("func",C.__name) class D(C): def show(self): print("show",C.__name) obj=C() obj.func() #类的内部可以访问 C.__name #类无法访问,报错 obj_son=D() obj_son.show() #派生类中也无法访问,报错
然后来看看普通字段:
公有普通字段——对象可以访问;类内部可以访问;派生类中也可以访问。
#-*- coding:utf-8 -*- #Author:'Yang' class C: def __init__(self): self.var="公有普通字段" def func(self): print("func",self.var) class D(C): def show(self): print("show",self.var) obj=C() #创建实例化对象 res=obj.var #通过对象访问 print(res) obj.func() #类的内部可以访问 obj_son=D() obj_son.show() #派生类中也可以访问
私有普通字段——仅仅类内部可以访问。
#-*- coding:utf-8 -*- #Author:'Yang' class C: def __init__(self): self.__var="私有普通字段" def func(self): print("func",self.__var) class D(C): def show(self): print("show",self.__var) obj=C() #创建实例化对象 obj.func() #类的内部可以访问 res=obj.__var #通过对象无法访问,报错 print(res) obj_son=D() obj_son.show() #派生类中也无法访问,报错
注意:如果要强制访问私有字段,可以通过 对象._类名__私有字段名 (如obj._C__var)进行访问,但是不建议强制访问私有成员。
再来讲讲方法:
公有方法——在任何地方都可以被访问。
私有方法——这里分为单下划线的方法(函数)和双下划线的方法(函数),两者在调用时区别很大:
单下划线的函数,在类中可以访问和调用,类的实例对象可以直接访问,派生类也可以访问;
双下划线的函数,在类中可以访问和调用,但类的实例对象无法访问,派生类也无法访问。
#-*- coding:utf-8 -*- #Author:'Yang' class ClassName(object): def __init__(self): pass #在公有方法中调用双下划线私有方法、单下划线私有方法 def func(self): print("公有方法") ClassName.__func(self) ClassName._func(self) #单下划线私有方法 def _func(self): print("单下划线,私有方法") #双下划线私有方法 def __func(self): print("双下划线,私有方法") #创建实例对象 obj=ClassName() print("在公有方法中可以调用双下划线私有方法、单下划线私有方法".center(50,"*")) obj.func()
#-*- coding:utf-8 -*- #Author:'Yang' class ClassName(object): def __init__(self): pass #公有方法 def func(self): print("公有方法") #在单下划线私有方法中调用双下划线私有方法、公有方法 def _func(self): print("单下划线,私有方法") ClassName.__func(self) ClassName.func(self) def __func(self): print("双下划线,私有方法") #创建实例对象 obj=ClassName() print("在单下划线私有方法中可以调用双下划线私有方法、公有方法".center(50,"*")) obj._func()
#-*- coding:utf-8 -*- #Author:'Yang' class ClassName(object): def __init__(self): pass #公有方法 def func(self): print("公有方法") #单下划线私有方法 def _func(self): print("单下划线,私有方法") #在双下划线私有方法中调用单下划线私有方法、公有方法 def __func(self): print("双下划线,私有方法") ClassName._func(self) ClassName.func(self) def foo(self): ClassName.__func(self) #创建实例对象 obj=ClassName() print("在双下划线私有方法中可以调用单下划线私有方法、公有方法".center(50,"*")) # obj.__func()#报错:AttributeError: 'ClassName' object has no attribute '__func' obj.foo()
注意:以上调用仅适用在类定义中。
#-*- coding:utf-8 -*- #Author:'Yang' class ClassName(object): def __init__(self): pass def func(self): print("公有方法") def _func(self): print("单下划线,私有方法") class D(ClassName): def __init__(self): ClassName.__init__(self) pass def show(self): ClassName.func(self) ClassName._func(self) print("实例对象可以访问的方法".center(50,"*")) #创建实例对象 obj=ClassName() obj.func() '''单下划线的函数,类的实例对象 可以 直接访问''' obj._func() # obj.__func() print("派生类可以访问的方法".center(50,"*")) obj_son=D() obj_son.show()
#-*- coding:utf-8 -*- #Author:'Yang' class ClassName(object): def __init__(self): pass #在公有方法中调用双下划线私有方法 def func(self): print("公有方法") ClassName.__func(self) #在单下划线私有方法中调用双下划线私有方法 def _func(self): print("单下划线,私有方法") ClassName.__func(self) '''双下划线的函数或属性,在类定义中可以调用和访问,类的实例实例 无法 访问,派生类也 无法 访问''' def __func(self): print("双下划线,私有方法") class D(ClassName): def __init__(self): ClassName.__init__(self) pass def show(self): '''双下划线的函数,派生类 无法 访问''' ClassName.__func(self) #报错:AttributeError: type object 'ClassName' has no attribute '_D__func' #创建实例对象 obj=ClassName() print("在公有方法中调用双下划线私有方法".center(50,"*")) obj.func() print("在单下划线私有方法中调用双下划线私有方法".center(50,"*")) obj._func() '''双下划线的函数,类的实例实例 无法 访问''' obj.__func() #报错:AttributeError: 'ClassName' object has no attribute '__func' obj_son=D() obj_son.show()
最后提一下属性,完全可以参考普通字段部分。
3.类的特殊成员
所谓类的特殊成员,就是指不同于类成员:字段、方法和属性的其他成员。下面就来认识下,这些特殊成员们:
1)__init__
构造函数
2)__del__
析构函数
3)__doc__
查看类的描述信息
4)__module__
查看当前操作的对象在哪个模块
5)__class__
查看当前操作的对象的所属类
6)__dict__
查看类或对象中的所有成员
7)__call__
在类中定义def __call__(self,*args, **kwargs),在实例化对象时,就可以直接在对象后面加括号,触发执行。
例如,构造方法def __init__(self)的执行是由创建实例化对象触发的,即 对象=类名();
__call__方法的执行是由对象后加括号触发的,即 对象() 或 类名()()
8)__str__
在类中定义def __str__(self),在实例化对象时,就可以通过打印 对象,输出__str__方法的返回值。
9)__new__、__metaclass__
在讲__new__、__metaclass__方法之前,让我们先来理解“python中一切事物皆对象”这句话。
#-*- coding:utf-8 -*- class ClassName(object): def __init__(self): pass obj=ClassName() #obj是通过ClassName类实例化的对象 print(type(obj)) #输出<class '__main__.ClassName'>,表示obj对象由ClassName这个类创建 print(type(ClassName)) #输出<class 'type'>,表示ClassName这个类由type类创建
执行上述代码后,你再理解“python中一切事物皆对象”这句话:obj对象通过执行 ClassName这个类的构造方式创建的,而 ClassName这个类又是通过执行 type这个类的构造方法创建的。
也就是说,我们平时创建的类,是由type类实例化而产生的。
Python中类的实例化是由Python解释器先后调用__new__、__init__这两个魔法方法来完成的。
__new__方法用来完成实例化后的对象的“骨架”(比如,解释器会为对象分配地址,并返回一个指向该对象的引用值,该引用值会被紧接着传递给__init__函数);
__init__方法用“ self.属性名 = 属性值 ”这样的方式对实例化的对象进行“填充”。
那么你一定会想深究,type类又是如何实现类的创建的呢?类的内部又是怎样创建对象的呢?
先来解答前半段问题,看看下面用普通方式创建类
#-*- coding:utf-8 -*- #Author:'Yang' #普通方式创建类 class ClassName(object): def __init__(self,name): print("自动触发构造函数") self.name=name def func(self): print("hello %s." %self.name) obj=ClassName("Jack") #obj是通过ClassName类实例化的对象 obj.func()
其实上述代码还有一种特殊的方式,直接用type类创建类;本质上普通方式创建类也是通过type类创建的,只是被封装在内部。
#-*- coding:utf-8 -*- #Author:'Yang' #特殊方式(type类创建) def func(self): print("hello %s." %self.name) def __init__(self,name): print("自动触发type类创建的构造函数") self.name=name ClassName_type=type("ClassName_type",(object,),{'func':func,'__init__':__init__}) obj_type=ClassName_type("Jack") obj_type.func()
以上两种类的创建方式,达到的效果是完全一样的,实例化对象的输出结果也是一致的。
现在让我们来揭开后半段问题的谜底:
元类:__metaclass__
什么是元类呢?元类就是用来创建这些类(对象)的,是类的类。type其实就是python的内建元类。
我们可以在写一个类的时候,像这样为这个类添加__metaclass__属性。
class ClassName(object): __metaclass__=something [...]
这样做了,Python就会用元类来创建类ClassName。小心点,这里面有些技巧。执行到class ClassName(object),类对象ClassName还没有在内存中创建。这时,Python会在类的定义中寻找__metaclass__属性,如果找到了,Python就会用__metaclass__属性来创建类ClassName,如果没有找到__metaclass__属性,就会用内建的type来创建类ClassName。
如果是派生类,像下面这样:
class ClassName(BaseClass): pass
Python又是怎么创建类的呢?
Python先在ClassName中查找是否有__metaclass__属性,如果有,Python会在内存中通过__metaclass__创建一个名为ClassName的类对象;如果没有,Python会继续到BaseClass(父类)中寻找__metaclass__属性,并尝试做和前面同样的操作;如果在任何父类中都没找到__metaclass__属性,Python就会在模块层次中寻找__metaclass__属性,并尝试在上述同样的操作;如果还是找不到__metaclass__属性,Python就会有用内置的type来创建这个类对象。
了解了上述原理,问题又来了,something到底可以放些什么样的代码呢?
答:可以放置创建一个类的东西。
什么可以用来创建一个类呢?
答:type类,或 任何使用到type或子类化type的东西。这可能就要用到自定义元类了。
什么是自定义元类?
答:元类的目的主要是为了在创建类时能够自动的拦截类、改变类、返回类。
通常通常,你会为API做这样的事情,你希望可以创建符合当前上下文的类。
假想一个很傻的例子,你希望在你的模块里所有类的属性都应该是大写形式。有好几种方法可以办到,但其中一种就是通过在模块级别设定__metaclass__。采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就好了。
def upper_attr(future_class_name,future_class_parents,future_class_attr): '''返回一个类对象,将属性都转为大写形式''' attrs=((name,value) for name,value in future_class_attr.items() if not name.startswith('__')) uppercase_attr=dict((name.upper(),value) for name,value in attrs) #通过'type'来做类对象的创建 return type(future_class_name, future_class_parents, uppercase_attr) __metaclass__=upper_attr # 这会作用到这个模块中的所有类 class Foo(object): # __metaclass__=upper_attr # 我们也可以只在这里定义__metaclass__,这样就只会作用于这个类中 bar="bip" #公有静态属性字段 print(hasattr(Foo,'bar')) print(hasattr(Foo,'BAR')) f=Foo() print(f.BAR)
上面我们使用了函数做元类传递给类,下面我们使用一个正式类来作为元类传递给元类__metaclass__。
class UpperAttrMataclass(type): def __new__(cls,name,bases,dct): '''返回一个类对象,将属性都转为大写形式''' # __new__ 是在__init__之前被调用的特殊方法 # __new__是用来创建对象并返回之的方法 # 而__init__只是用来将传入的参数初始化给对象 # 你很少用到__new__,除非你希望能够控制对象的创建 # 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__ # 如果你希望的话,你也可以在__init__中做些事情 # 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用 attrs=((name,value) for name,value in dct.items() if not name.startswith('__')) #选择所有不以'__'开头的属性 uppercase_attr=dict((name.upper(),value) for name,value in attrs) #将它们装换为大写形式 return super(UpperAttrMataclass,cls).__new__(cls,name,bases,uppercase_attr) #new是用来创建实例的 class Foo(object): __metaclass__=UpperAttrMataclass bar="bip" #公有静态属性字段 print(hasattr(Foo,'bar')) print(hasattr(Foo,'BAR')) f=Foo() print(f.BAR)
注:以上关于元类__metaclass__的代码仅在python2.7中验证。在python3.x中无法成功。
10)__getitem__、__setitem__、__delitem__
用于索引操作,如字典。以上分别表示获取、设置、删除数据。
11)__getslice__、__setslice__、__delslice__
这三个方法用于分片操作,如:列表 slice : 切片 切割。
12)__iter__
用于迭代器,之所以列表、字典、元组可以进行for循环,是因为类型内部定义了 __iter__。
浙公网安备 33010602011771号