Python 对属性的访问控制是靠程序员自觉的。
我们也可以把方法看成是类的属性的,那么方法的访问控制也是跟属性是一样的,也是没有实质上的私有方法。一切都是靠程序员自觉遵守 Python 的编程规范。
1 类
1.1 方法的装饰器
@classmethod:调用的时候直接使用类名类调用,而不是某个对象
@property:可以像访问属性一样调用方法
class UserInfo:
...
@classmethod
def get_name(cls):
return cls.lv
@property
def get_age(self):
return self._age
if __name__ == '__main__':
...
# 直接使用类名类调用,而不是某个对象
print(UserInfo.lv)
# 像访问属性一样调用方法(注意看get_age是没有括号的)
print(userInfo.get_age)
1.2 继承
语法格式:
class ClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
当然上面的是单继承,Python 也是支持多继承的(注意: Java 是单继承、多实现),具体的语法如下:
class ClassName(Base1,Base2,Base3):
<statement-1>
.
.
.
<statement-N>
多继承有一点需要注意的:若是父类中有相同的方法名,而在子类使用时未指定,Python 在圆括号中父类的顺序,从左至右搜索 , 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
继承的子类的好处:
会继承父类的属性和方法
可以自己定义,覆盖父类的属性和方法
1.3 多态
看个例子就好了:
class User(object):
def __init__(self, name):
self.name = name
def printUser(self):
print('Hello !' + self.name)
class UserVip(User):
def printUser(self):
print('Hello ! 尊敬的Vip用户:' + self.name)
class UserGeneral(User):
def printUser(self):
print('Hello ! 尊敬的用户:' + self.name)
def printUserInfo(user):
user.printUser()
if name == 'main':
userVip = UserVip('大金主')
printUserInfo(userVip)
userGeneral = UserGeneral('水货')
printUserInfo(userGeneral)
输出结果:
Hello ! 尊敬的Vip用户:大金主
Hello ! 尊敬的用户:水货
可以看到,userVip 和 userGeneral 是两个不同的对象,对它们调用 printUserInfo 方法,它们会自动调用实际类型的 printUser 方法,作出不同的响应。这就是多态的魅力。
PS:有了继承,才有了多态,也会有不同类的对象对同一消息会作出不同的相应。
1.4 Python中的魔法方法
在 Python 中,所有以 "**" 双下划线包起来的方法,都统称为"魔术方法"。比如我们接触最多的 init__ 。魔术方法有什么作用呢?
使用这些魔术方法,我们可以构造出优美的代码,将复杂的逻辑封装成简单的方法。
我们可以使用 Python 内置的方法 dir() 来列出类中所有的魔术方法。示例如下:
class User(object):
pass
if name == 'main':
print(dir(User()))
输出的结果:
可以看到,一个类的魔术方法还是挺多的,截图没有截全。不过我们只需要了解一些常见和常用的魔术方法就好了。
1、属性的访问控制
Python 没有真正意义上的私有属性。然后这就导致了对 Python 类的封装性比较差。我们有时候会希望 Python 能够定义私有属性,然后提供公共可访问的 get 方法和 set 方法。Python 其实可以通过魔术方法来实现封装。
方法说明
getattr(self, name)
该方法定义了你试图访问一个不存在的属性时的行为。因此,重载该方法可以实现捕获错误拼写然后进行重定向,或者对一些废弃的属性进行警告。
setattr(self, name, value)
定义了对属性进行赋值和修改操作时的行为。不管对象的某个属性是否存在,都允许为该属性进行赋值。有一点需要注意,实现 setattr 时要避免"无限递归"的错误
delattr(self, name)
delattr 与 setattr 很像,只是它定义的是你删除属性时的行为。实现 delattr 是同时要避免"无限递归"的错误
getattribute(self, name)
getattribute 定义了你的属性被访问时的行为,相比较,getattr 只有该属性不存在时才会起作用。因此,在支持 getattribute的 Python 版本,调用getattr 前必定会调用 getattribute,getattribute 同样要避免"无限递归"的错误。
2、对象的描述器
一般来说,一个描述器是一个有“绑定行为”的对象属性 (object attribute),它的访问控制被描述器协议方法重写。这些方法是 get(),set()和 delete()。有这些方法的对象叫做描述器。
默认对属性的访问控制是从对象的字典里面 (dict) 中获取 (get) , 设置 (set) 和删除 (delete) 。举例来说, a.x 的查找顺序是 a.dict['x'],然后 type(a).dict['x'],然后找 type(a) 的父类 ( 不包括元类 (metaclass) )。如果查找到的值是一个描述器,Python 就会调用描述器的方法来重写默认的控制行为。这个重写发生在这个查找环节的哪里取决于定义了哪个描述器方法。注意,只有在新式类中时描述器才会起作用。
至于新式类最大的特点就是所有类都继承自 type 或者 object 的类。
在面向对象编程时,如果一个类的属性有相互依赖的关系时,使用描述器来编写代码可以很巧妙的组织逻辑。在 Django 的 ORM 中,models.Model 中的 InterField 等字段,就是通过描述器来实现功能的。
看一个例子:
class User(object):
def __init__(self, name='小明', sex='男'):
self.sex = sex
self.name = name
def __get__(self, obj, objtype):
print('获取 name 值')
return self.name
def __set__(self, obj, val):
print('设置 name 值')
self.name = val
class MyClass(object):
x = User('小明', '男')
y = 5
if name == 'main':
m = MyClass()
print(m.x)
print('\n')
m.x = '大明'
print(m.x)
print('\n')
print(m.x)
print('\n')
print(m.y)
输出结果:
获取 name 值
小明
设置 name 值
获取 name 值
大明
获取 name 值
大明
5
3、自定义容器(Container)
我们知道在 Python 中,常见的容器类型有:dict、tuple、list、string。其中也提到过可容器和不可变容器的概念。其中 tuple、string 是不可变容器,dict、list 是可变容器。
可变容器和不可变容器的区别在于,不可变容器一旦赋值后,不可对其中的某个元素进行修改。
那么这里先提出一个问题,这些数据结构就够我们开发使用吗?不够的时候,或者说有些特殊的需求不能单单只使用这些基本的容器解决的时候,该怎么办呢?
这个时候就需要自定义容器了,那么具体我们该怎么做呢?
功能
说明
自定义不可变容器类型
需要定义 len 和 getitem方法
自定义可变类型容器
在不可变容器类型的基础上增加定义 setitem 和 delitem
自定义的数据类型需要迭代
需定义 iter
返回自定义容器的长度
需实现 len(self)
自定义容器可以调用 self[key],如果 key 类型错误,抛出 TypeError,如果没法返回 key对应的数值时,该方法应该抛出 ValueError
需要实现 getitem(self, key)
当执行 self[key] = value 时
调用是 setitem(self, key, value)这个方法
当执行 del self[key] 方法
其实调用的方法是 delitem(self, key)
当你想你的容器可以执行 for x in container: 或者使用 iter(container) 时
需要实现 iter(self) ,该方法返回的是一个迭代器
还有很多魔术方法,比如运算符相关的模式方法,就不在该文展开了。
2 枚举类
2.1 什么是枚举
举例,直接看代码:
from enum import Enum
Month = Enum('Month1', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
遍历枚举类型
for name, member in Month.members.items():
print(name, '---------', member, '----------', member.value)
直接引用一个常量
print('\n', Month.Jan)
输出结果:
Jan --------- Month1.Jan ---------- 1
Feb --------- Month1.Feb ---------- 2
Mar --------- Month1.Mar ---------- 3
Apr --------- Month1.Apr ---------- 4
May --------- Month1.May ---------- 5
Jun --------- Month1.Jun ---------- 6
Jul --------- Month1.Jul ---------- 7
Aug --------- Month1.Aug ---------- 8
Sep --------- Month1.Sep ---------- 9
Oct --------- Month1.Oct ---------- 10
Nov --------- Month1.Nov ---------- 11
Dec --------- Month1.Dec ---------- 12
Month.Jan
可见,我们可以直接使用 Enum 来定义一个枚举类。上面的代码,我们创建了一个有关月份的枚举类型 Month,这里要注意的是构造参数,第一个参数 Month 表示的是该枚举类的类名,第二个 tuple 参数,表示的是枚举类的值; 当然,枚举类通过 members 遍历它的所有成员的方法。
注意的一点是 , member.value 是自动赋给成员的 int 类型的常量,默认是从 1 开始的。而且 Enum 的成员均为单例(Singleton),并且不可实例化,不可更改。
2.2 自定义枚举类型
有时候我们需要控制枚举的类型,那么我们可以 Enum 派生出自定义类来满足这种需要。修改上面的例子:
from enum import Enum, unique
Enum('Month1', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
@unique 装饰器可以帮助我们检查保证没有重复值
@unique
class Month1(Enum):
Jan = 'January'
Feb = 'February'
Mar = 'March'
Apr = 'April'
May = 'May'
Jun = 'June'
Jul = 'July'
Aug = 'August'
Sep = 'September '
Oct = 'October'
Nov = 'November'
Dec = 'December'
if name == 'main':
print(Month1.Jan, '----------',
Month1.Jan.name, '----------', Month1.Jan.value)
for name, member in Month1.__members__.items():
print(name, '----------', member, '----------', member.value)
输出结果:
Month1.Jan ---------- Jan ---------- January
Jan ---------- Month1.Jan ---------- January
Feb ---------- Month1.Feb ---------- February
Mar ---------- Month1.Mar ---------- March
Apr ---------- Month1.Apr ---------- April
May ---------- Month1.May ---------- May
Jun ---------- Month1.Jun ---------- June
Jul ---------- Month1.Jul ---------- July
Aug ---------- Month1.Aug ---------- August
Sep ---------- Month1.Sep ---------- September
Oct ---------- Month1.Oct ---------- October
Nov ---------- Month1.Nov ---------- November
Dec ---------- Month1.Dec ---------- December
2.3 枚举类的比较
因为枚举成员不是有序的,所以它们只支持通过标识(identity) 和相等性 (equality) 进行比较。下面来看看 == 和 is 的使用:
from enum import Enum
class User(Enum):
Twowater = 98
Liangdianshui = 30
Tom = 12
Twowater = User.Twowater
Liangdianshui = User.Liangdianshui
print(Twowater == Liangdianshui, Twowater == User.Twowater)
print(Twowater is Liangdianshui, Twowater is User.Twowater)
try:
print('\n'.join(' ' + s.name for s in sorted(User)))
except TypeError as err:
print(' Error : {}'.format(err))
输出结果:
False True
False True
Error : '<' not supported between instances of 'User' and 'User'
可以看看最后的输出结果,报了个异常,那是因为大于和小于比较运算符引发 TypeError 异常。也就是 Enum 类的枚举是不支持大小运算符的比较的。
但是使用 IntEnum 类进行枚举,就支持比较功能。
import enum
class User(enum.IntEnum):
Twowater = 98
Liangdianshui = 30
Tom = 12
try:
print('\n'.join(s.name for s in sorted(User)))
except TypeError as err:
print(' Error : {}'.format(err))
输出结果:
Tom
Liangdianshui
Twowater
通过输出的结果可以看到,枚举类的成员通过其值得大小进行了排序。也就是说可以进行大小的比较。
3 元类
3.1 Python 中类也是对象
在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在 Python 中这一点也是一样的。但是,Python 中的类有一点跟大多数的编程语言不同,在 Python 中,可以把类理解成也是一种对象。对的,这里没有写错,就是对象。
因为只要使用关键字 class,Python 解释器在执行的时候就会创建一个对象。如:
class ObjectCreator(object):
pass
当程序运行这段代码的时候,就会在内存中创建一个对象,名字就是ObjectCreator。这个对象(类)自身拥有创建对象(类实例)的能力,而这就是为什么它是一个类的原因。
3.2 使用type()动态创建类
因为类也是对象,所以我们可以在程序运行的时候创建类。Python 是动态语言。动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。在之前,我们先了了解下 type() 函数。
class Hello(object):
def hello(self, name='Py'):
print('Hello,', name)
然后再另外一个模块引用 hello 模块,输出相应信息。(其中 type() 函数的作用是可以查看一个类型和变量的类型。)
from com.strivebo.hello import Hello
h = Hello()
h.hello()
print(type(Hello))
print(type(h))
输出信息:
Hello, Py
<class 'type'>
<class 'com.twowater.hello.Hello'>
上面也提到过,type() 函数可以查看一个类型或变量的类型,Hello 是一个 class ,它的类型就是 type ,而 h 是一个实例,它的类型就是 com.strivebo.hello.Hello。前面的 com.strivebo 是我的包名,hello 模块在该包名下。
在这里还要细想一下,上面的例子中,我们使用 type() 函数查看一个类型或者变量的类型。其中查看了一个 Hello class 的类型,打印的结果是: <class 'type'>。其实 type() 函数不仅可以返回一个对象的类型,也可以创建出新的类型。class 的定义是运行时动态创建的,而创建 class 的方法就是使用 type() 函数。比如我们可以通过 type() 函数创建出上面例子中的 Hello 类,具体看下面的代码:
def printHello(self, name='Py'):
# 定义一个打印 Hello 的函数
print('Hello,', name)
创建一个 Hello 类
Hello = type('Hello', (object,), dict(hello=printHello))
实例化 Hello 类
h = Hello()
调用 Hello 类的方法
h.hello()
查看 Hello class 的类型
print(type(Hello))
查看实例 h 的类型
print(type(h))
输出结果:
Hello, Py
<class 'type'>
<class 'main.Hello'>
在这里,需先了解下通过 type() 函数创建 class 对象的参数说明:
class 的名称,比如例子中的起名为 Hello
继承的父类集合,注意 Python 支持多重继承,如果只有一个父类,tuple 要使用单元素写法;例子中继承 object 类,因为是单元素的 tuple ,所以写成 (object,)
class 的方法名称与函数绑定;例子中将函数 printHello 绑定在方法名 hello 中
具体的模式如下:type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
好了,了解完具体的参数使用之外,我们看看输出的结果,可以看到,通过 type() 函数创建的类和直接写 class 是完全一样的,因为 Python 解释器遇到 class 定义时,仅仅是扫描一下 class 定义的语法,然后调用 type() 函数创建出 class 的。
3.3 什么是元类
我们创建类的时候,大多数是为了创建类的实例对象。那么元类呢?元类就是用来创建类的。也可以换个理解方式就是:元类就是类的类。
通过上面 type() 函数的介绍,我们知道可以通过 type() 函数创建类:MyClass = type('MyClass', (), {})
实际上 type() 函数是一个元类。type() 就是 Python 在背后用来创建所有类的元类。
那么现在我们也可以猜到一下为什么 type() 函数是 type 而不是 Type呢?
这可能是为了和 str 保持一致性,str 是用来创建字符串对象的类,而 int 是用来创建整数对象的类。type 就是创建类对象的类。
可以看到,上面的所有东西,也就是所有对象都是通过类来创建的,那么我们可能会好奇,class 的 class 会是什么呢?换个说法就是,创建这些类的类是什么呢?
print(age.class.class)
print(name.class.class)
print(fu.class.class)
print(mEat.class.class)
输出结果:
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
可以看出,把他们类的类打印结果。发现打印出来的 class 都是 type 。
一开始也提到了,元类就是类的类。也就是元类就是负责创建类的一种东西。你也可以理解为,元类就是负责生成类的。而 type 就是内建的元类。也就是 Python 自带的元类。
3.4 自定义元类
连接起来就是:先定义 metaclass,就可以创建类,最后创建实例。
所以,metaclass 允许你创建类或者修改类。换句话说,你可以把类看成是 metaclass 创建出来的“实例”。
class MyObject(object):
__metaclass__ = something…
[…]
如果是这样写的话,Python 就会用元类来创建类 MyObject。当你写下 class MyObject(object),但是类对象 MyObject 还没有在内存中创建。Python 会在类的定义中寻找 metaclass 属性,如果找到了,Python 就会用它来创建类 MyObject,如果没有找到,就会用内建的 type 函数来创建这个类。如果还不怎么理解,看下下面的流程图:
举个实例:
class Foo(Bar):
pass
它的流程是怎样的呢?
首先判断 Foo 中是否有 metaclass 这个属性?如果有,Python 会在内存中通过 metaclass 创建一个名字为 Foo 的类对象(注意,这里是类对象)。如果 Python 没有找到metaclass ,它会继续在 Bar(父类)中寻找metaclass 属性,并尝试做和前面同样的操作。如果 Python在任何父类中都找不到 metaclass,它就会在模块层次中去寻找metaclass ,并尝试做同样的操作。如果还是找不到metaclass ,Python 就会用内置的 type 来创建这个类对象。
其实 metaclass 就是定义了 class 的行为。类似于 class 定义了 instance 的行为,metaclass 则定义了 class 的行为。可以说,class 是 metaclass 的 instance。
现在,我们基本了解了 metaclass 属性,但是,也没讲过如何使用这个属性,或者说这个属性可以放些什么?
答案就是:可以创建一个类的东西。那么什么可以用来创建一个类呢?type,或者任何使用到 type 或者子类化 type 的东东都可以。
3.5 元类的作用
元类的主要目的就是为了当创建类时能够自动地改变类。通常,你会为 API 做这样的事情,你希望可以创建符合当前上下文的类。
假想一个很傻的例子,你决定在你的模块里所有的类的属性都应该是大写形式。有好几种方法可以办到,但其中一种就是通过在模块级别设定metaclass 。采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。
总结:Python 中的一切都是对象,它们要么是类的实例,要么是元类的实例,除了 type。type 实际上是它自己的元类,在纯 Python 环境中这可不是你能够做到的,这是通过在实现层面耍一些小手段做到的。加V:mmp9972 小编准备了一份2018年最新Python入门教程资料,都会发给大家的~
网友评论