Python【05】【基础部分】- 面向对象 1
~~~ 面向对象 ~~~
1、基本概念
面向对象(Object Oriented,OO)是一种编程方式,是一种对事物的理解与抽象,在Python中,面向对象是基于【类】与【对象】的方式来实现,而这个编程过程就是对类与对象的使用。
类:就是一个模板,这个模板中包含各种功能(也就是函数喽)
对象:在面向对象中的这个对象就是根据类创建出的实例,通过实例化对象就可以使用类中的功能(函数)
面向对象的特性:
(1)封装
所谓封装就是把客观的事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或对象操作,对不可信的的进行信息隐藏。
(2)继承
所谓继承是指可以让某个类型的对象获得另一个类型对象的属性的方法。继承是这样的一种能力,它可以使用现有类的所有功能,并在无需重新编写原来类的情况下对这些功能进行扩展。
通过继承创建的类称为"子类"或"派生类",被继承的类称为"基类"、"父类"、"超类"。
(3)多态
所谓多态就是指一个类实例的相同方法在不同情形下有不同的表现形式。
面向对象的原则:
(1)单一职责原则SRP(Single Responsibility Principle),是指一个类的功能要单一,不能包罗万象。
(2)开放封闭原则OCP(Open-Close Principle),一个模块在扩展性方面应该是开放的而在更改性方面应该是封闭的。
(3)替换原则(the Liskov Substitution Principle LSP) ,子类应当可以替换父类并出现在父类能够出现的任何地方。
(4)依赖原则(the Dependency Inversion Principle DIP), 具体依赖抽象,上层依赖下层。
(5)接口分离原则(the Interface Segregation Principle ISP) ,模块间要通过抽象接口隔离开,而不是通过具体的类强耦合起来。
2、实例理解
(1)标准形态
# 定义一个类 class ClassName(object): # 新式类,如果不加object便为经典类 """docstring for ClassName""" # 注释信息 def __init__(self, arg): # 构造方法,self是必写的 self.arg = arg def func(self): # 普通方法,self是必写的 print self.arg # 对象实例化 obj = ClassName('arg') # 使用类中方法 obj.func()
class:函数关键词
ClassName:函数名
object:如果类不带有object,我们称为经典类,如果带有则称之为新式类
def func(self):类中的函数称之为方法,而def __init__(self)是一个构造方法,__init__的作用是初始化实例化后的对象。
self:这个一定是要写的,self在这里表示实例化后对象本身
(2)执行过程
#!/usr/bin/env python # coding:utf-8 __author__ = "QingPing" class Persion(object): # 1、将类读入内存。 def __init__(self,name): # 2、将构造函数读入内存。5、将"tom"传入 self.name = name # 6、执行方法 self.name = "tom" print "name =",name # 7、输出内容 def say_name(self): # 3、将函数读入内存。9、将上面初始化函数self赋值传入 print "My name is %s" %self.name # 10、输出内容 a = Persion('tom') # 4、实例化对象,将值输入。 a.say_name() # 8、调用类中方法 ## name = tom ## My name is tom
面向对象三大特性
1、封装
封装就是把内容或功能打包到一个对象属性中,然后提供调用的接口调内容或功能。
对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,然后通过对象直接或者self间接获取被封装的内容。
(1)将内容或功能封装
class ClassName(object): def __init__(self,name,age): # self在这是一个形参,代表object1或object2 self.name = name self.age = age object1 = ClassName('tom',12) # tom与12封装到object1、self中的name与age属性中 object2 = ClassName('kim',11) # kim与12封装到object2、self中的name与age属性中
内容被封装到了对象object1、object2中,在对象中都会有name与age属性,那么它们在内存中的保存状态应该是这样
(2)调用封装的内容或功能
通过对象直接调用:对象.属性名
class ClassName(object): def __init__(self,name,age): self.name = name self.age = age object1 = ClassName('tom',12) print object1.name # 直接调用object1中的name属性 print object1.age # 直接调用object1中的age属性
通过self间接调用
class ClassName(object): def __init__(self,name,age): self.name = name self.age = age def call(self): print self.name print self.age object1 = ClassName('tom',12) object1.call() # 通过self间接调用封装的内容,object1.call(object1),构造方法中会执行self.name = tom,self.age = 12
2、继承
面向对象中的继承就是子类继承父类的内容或功能,可以将重复的功能放到父类中,让子类继承。
(1)单继承
class Father(object): def __init__(self,name): self.name = name def run(self): print "%s run" % self.name def read(self): print "%s read" % self.name class Son1(Father): def eat(self): print "%s eat" % self.name class Son2(Father): def say(self): print "%s say" % self.name obj_son1 = Son1('tom') obj_son1.read() obj_son2 = Son2('alan') obj_son2.read() ## tom read # 子类继承父类 ## alan read # 子类继承父类
(2)多继承,也就是继承多个类
Python可以继承多个类,Java、C#只能继承一个类。Python在继承多个类时,有两种选择方式。
实例
class A: # 经典类 # class A(object): # 新式类 def echo(self): print 'a' class B(A): def echo(self): print 'b' class C(B): def echo(self): print 'c' class D(C,B): def echo(self): print 'd' obj = D() obj.echo() # 经典类,查找顺序:D --> C --> A --> D ,如果找不到则报错 # 新式类,查找顺序:D --> C --> B --> A , 如果找不到则报错 # 在上述查找方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
3、多态
Pyhon不支持多态并且也用不到多态,多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚“鸭子类型”。
class F1: pass class S1(F1): def show(self): print 'S1.show' class S2(F1): def show(self): print 'S2.show' def Func(obj): print obj.show() s1_obj = S1() Func(s1_obj) s2_obj = S2() Func(s2_obj)
===============================================================================
类与对象在内存中的关系
根据类创建对象时,对象中除了封装 name 和 age 的值之外,还会保存一个类对象指针,该值指向当前对象的类。
当通过 obj1 执行方法时,过程如下:
根据当前对象中的 类对象指针 找到类中的方法
将对象 obj1 当作参数传给 方法的第一个参数 self
类成员
类成员可分为三类:字段、方法、属性
1、字段
字段包括:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同
普通字段属于对象
静态字段属于类
class Province(object): # 静态字段 country = "China" def __int__(self,name): # 普通字段 self.name = name # 直接访问普通字段 obj = Province('shandong') print object.name # 直接访问静态字段 Province.country
普通字段需要通过对象来访问,静态字段通过类访问,在使用上可以看出普通字段和静态字段的归属是不同的。
- 静态字段在内存中只保存一份
- 普通字段在每个对象中都要保存一份
应用场景: 通过类创建对象时,如果每个对象都具有相同的字段,那么就使用静态字段
2、方法
方法包括:普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同。
- 普通方法:由对象调用;至少一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self;
- 类方法:由类调用; 至少一个cls参数;执行类方法时,自动将调用该方法的类复制给cls;
- 静态方法:由类调用;无默认参数;
class ClassName(): def __int__(self,name): self.name = name def ordinary_func(self): """ 普通方法,至少有一个self """ print "ordinary func" @classmethod def class_func(cls): """ 类方法,至少有一个cls """ print "calss func" @staticmethod def static_func(): """ 静态方法,没有默认参数 """ print "静态方法" # 调用普通方法 obj = ClassName() obj.ordinary_func() # 调用类方法 ClassName.class_func() # 调用静态方法 ClassName.static_func()相同点:对于所有的方法而言,均属于类(非对象)中,所以,在内存中也只保存一份。
不同点:方法调用者不同、调用方法时自动传入的参数不同。
3、属性
(1)属性的使用
class ClassName: def func(self): return "hi" # 定义属性 @property def prop(self): return "hello" obj = ClassName() print obj.func() print obj.prop # 调用属性 """ 由属性的定义和调用要注意一下几点: 定义时,在普通方法的基础上添加 @property 装饰器; 定义时,属性仅有一个self参数 调用时,无需括号 方法:foo_obj.func() 属性:foo_obj.prop 注意:属性存在意义是:访问属性时可以制造出和访问字段完全相同的假象 属性由方法变种而来,如果Python中没有属性,方法完全可以代替其功能。 """
实例:分页
#!/usr/bin/env python # coding:utf-8 __author__ = "QingPing" """ 对于主机列表页面,每次请求不可能把数据库中的所有内容都显示到页面上,而是通过分页的功能局部显示, 所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的所有数据(即:limit m,n),这个分页的功能包括: 1.根据用户请求的当前页和总数据条数计算出 m 和 n 2.根据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
(2)属性的定义
属性的定义有两种方式:
- 装饰器 即:在方法上应用装饰器
- 静态字段 即:在类中定义值为property对象的静态字段
A 装饰器方式:在类的普通方法上应用@property装饰器
经典类,具有一种@property装饰器
# ############### 定义 ############### class Goods: @property def price(self): return "tom" # ############### 调用 ############### obj = Goods() result = obj.price # 自动执行 @property 修饰的 price 方法,并获取方法的返回值新式类,具有三种@property装饰器
# ############### 定义 ############### class Goods(object): @property def price(self): print '@property' @price.setter def price(self, value): print '@price.setter' @price.deleter def price(self): print '@price.deleter' # ############### 调用 ############### obj = Goods() obj.price # 自动执行 @property 修饰的 price 方法,并获取方法的返回值 obj.price = 123 # 自动执行 @price.setter 修饰的 price 方法,并将 123 赋值给方法的参数 del obj.price # 自动执行 @price.deleter 修饰的 price 方法注:经典类中的属性只有一种访问方式,其对应被 @property 修饰的方法
新式类中的属性有三种访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法由于新式类中具有三种访问方式,我们可以根据他们几个属性的访问特点分别将三个方法定义为对同一个属性:获取、修改、删除
class Goods(object): def __init__(self): # 原价 self.original_price = 100 # 折扣 self.discount = 0.8 @property def price(self): # 实际价格 = 原价 * 折扣 new_price = self.original_price * self.discount return new_price @price.setter def price(self, value): self.original_price = value @price.deltter def price(self, value): del self.original_price obj = Goods() obj.price # 获取商品价格 obj.price = 200 # 修改商品原价 del obj.price # 删除商品原价
B 静态字段方式,创建值为property对象的静态字段
当使用静态字段的方式创建属性时,经典类和新式类无区别
class ClassName: def echo(self): return "tom" STR = property(echo) obj = ClassName() print obj.STR # 自动调用get_bar方法,并获取方法的返回值property的构造方法中有个四个参数
- 第一个参数是方法名,调用
对象.属性
时自动触发执行方法- 第二个参数是方法名,调用
对象.属性 = XXX
时自动触发执行方法- 第三个参数是方法名,调用
del 对象.属性
时自动触发执行方法- 第四个参数是字符串,调用
对象.属性.__doc__
,此参数是该属性的描述信息class ClassName: def get_func(self): return "get" def set_func(self,value): return "set + value" + value def del_func(self): return "del" ORD = property(get_func,set_func,del_func,"description...") obj = ClassName() obj.ORD # 调用第一个参数定义的方法:get_func obj.ORD = 'tom' # 调用第二个参数中的方法:set_func并传入值到方法中: value = tom del obj.ORD # 调用第三个参数中的方法:del_func obj.ORD.__doc__ # 获取第四个参数设定的值:description由于静态字段方式创建属性具有三种访问方式,我们可以根据他们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除
class Goods(object): def __init__(self): # 原价 self.original_price = 100 # 折扣 self.discount = 0.8 def get_price(self): # 实际价格 = 原价 * 折扣 new_price = self.original_price * self.discount return new_price def set_price(self, value): self.original_price = value def del_price(self, value): del self.original_price PRICE = property(get_price, set_price, del_price, '价格属性描述...') obj = Goods() obj.PRICE # 获取商品价格 obj.PRICE = 200 # 修改商品原价 del obj.PRICE # 删除商品原价注意: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)
类成员修饰符
对于类成员都有两种形式:
- 公有成员,在任何地方都能访问
- 私有成员,只有在类的内部才能方法
私有成员和公有成员的定义不同:私有成员命名时,前两个字符是下划线。(特殊成员除外,例如:__init__、__call__、__dict__等)
class C: def __init__(self): self.name = '公有字段' self.__foo = "私有字段"
私有成员和公有成员的访问限制不同:
静态字段
- 公有静态字段:类可以访问;类内部可以访问;派生类中可以访问
- 私有静态字段:仅类内部可以访问
公有静态字段class C: name = "公有静态字段" def func(self): print C.name class D(C): def show(self): print C.name C.name # 类访问 obj = C() obj.func() # 类内部可以访问 obj_son = D() obj_son.show() # 派生类中可以访问 公有静态字段
私有静态字段class C: __name = "公有静态字段" def func(self): print C.__name class D(C): def show(self): print C.__name C.__name # 类访问 ==> 错误 obj = C() obj.func() # 类内部可以访问 ==> 正确 obj_son = D() obj_son.show() # 派生类中可以访问 ==> 错误
普通字段
- 公有普通字段:对象可以访问;类内部可以访问;派生类中可以访问
- 私有普通字段:仅类内部可以访问;
ps:如果想要强制访问私有字段,可以通过 【对象._类名__私有字段明 】访问(如:obj._C__foo),不建议强制访问私有成员。。
公有字段class C: def __init__(self): self.foo = "公有字段" def func(self): print self.foo # 类内部访问 class D(C): def show(self): print self.foo # 派生类中访问 obj = C() obj.foo # 通过对象访问 obj.func() # 类内部访问 obj_son = D(); obj_son.show() # 派生类中访问 公有字段
私有字段class C: def __init__(self): self.__foo = "私有字段" def func(self): print self.foo # 类内部访问 class D(C): def show(self): print self.foo # 派生类中访问 obj = C() obj.__foo # 通过对象访问 ==> 错误 obj.func() # 类内部访问 ==> 正确 obj_son = D(); obj_son.show() # 派生类中访问 ==> 错误 私有字段方法、属性的访问于上述方式相似,即:私有成员只能在类内部使用
ps:非要访问私有属性的话,可以通过 对象._类__属性名
类中特殊成员
1、
2、
3、
4、
5、
6、
7、
8、
9、
10、