上一篇《Python 面向对象(初级篇)》文章只介绍了面向对象基本知识,本篇将详细介绍Python 类的成员、成员修饰符、类的特殊成员。
注:在所有成员中,只有方法中定義之普通字段的内容會保存对象中,即:根据此类创建了多少对象,在内存中就有多少个普通字段。而其他的成员,则都是保存在类中,即:无论对象的多少,在内存中只创建一份。
字段包括:普通字段和静态字段,他们在定义和使用中有所区别,其本质在内存中保存的位置亦不同
由上述代码可以看出【普通字段需要通过对象来访问】【静态字段通过类访问】,在使用上可以看出普通字段和静态字段的归属是不同的,其在内容的存储方式类似如下图:
应用场景: 通过类创建对象时,如果每个对象都要具有相同的字段,那么就使用静态字段
方法包括:普通方法、静态方法和类方法,這三种方法在内存中都归属于类,区别在于调用方式不同。
· 普通方法:由对象调用,內須有一个self参数,當执行普通方法时,會自动將调用该方法的对象參考赋值给self;表示方法中須使用對象名。
· 类方法:由类调用,內須有一个cls参数;當执行类方法时,會自动将调用该方法的类參考赋值给cls;表示方法中須使用本類名。
· 静态方法:由类直接调用;无默认参数;表示方法中不須使用本類名
def ord_func(self): #表示方法中須使用對象名
相同点:对于所有的方法而言均属于类中,所以在内存中也只保存一份。
如果你已经了解Python类中的方法,那么属性就非常简单了,因为Python中的属性其实是普通方法的变种。
def prop(self): #表示prop()方法以屬性定義之(*僅只能對單一self參數之普通方法有效)
·定义时,在普通方法的基础上前要添加 @property 装饰器;
注意:此種属性之存在意义是--访问方法时可以制造出如同访问属性完全相同的假象,故属性由普通方法变种而来,Python中之属性用普通方法也可完全代替其功能。
实例:对于呈現在電腦之列表页面,每次请求不可能把数据库中的所有内容全部都显示到页面上,而是通过分页的功能作局部显示,所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的所有数据(即:limit m,n),这个分页的功能包括:
· 根据用户请求欲顯示的当前页和数据总条数來计算出 m 和 n
def __init__(self, current_page):
self.current_page = current_page
val = (self.current_page - 1) * self.per_items
val = self.current_page * self.per_items #即(self.current_page - 1) * self.per_items+ self.per_items
从上述可见,Python普通方法之属性定義的主要功能是:可透過普通方法内部來进行一系列的逻辑计算後再将最终结果返回。
·静态字段:即在类中定义以property(方法名)之屬性值
我们知道Python中的类有经典类和新式类之分,而新式类的属性比经典类的属性丰富。
result = obj.price # 自动执行以 @property 修饰的 price 普通方法
obj.price # 自动执行以 @property 修饰的 price 普通方法
obj.price = 123 # 自动执行以 @price.setter 修饰的 price 普通方法,并将123 赋值给方法的指定参数
del obj.price # 自动执行以 @price.deleter 修饰的 price 普通方法
注:经典类中的属性只有一种getter访问方式,对应被 @property 修饰的普通方法;新式类中的属性有三种getter、setter及deleter访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的普通方法
由于新式类中可具有三种访问方式,故我们可以根据他们几个属性的访问特点,分别一次将三个方法指定为同一个属性作对應(获取、修改、删除)之操作。
new_price = self.original_price * self.discount
创建方式是以property(方法名)產生的静态字段(*表示透過普通方法之返回值來設定屬性值)
reuslt = obj.BAR # 自动调用get_bar普通方法
· 第一个参数是以設定之方法名调用 对象.属性 时自动触发执行方法
· 第二个参数是以設定之方法名调用 对象.属性 = XXX 时自动触发执行方法
· 第三个参数是以設定之方法名调用 del 对象.属性 时自动触发执行方法
· 第四个参数是字符串,调用 对象.属性.__doc__ ,此参数是用於表示该属性的描述信息
return return 'set value' + value
BAR = property(get_bar, set_bar, del_bar, 'description...')
obj.BAR # 自动调用第一个参数中定义的方法:get_bar
obj.BAR = "alex" # 自动调用第二个参数中定义的方法:set_bar方法,并将“alex”当作参数传入
del Foo.BAR # 自动调用第三个参数中定义的方法:del_bar方法
obj.BAR.__doc__ # 自动获取第四个参数中设置的值:description...
此種以静态字段方式所创建之属性可同時具有三种访问方式,故我们可以根据属性的访问特点分别将三个方法定义为同一个属性之(获取、修改、删除)对應操作。
new_price = self.original_price * self.discount
PRICE = property(get_price, set_price, del_price, '价格属性描述...')
注:Python WEB框架 Django 的视图中 request.POST 就是使用此種静态字段的方式來创建属性,看一下如下源碼:
class WSGIRequest(http.HttpRequest):
script_name = get_script_name(environ)
path_info = get_path_info(environ)
# 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.
self.path = '%s/%s' % (script_name.rstrip('/'), path_info.lstrip('/'))
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:
codecs.lookup(content_params['charset'])
self.encoding = content_params['charset']
self._post_parse_error = False
content_length = int(environ.get('CONTENT_LENGTH'))
except (ValueError, TypeError):
self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
return self.environ.get('wsgi.url_scheme')
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)
# 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)
if not hasattr(self, '_post'):
raw_cookie = get_str_from_wsgi(self.environ, 'HTTP_COOKIE', '')
return http.parse_cookie(raw_cookie)
if not hasattr(self, '_files'):
POST = property(_get_post, _set_post)
REQUEST = property(_get_request)
結論:定义属性共有两种方式,分别是【装饰器】和【静态字段】,但要注意【装饰器】方式针对经典类(*只有@property一個方式 )和新式类(*可有@property、@方法名.setter及@方法名.deleter三個方式),兩者有所不同,但【静态字段】則無區別之分。
类的所有成员在上一步骤中已经做了详细的介绍,对于每一个类的成员之存取權限而言則有两种形式:
私有成员(*限在本類內)和公有成员(*不受限)的定义不同:私有成员命名时,以两个下划线字符作前導。(但特殊成员除外,不是私有成員之特性,例如:__init__、__call__、__dict__等)
class C: def __init__(self): self.name = '公有字段' self.__foo = "私有字段" |
·公有静态字段:类可以访问;类内部可以访问;派生类中可以访问(*即繼承體系下都可訪問)
print C.__name #只限制在此類內可訪問==> 正确
· 公有普通字段:对象可以访问;类内部可以访问;派生类中可以访问
ps:如果强制想要访问私有普通字段,可以通过 【对象._类名__私有普通字段 】访问(如:obj._C__foo),但不建议强制访问私有普通成员。
print self.__foo #只限制在本類內部访问==> 正确)
至於方法访问之存取權限與上述属性访问相同,總結即:私有成员只能對內在本类内部訪問,但公有成員只要在繼承體下對內外都可訪問。
ps:如非要強制访问私有属性的话,可以通过 对象._类__属性(或方法)
上文介绍了Python的类成员以及成员修饰符,从而了解到类中有字段、方法和属性三大类成员,并且成员名前如果有两个下划线,则表示该成员是私有成员,私有成员只能在本类内部作訪問。另Python的类成员存在着一些具有特殊含义的成员,详情如下:
__class__ 表示当前操作对象是什么类(*以模塊.類別表示之)
print obj.__module__ #输出 lib.aa,即输出模块名
print obj.__class__ #输出 lib.aa.C,即模块中之类名
obj = Foo('wupeiqi') #自动执行类中的 __init__ 方法並將傳入值給指定之name參數
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来自動执行,所以,析构函数的调用一般是由解释器在进行垃圾回收时自动触发执行的。
注:构造方法的执行是由類创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
def __call__(self, *args, **kwargs):
上文中我们知道:类的普通字段属于对象;类中的静态字段和方法等属于类,即:
def __init__(self, name, count):
def func(self, *args, **kwargs):
print Province.__dict__# 获取类的所有成员(含:静态字段、方法)
# 输出:{'country': 'China', '__module__': '__main__', 'func': <function func at 0x10be30f50>, '__init__': <function __init__ at 0x10be30ed8>, '__doc__': None}
obj1 = Province('HeBei',10000)
print obj1.__dict__ #获取对象obj1 的所有成员(*但不含類的成員)
# 输出:{'count': 10000, 'name': 'HeBei'}
obj2 = Province('HeNan', 3888)
print obj2.__dict__ # 获取对象obj2 的所有成员(*但不含類的成員)
# 输出:{'count': 3888, 'name': 'HeNan'}
如果一个类中有定义了__str__方法,那么在打印时,默认會输出此方法的返回值。
8、__getitem__、__setitem__、__delitem__
此是用于索引操作(如:list、字典),以上分别表示获取、设置、删除数据之訪問
#!/usr/bin/env python # -*- coding:utf-8 -*-
class Foo(object): def __getitem__(self, key): print '__getitem__',key
def __setitem__(self, key, value): print '__setitem__',key,value
def __delitem__(self, key): print '__delitem__',key
obj = Foo()
result = obj['k1'] # 自动触发执行 __getitem__ obj['k2'] = 'wupeiqi' # 自动触发执行 __setitem__ del obj['k1'] # 自动触发执行 __delitem__ |
9、__getslice__、__setslice__、__delslice__
#!/usr/bin/env python # -*- coding:utf-8 -*-
class Foo(object): def __getslice__(self, i, j): print '__getslice__',i,j
def __setslice__(self, i, j, sequence): print '__setslice__',i,j
def __delslice__(self, i, j): print '__delslice__',i,j
obj = Foo()
obj[-1:1] # 自动触发执行 __getslice__ obj[0:1] = [11,22,33,44] # 自动触发执行 __setslice__ del obj[0:2] # 自动触发执行 __delslice__ |
此是用于迭代器,列表、字典、元组之所以可以进行for循环,是因为类型内部有定义了 __iter__ 方法
# 报错:TypeError: 'Foo' object is not iterable
# 报错:TypeError: iter() returned non-iterator of type 'NoneType'
def __init__(self, sq): #藉由sq傳入list
以上步骤可以看出,for循环迭代的其实是iter([11,22,33,44]) ,所以执行流程可以变更为:
#!/usr/bin/env python # -*- coding:utf-8 -*-
obj = iter([11,22,33,44]) for i in obj: print i |
class Foo(object): def __init__(self): pass
obj = Foo() # obj是通过Foo类实例化的对象 |
上述代码中,obj 是通过 Foo 类实例化的对象,其实,不仅 obj 是一个对象,其實Foo类本身也是一个对象,因为在Python中一切事物都是对象。
如果按照一切事物都是对象的理论:obj对象是通过执行Foo类的构造方法创建,那么Foo类之对象应该也是通过执行type类的构造方法创建。
print type(obj) # 输出:<class '__main__.Foo'>,可看出obj 对象是由Foo类创建 print type(Foo) # 输出:<type 'type'>,可看出Foo类对象是由 type 类创建 |
所以,obj对象是Foo类的一个实例,Foo类对象是 type 类的一个实例,即:Foo类之对象是通过type类的构造方法所创建。
class Foo(object): def func(self): print 'hello wupeiqi' |
def func(self): print 'hello wupeiqi'
Foo = type('Foo',(object,), {'func': func}) #type第一个参数:类名 #type第二个参数:当前类的基类(可支持多重繼承) #type第三个参数:类的成员(以dict表示) |
那么问题来了,类默认是由 type 类实例化产生,type类中又是如何实现的创建类?类又是如何创建对象?
答:类中有一个属性 __metaclass__,可用来表示该类是由 谁 来作实例化创建,所以,我们可以为 __metaclass__ 设置一个type基类,从而查看 类 创建的过程。下圖是類创建實例過程中所調用之方法:
參考 :https://www.cnblogs.com/wupeiqi/p/4766801.html