【python】python面向对象(进阶)
本篇将详细介绍python类的成员、成员修饰符、类的特殊成员。
1、类的成员
类的成员可以分为三大类:字段、方法和属性
字段:普通字段、静态字段
类成员 方法:普通方法、类方法、静态方法
属性:普通属性
当然普通字段和普通方法还可以分为私有方法和共有方法,私有变量,共有变量,方法和有用于重构的magic method
注:所有成员中,只有普通字段的内容保存对象中,即:根据此类创建了多少对象,在内存中就有多少个普通字段。而其他的成员,则都是保存在类中,即:无论对象的多少,在内存中只创建一份。
1.1 字段(变量)
字段包括:普通字段和静态字段,他们在定义和使用有所区别,而最本质的区别是内存中保存位置不同
- 普通字段属于对象
- 静态字段属于类

class Province: # 静态字段 country = '中国' def __init__(self, name): # 普通字段 self.name = name # 直接访问普通字段 obj = Province('河北省') print(obj.name) # 直接访问静态字段 print(Province.country)
由上述代码可以看出【普通字段需要通过对象来访问】【静态字段通过类访问】,在使用上可以看出普通字段和静态字段的归属是不同的。其在内容的存储方式类似如下图:
由上图可是:
- 静态字段在内存中只保存一份
- 普通字段在每个对象中都要保存一份
应用场景: 通过类创建对象时,如果每个对象都具有相同的字段,那么就使用静态字段
2、方法
方法包括:普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同。
- 普通方法:由对象调用;至少一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self;
- 类方法:由类调用; 至少一个cls参数;执行类方法时,自动将调用该方法的类复制给cls;
- 静态方法:由类调用;无默认参数;

class Foo: def __init__(self, name): self.name = name def ord_func(self): """ 定义普通方法,至少有一个self参数 """ # print self.name print('普通方法') @classmethod def class_func(cls): """ 定义类方法,至少有一个cls参数 """ print('类方法') @staticmethod def static_func(): """ 定义静态方法 ,无默认参数""" print('静态方法') # 调用普通方法 f = Foo() f.ord_func() # 调用类方法 Foo.class_func() # 调用静态方法 Foo.static_func()
相同点:对于所有的方法而言,均属于类(非对象)中,所以,在内存中也只保存一份。
不同点:方法调用者不同、调用方法时自动传入的参数不同。
原则上,类方法是将类本身作为对象进行操作的方法。假设有个方法,且这个方法在逻辑上采用类本身作为对象来调用更合理,那么这个方法就可以定义为类方法。另外,如果需要继承,也可以定义为类方法。
如下场景:
假设我有一个学生类和一个班级类,想要实现的功能为:
执行班级人数增加的操作、获得班级的总人数;
学生类继承自班级类,每实例化一个学生,班级人数都能增加;
最后,我想定义一些学生,获得班级中的总人数。
思考:这个问题用类方法做比较合适,为什么?因为我实例化的是学生,但是如果我从学生这一个实例中获得班级总人数,在逻辑上显然是不合理的。同时,如果想要获得班级总人数,如果生成一个班级的实例也是没有必要的。

class Class_demo(object): __num = 0 @classmethod def addNum(cls): cls.__num += 1 @classmethod def getNum(cls): return cls.__num # 这里我用到魔术函数__new__,主要是为了在创建实例的时候调用人数累加的函数。 def __new__(self): Class_demo.addNum() return super(Class_demo, self).__new__(self) class Student(Class_demo): def __init__(self): self.name = '' a = Student() b = Student() print(Class_demo.getNum())
3、属性
如果你已经了解Python类中的方法,那么属性就非常简单了,因为Python中的属性其实是普通方法的变种。
对于属性,有以下三个知识点:
- 属性的基本使用
- 属性的两种定义方式
3.1 属性的基本使用

class Foo: def func(self): pass #定义属性 @property def prop(self): pass #调用 foo_obj = Foo() foo_obj.func() foo_obj.prop#属性调用方法
由属性的定义和调用要注意一下几点:
- 定义时,在普通方法的基础上添加 @property 装饰器;
- 定义时,属性仅有一个self参数
- 调用时,无需括号
方法:foo_obj.func()
属性:foo_obj.prop
注意:属性存在意义是:访问属性时可以制造出和访问字段完全相同的假象,有什么好处呢?
属性由方法变种而来,如果Python中没有属性,方法完全可以代替其功能。
实例:对于主机列表页面,每次请求不可能把数据库中的所有内容都显示到页面上,而是通过分页的功能局部显示,所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的所有数据(即:limit m,n),这个分页的功能包括:
- 根据用户请求的当前页和总数据条数计算出 m 和 n
- 根据m 和 n 去数据库中请求数据

class Pager: def __init__(self,current_page): #用户当前请求的页码(第一页,第二页..) self.current_page = current_page #每页默认显示10条数据 self.per_items = 10 @property def start(self): val = (self.current_page-1) * self.per_items return val @property def end(self): val = self.current_page * self.per_items return val #调用 p = Pager(1) p.start #就是起始值,即:m p.end #就是结束值,即:n
从上述可见,Python的属性的功能是:属性内部进行一系列的逻辑计算,最终将计算结果返回。
3.2 属性的两种定义方式
属性的定义有两种方式:
- 装饰器 即:在方法上应用装饰器
- 静态字段 即:在类中定义值为property对象的静态字段
3.2.1 装饰器方式:在类的普通方法上应用@property装饰器
我们知道Python中的类有经典类和新式类,新式类的属性比经典类的属性丰富。( 如果类继object,那么该类是新式类 )
经典类:具有一种@property装饰器(如上一步实例)

class Goods: @property def price(self): return '100' obj = Goods() result = obj.price
新式类:具有三种@property装饰器(@property,@property.setter,@property.deleter)
正常demo:

class Geeks: def __init__(self): self._age = 0 # using property decorator # a getter function @property def age(self): print("getter method called") return self._age # a setter function @age.setter def age(self, a): if (a < 18): raise ValueError("Sorry you age is below eligibility criteria") print("setter method called") self._age = a mark = Geeks() mark.age = 19 print(mark.age)
经典demo:

class Protective(object): def __init__(self, start_protected_value=0): self.protected_value = start_protected_value #!!!执行完此句,立马到属性赋值那一行 print(type(self.protected_value)) @property def protected_value(self): print('1 is executing...') return self._protected_value @protected_value.setter #属性赋值 def protected_value(self, value): print('2 is executing') if value != int(value): raise TypeError("protected_value must be an integer") if 0 <= value <= 100: self._protected_value = int(value) else: raise ValueError("protected_value must be " + "between 0 and 100 inclusive") @protected_value.deleter def protected_value(self): raise AttributeError("do not delete, protected_value can be set to 0") p1 = Protective(88)
3.2.2 装饰器方式:在类的普通方法上应用@property装饰器
当使用静态字段的方式创建属性时,经典类和新式类无区别

class Foo: def get_bar(self): return 'value' BAR = property(get_bar) obj = Foo() reuslt = obj.BAR # 自动调用get_bar方法,并获取方法的返回值 print(reuslt)
- 第一个参数是方法名,调用
对象.属性
时自动触发执行方法 - 第二个参数是方法名,调用
对象.属性 = XXX
时自动触发执行方法 - 第三个参数是方法名,调用
del 对象.属性
时自动触发执行方法 - 第四个参数是字符串,调用
对象.属性.__doc__
,此参数是该属性的描述信息

class Foo: def get_bar(self): return 'value' # *必须两个参数 def set_bar(self, value): return 'set value' + value def del_bar(self): return 'value' BAR = property(get_bar, set_bar, del_bar, 'description...') obj = Foo() obj.BAR # 自动调用第一个参数中定义的方法:get_bar obj.BAR = "alex" # 自动调用第二个参数中定义的方法:set_bar方法,并将“alex”当作参数传入 del Foo.BAR # 自动调用第三个参数中定义的方法:del_bar方法 obj.BAE.__doc__ # 自动获取第四个参数中设置的值:description...
Django源码:

class WSGIRequest(http.HttpRequest): def __init__(self, environ): script_name = get_script_name(environ) path_info = get_path_info(environ) if not path_info: # Sometimes PATH_INFO exists, but is empty (e.g. accessing # the SCRIPT_NAME URL without a trailing slash). We really need to # operate as if they'd requested '/'. Not amazingly nice to force # the path like this, but should be harmless. path_info = '/' self.environ = environ self.path_info = path_info self.path = '%s/%s' % (script_name.rstrip('/'), path_info.lstrip('/')) self.META = environ self.META['PATH_INFO'] = path_info self.META['SCRIPT_NAME'] = script_name self.method = environ['REQUEST_METHOD'].upper() _, content_params = cgi.parse_header(environ.get('CONTENT_TYPE', '')) if 'charset' in content_params: try: codecs.lookup(content_params['charset']) except LookupError: pass else: self.encoding = content_params['charset'] self._post_parse_error = False try: content_length = int(environ.get('CONTENT_LENGTH')) except (ValueError, TypeError): content_length = 0 self._stream = LimitedStream(self.environ['wsgi.input'], content_length) self._read_started = False self.resolver_match = None def _get_scheme(self): return self.environ.get('wsgi.url_scheme') def _get_request(self): warnings.warn('`request.REQUEST` is deprecated, use `request.GET` or ' '`request.POST` instead.', RemovedInDjango19Warning, 2) if not hasattr(self, '_request'): self._request = datastructures.MergeDict(self.POST, self.GET) return self._request @cached_property def GET(self): # The WSGI spec says 'QUERY_STRING' may be absent. raw_query_string = get_bytes_from_wsgi(self.environ, 'QUERY_STRING', '') return http.QueryDict(raw_query_string, encoding=self._encoding) # ############### 看这里看这里 ############### def _get_post(self): if not hasattr(self, '_post'): self._load_post_and_files() return self._post # ############### 看这里看这里 ############### def _set_post(self, post): self._post = post @cached_property def COOKIES(self): raw_cookie = get_str_from_wsgi(self.environ, 'HTTP_COOKIE', '') return http.parse_cookie(raw_cookie) def _get_files(self): if not hasattr(self, '_files'): self._load_post_and_files() return self._files # ############### 看这里看这里 ############### POST = property(_get_post, _set_post) FILES = property(_get_files) REQUEST = property(_get_request) Django源码
3.3 私有成员和公有成员(这里私有是指双前导下划线开头的)
成员形式:
- 公有成员,在任何地方都能访问
- 私有成员,只有在类的内部才能方法
静态字段:
- 公有静态字段:类可以访问;类内部可以访问;派生类中可以访问

class Demo1: name = '公有静态字段' def func(self): print(Demo1.name) class Demo2(Demo1): def show(self): print(Demo1.name) Demo1.name #类访问 -->正确 obj = Demo1() obj.func() #可以通过方法在类内使用类访问 -->正确 obj_child = Demo2() obj_child.show() #派生类不可以访问 -->正确
- 私有静态字段:仅类内部可以访问;

class Demo1: __name = '私有静态字段' def func(self): print(Demo1.__name) class Demo2(Demo1): def show(self): print(Demo1.__name) Demo1.__name #类访问 -->错误 obj = Demo1() obj.func() #可以通过方法在类内使用类访问 -->正确 obj_child = Demo2() obj_child.show() #派生类不可以访问 -->错误
普通字段:
- 公有普通字段:对象可以访问;类内部可以访问;派生类中可以访问

class Demo1: def __init__(self): self.foo = '公有普通字段' def func(self): print(self.foo) class Demo2(Demo1): def show(self): print(self.foo) obj = Demo1() obj.func() # --> ok obj.__foo # --> ok obj_child = Demo2() obj_child.show() # --> ok
- 私有普通字段:仅类内部可以访问;

class Demo1: def __init__(self): self.__foo = '公有普通字段' def func(self): print(self.__foo) class Demo2(Demo1): def show(self): print(self.__foo) obj = Demo1() obj.func() # --> ok obj.__foo # --> not ok obj_child = Demo2() obj_child.show() # --> not ok
ps:如果想要强制访问私有字段,可以通过 【对象._类名__私有字段明 】访问(如:obj._Demo1__foo),不建议强制访问私有成员。
4 、类的特殊成员(magic method)
4.1 __doc__
表示类描述信息

class Demo: """ 描述类信息,这是用于描述 """ def func(self): pass print(Demo.__doc__)
4.2 __module__和__class__
__module__表示当前操作的对象在哪个模块
__class__表示当前操作的对象的类是什么
4.3 __init__和__del__
__init__:构造函数 当实例化一个类时,自动执行构造函数
__del__:析构函数 当对象在内存中释放时,自动触发执行析构函数(可以隐式执行)
4.4 __call__
对象后面加括号,触发执行
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

class Demo: def __init__(self): pass def __call__(self, *args, **kwargs): print('__call__') obj = Demo() obj()
4.5 __dict__
获取类成员,即:静态字段、方法等

class Demo: """ this is a comment """ country = 'China' def __init__(self,name,count): self.name = name self.count = count def func(self,*args,**kwargs): print('func') print(Demo.__dict__) #输出 “”{'__module__': '__main__', '__doc__': '\n\t this is a comment\n\t', 'country': 'China', '__init__': <function Demo.__init__ at 0x0000000002B4DC80>, 'func': <function Demo.func at 0x0000000002B89048>, '__dict__': <attribute '__dict__' of 'Demo' objects>, '__weakref__': <attribute '__weakref__' of 'Demo' objects>} “”
4.6 __str__
如果一个类中定义了__str__方法,那么在打印对象时,默认输出该方法的返回值。

class Demo: """ this is a comment """ country = 'China' def __init__(self,name,count): self.name = name self.count = count def func(self,*args,**kwargs): print('func') def __str__(self): return 'this is a str' d = Demo('zhenxian',11) print(d) #输出 this is a str
4.7 __getitem__、__setitem__、__delitem__
用于索引操作,如字典。以上分别表示获取、设置、删除数据
pass ...
4.8 __getslice__ 、__setslice__、__delslice__
该三个方法用于分片操作,如:列表
4.9 __iter__
用于迭代器,之所以列表、字典、元祖可以进行for loop,是因为类型内部实现了__iter()
4.10 __new__和__metaclass__
pass ...
5 、python类动态装载
正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。先定义class:

class Student(object): pass #然后尝试给实例绑定一个属性: s = Student() s.name = 'Micheal' print(s.name) #还可以尝试给实例绑定一个方法: def set_age(self, age): self.age = age from types import MethodType s.set_age = MethodType(set_age, s)#给实例绑定一个方法 s.set_age(25) s.age #输出结果:25
但是,给一个实例绑定的方法,对另一个实例是不起作用的:

s2 = Student() s2.set_age(25) #此句会报错 #为了给所有实例绑定方法,可以给class绑定方法: def set_score(self, score): self.score = score Student.set_score = set_score #绑定后所有实例均已绑定方法
通常情况下,上面set_score方法可以直接定义在class中,但动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现、
5.1 使用__slots__
但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加name
和age
属性。
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__
变量,来限制该class实例能添加的属性:

class Student(object): __slots__ = ('name', 'age') #然后尝试绑定 s = Student() s.name = 'Micheal' s.age = 25 s.score = 99#此时会报错
由于'score'
没有被放到__slots__
中,所以不能绑定score
属性,试图绑定score
将得到AttributeError
的错误。
使用__slots__
要注意,__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的