Python面向对象中类的成员

Python面向对象中类的成员总共有三种,分别是字段、方法和属性

Python Version: 3.5+

字段

普通字段

1
2
3
4
5
6
7
8
class A:
def __init__(self, name):
# 我就是字段,普通字段
self.name = "polarsnow"

def show(self):
print(self.name)
return self.name

在上面的代码块中,self.name就是类中的字段。该字段的信息会保存在每一个实例化的对象中

静态字段

1
2
3
4
5
6
7
8
9
10
11
class A:
# 我也是字段,我是静态字段
country = "China"

def __init__(self, name):
# 我就是字段,普通字段
self.name = "polarsnow"

def show(self):
print(self.name)
return self.name

静态字段保存在类中。每个实例化的类中,都保存了一个类对象指针指向一个类。每个对象自己封装了自己的普通字段信息,但是该类所有的对象都共用了一份儿静态字段。

静态字段的应用场景和好处是某个类中,共用的一些信息可以抽离出来当做静态字段,好处是每个对象可以不用再重复性的保存同一份数据。

字段访问规则

一般情况下:自己去访问自己的字段

例如:访问静态字段,前面提到了,静态字段保存在类中,所以原则上需要使用类去访问静态字段;如果访问普通字段,普通字段保存在对象中,所以只能使用对象去访问普通字段。

在Java中,强制性的要求静态字段只能由类去访问;但是在Python中比较特殊,类中的静态字段也是可以通过对象去访问的。你需要坚持的原则就是:自己去访问自己的字段。不到特殊情况下,不要使用对象去访问类中的静态字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class A:
# 我也是字段,我是静态字段
country = "China"

def __init__(self, name):
# 我就是字段,普通字段
self.name = "polarsnow"

def show(self):
print(self.name)
return self.name


a = A("polarsnow")
print("使用对象访问普通字段")
print(a.name)

print("使用类访问静态字段")
print(A.country)

print("使用对象访问静态字段")
print(a.country)

------------
使用对象访问普通字段
polarsnow
使用类访问静态字段
China
使用对象访问静态字段
China

由于每个对象都保存了类对象指针,当访问的字段在本对象中找不到的时候会去类中查找有没有对应的静态字段

总结

静态字段在代码加载的时候就已经被创建;而普通字段只有在实例化一个对象时候才被创建

在类的内部访问静态字段,推荐也带上类名来访问A.country符合上面说的自己去访问自己的字段的访问原则

方法

在其他面向对象语言中,类的方法有两种,但是在Python中有三种

普通方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class B:
country = "China"

def __init__(self, name):
self.name = name

# 我是普通方法 由对象调用的方法
def show(self):
print(self.name)
return self.name

b = B("北京")
b.show()

------------
北京

普通方法,由对象去调用执行(方法属于类)

静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class B:
country = "China"

def __init__(self, name):
self.name = name

# 我是普通方法 由对象调用的方法
def show(self):
print(self.name)
return self.name

# 我是静态方法
@staticmethod
def func_static():
print("我是静态方法")
return "我是静态方法"

b = B("北京")

print("使用对象去调用执行普通方法")
b.show()

print("使用类去调用静态方法")
B.func_static()

------------
使用对象去调用执行普通方法
北京
使用类去调用静态方法
我是静态方法

普通方法和静态方法的区别:在上面的字段介绍中,静态字段和普通字段的区别是,静态字段一般由类去调用而普通字段一定是由对象去调用。静态方法和普通方法也是一样,静态方法一般是由类去调用执行,而普通方法是由对象去调用执行

定义一个静态方法

定义一个静态方法,首先需要在方法上面加上一个装饰器@staticmethod,下面的方法参数中,不要再含有self参数。至于静态方法的用法,和普通的函数用法没有任何区别,后面可以添加任意参数进行传值

小结:静态方法虽然在类中,属于类,但是静态方法和对象没有任何关系,它是由类去调用的。这一点在静态方法的参数列表中也可以很明显的看出,它没有接受self参数,也就是没有接受对象。静态方法的调用不依赖与任何一个具体的对象。那为什么这个方法和类没有关系还要放在这个类中呢?一般情况下,我们把某些和类相关的操作都会放在类中,方便查找和使用,所以很多时候,类中会出现和对象不相关,但是和类却息息相关的操作。

类方法

类方法是Python独有的一个方法。本质上静态方法的一种特殊形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class B:
country = "China"

def __init__(self, name):
self.name = name

# 我是普通方法 由对象调用的方法
def show(self):
print(self.name)
return self.name

# 我是静态方法
@staticmethod
def func_static():
print("我是静态方法")
return "我是静态方法"

# 我是类方法
@classmethod
def func_class(cls):
print("我是类方法")
return "我是类方法"

b = B("北京")

print("使用对象去调用执行普通方法")
b.show()

print("使用类去调用静态方法")
B.func_static()

print("使用类去调用类方法")
B.func_class()

------------
使用对象去调用执行普通方法
北京
使用类去调用静态方法
我是静态方法
使用类去调用类方法
我是类方法

在静态方法中,可以没有任何参数,也可以有任意多个参数,使用类去调用;但是在类方法中,至少要有一个参数cls, 也是使用类去调用。这一特点和普通方法又很像,普通方法也是至少要有一个参数,但普通方法的必有参数为self

  • self在普通方法中,代指了对象
  • cls在类方法中,代指了类。跟self一样会隐式传递

类方法比静态方法只多了一步,就是隐式的把类传递给了类方法

类方法的应用:单例模式的实现

总结

Python中类的三个方法都属于类

  • 普通方法 至少要有一个self参数 通过对象去执行
  • 静态方法 参数可有可无 通过类去执行(也可以对象执行)
  • 类方法 至少有一个cls参数 通过类去执行(也可以对象执行)

属性

属性的特征是:拥有普通方法的样式和表现形式,却拥有字段的访问方式

现有如下需求:需要做一个分页器,每10条内容分一页,最后得出一共分出多少页的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 首先使用普通方法的方式来实现这个功能
class Pageer:

def __init__(self, num):
self.num = num

def all_pager(self):
p1, p2 = divmod(self.num, 10)
if p2 == 0:
return p1
else:
return p1 + 1

p = Pageer(59)
print(p.all_pager())

------------
6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 使用属性的方式来实现这个功能
class Pageer:

def __init__(self, num):
self.num = num

@property
def all_pager(self):
p1, p2 = divmod(self.num, 10)
if p2 == 0:
return p1
else:
return p1 + 1

p = Pageer(59)
print(p.all_pager)

------------
6

对比两段代码可以看出,有两点区别

  • 在普通方法的上面加了一个@property装饰器
  • 在调用这个普通成员的时候,第一段代码使用了标准的普通方法调用执行的方式p.all_pager();第二段代码则使用了访问普通字段的方式p.all_pager

这就是上面说的属性的特征,既拥有和普通方法一样的表现形式,又有访问普通字段那样的访问形式

对属性的赋值

上面的小例子实现了对属性的取值。对于访问普通字段来说,不仅可以访问,还是为普通字段赋值。那么属性既然有着普通字段的访问形式,能不能也去实现像普通字段那样的赋值呢?p.all_pager = 1991

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Pageer:

def __init__(self, num):
self.num = num

@property
def all_pager(self):
p1, p2 = divmod(self.num, 10)
if p2 == 0:
return p1
else:
return p1 + 1

@all_pager.setter
def all_pager(self, value):
print(value)

p = Pageer(59)
print(p.all_pager)

p.all_pager = 1991

------------
6
1991

对属性进行赋值的关键语法:

  • 一定要使用属性取值方法的方法名.setter装饰器
  • 赋值方法的函数名必须和取值方法的方法名一致
  • 在赋值方法中,接受一个赋值参数value

在为all_pager赋值的时候,会自动执行@all_pager.setter下面的方法(特别注意:属性赋值方法中不能return返回值)

删除属性

在对字段的操作中,可以访问字段,给字段赋值,还可以删除字段。上面的属性中,已经实现了属性的访问和赋值,接下来要实现属性的删除,这样一来,属性就拥有了和字段一样的操作特性了(查,改,删)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Pageer:

def __init__(self, num):
self.num = num

@property
def all_pager(self):
p1, p2 = divmod(self.num, 10)
if p2 == 0:
return p1
else:
return p1 + 1

@all_pager.setter
def all_pager(self, value):
print(value)

@all_pager.deleter
def all_pager(self):
print("del all_pager")

p = Pageer(59)
print(p.all_pager)

print("为属性赋值")
p.all_pager = 1991

print("删除属性")
del p.all_pager

总结

属性具有方法的表现形式,同时具有字段的访问形式(通过不加括号()的方式去执行了一个方法)

由于属性的这个访问特性是我们通过三个方法来伪造出来的,所以每个操作对应了具体什么内容,需要我们来自己定义。就像上面的删除方法,我只是print了一下而已~~ Python只是根据我们操作属性的方式,提供了一种关联方式而已,具体里面需要实现哪些功能都需要我们自己去定义

注意:如果你想真的在内存中实现删除all_pager属性,你可能会这样写:

1
2
3
@all_pager.deleter
def all_pager(self):
del self.all_pager

这样做的结果是会进入一个死循环。因为你在外面调用del函数来删除对象属性的时候,会去deleter里面执行del self.all_pager代码,该代码又会跳到deleter里面执行del self.all_pager,所以会进入一个死循环。属性的删除方法,应该根据实际需要调整他的功能

属性存在意义是:访问属性时可以制造出和访问字段完全相同的假象。属性由方法变种而来,如果Python中没有属性,方法完全可以代替其功能

属性的第二种表现形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Pageer:

def __init__(self, num):
self.num = num

def func1(self):
return 1991

def func2(self, value):
print("set", value)

def func3(self):
print("del")

func = property(fget=func1, fset=func2, fdel=func3)


p = Pageer(59)
print(p.func) # 自动去调用fget=func1
p.func = 2016 # 自动去调用fset=func2
del p.func # 自动去调用fdel=func3

------------
1991
set 2016
del

property的构造方法中有个四个参数

  • 第一个参数是方法名,调用 对象.属性 时自动触发执行方法
  • 第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法
  • 第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法
  • 第四个参数是字符串,调用 对象.属性.doc ,此参数是该属性的描述信息

这种通过静态字段的方式实现属性的操作在很多源码中很常见,使用的频率是最高的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# Python WEB框架 Django 的视图中 request.POST 就是使用的静态字段的方式创建的属性
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)
  • 第70行,定义了属性的两个方法,取值和赋值
  • 第52行,取值
  • 第57行,赋值

特别强调

最后再一次强调,本站关于Python的介绍大多数是基于Python3.5+

上面关于属性通过装饰器的方式来实现不同的操作时,均是在Python3.5+的环境下,如果你在Python2.x的环境下,得到的结果肯定会不一样。因为Python2.x中

  • 经典类中的属性只有一种访问方式,其对应被@property修饰的方法
  • 新式类中的属性有三种访问方式,并分别对应了三个被@property @方法名.setter @方法名.deleter修饰的方法

但是在Python3中,经典类也是拥有这三种访问方式的。

继承了object的类就是新式类,否则为经典类