第八章:Python基础の面向对象(二)
本課主題
- 面向对象的多态
- 面向对象的成员
- 成员修饰符
- 特殊成员
- 面向对象其他应用
- 异常处理
- 设计模式与单例模式
面向对象的多态
- 指定参数类型只是多态的一种表现
- 另外一种是允许自己类型和自己的子类型(典型)
以下是一个多态的例子,创建一个 func 函数,这个函数接受一个参数,我在分别传入了 字符串、列表和数字类型,因为 Python 在定义参数的时候不需要提先定义参数类型,所以它允许传入任意类型的参数,然后把它打印出来。
>>> def func(x): ... print(x) ... >>> func(1) 1 >>> func([11,22,33]) [11, 22, 33] >>> func("janice") janice
对于其他语言,比如说 Scala,这门语言必须在传入一个参数的同时,先定义它的类型,在这里我定义了参数x是必须是数字类型 (Int),以下是Scala版本的例子
scala> def func(x:Int){ | print(x) | } func: (x: Int)Unit scala> func(1) #把一组数组传入 func( ) 便会报错 scala> val array = Array(1,2,3,4) array: Array[Int] = Array(1, 2, 3, 4) scala> func(array) <console>:14: error: type mismatch; found : Array[Int] required: Int func(array)
传入的参数类型也可以是 Int 的派生类:意思是它们之间可以存在继承关系,且允许传入指定参数类型本身和它的子类型假设一个是父类,C是子类
scala> class A { | | } defined class A scala> class B extends A{ | | } defined class B scala> class C extends B{ | | } defined class C scala> def func(c: C) { | | } func: (c: C)Unit scala> val a = new A() a: A = A@a37aefe scala> func(a) <console>:17: error: type mismatch; found : A required: C func(a)
scala> class A { | } defined class A scala> class B extends A{ | } defined class B scala> class C extends B{ | } defined class C scala> def func(c: A) { | } func: (c: A)Unit scala> val a = new A() a: A = A@7c8c9a05 scala> func(a) scala> val b = new B() b: B = B@388526fb scala> func(b) scala> val c = new C() c: C = C@e70f13a scala> func(c)
面向对象的成员
Python 字段有以下两种
- 普通字段
- 静态字段
首先来看看下面的代码,这个类包含了两个方法,一个字段。name 是字段 (instance variable),它是保存在对象里面。
class Foo: def __init__(self): self.name = 'janice' #这是字段(普通字段) def show(self): print(self.name)
有普通字段(instance variable),也会有静态字段(statics variable),静态字段是保存在類里面。
class Foo: CC = 123 #这是字段(静态字段) def __init__(self): self.name = 'janice' #这是字段(普通字段) def show(self): print(self.name)
每个类里都有一个对象指针,指向这个类,比如创建了两个对象,每个对象都有 name 字段,然后他们指向同一个类,类里都有 __init__( )方法、show( )方法和一个CC静态字段,多个对象共用了一个静态字段。
普通字段和静态字段的分別:
对于静态字段来说,只要一加载,就会在内存中创建,静态方法是由你来调用的,对象是用来封装数据的,如果要用来封装数据,就得创建对象。如果用不着对象就不用创建对象了,因为静态方法的调用不依赖于任何对象,这样就可以节省内存空间。创建静态方法其实也可以相当于创建普通Python函数一样。
再次說明:
- 普通字段是保存在对象里;静态字段是保存在類里。
- 我们去访问成员的时候,都必需根据通用的语言规则,普通字段是用对象去访问,静态字段是用类去访问。
- 静态字段是在代码加载的时候已经在内存中创建,但普通字段是在创建对象时才会在内存中创建。
Python方法有以下三种:
- 普通方法,是由对象来调用的,所以至少有一個 self,因为 self 是指对象本身
class Foo: CC = 123 # 这是字段(静态字段) def __init__(self): self.name = 'janice' # 这是字段(普通字段) def show(self): print(self.name) obj = Foo() obj.show() """ janice """
- 静态方法,是由类来调用,允许传入任意参数
class Foo: CC = 123 # 这是字段(静态字段) def __init__(self): self.name = 'janice' # 这是字段(普通字段) def show(self): print(self.name) @staticmethod def f1(a1,a2): # 静态方法 print(a1,a2) Foo.f1(111,2222) #静态方法
- 类方法,是由类来调用的,至少有一個 cls
class Foo: CC = 123 # 这是字段(静态字段) def __init__(self): self.name = 'janice' # 这是字段(普通字段) def show(self): print(self.name) @classmethod def f2(cls): print("This is classmethod..") Foo.f2() #类方法 """ This is class method.. """
静态方法和类方法的区别在于你在调用类方法时不需要传入参数,它会自动把类传进去,好像普通方法的 self 一样
class Foo: CC = 123 # 这是字段(静态字段) def __init__(self): self.name = 'janice' # 这是字段(普通字段) def show(self): print(self.name) @staticmethod def f1(cla, a1,a2): print("This is staticmethod..") print(cla, a1,a2) @classmethod def f2(cls): print("This is classmethod..") print(cls) Foo.f1("abc",111,2222) #静态方法 Foo.f2() #类方法 """ This is staticmethod.. abc 111 2222 This is classmethod.. <class '__main__.Foo'> """
补充说明:如果你想封装数据的话,就可以用普通方法,如果你的方法用不到对象,你就不需要创建对象了,还有一点就是静态方法也相当于 Python 的函数,这个函数的调用不依赖于任何一个对象,但也写在类里的意思表明这个方法跟这个类有关系。
再次說明:所有的方法都是属于类的,而不是属于对象本身。
面向对象的属性
Python 中属性有三大功能:
- getter 功能 - @property
getter 意思是通过方法来获取成员中的值。加了 @property 便把这个函数变成了一个 getter 方法,你可以不用在方法后面加上括号来执行它,此时,Python 會允許方法伪造成字段的访问形式來获取成员中的值. e.g. p.all_pager,下面是普通函数和加了@property 之后来执行方法的区别。
class Pager: def __init__(self,all_count): self.all_count = all_count def all_pager(self): a1, a2 = divmod(self.all_count, 10) if a2 == 0: return a1 else: return a1 + 1 p = Pager(101) ret = p.all_pager() print(ret) #11
class Pager: def __init__(self,all_count): self.all_count = all_count @property def all_pager(self): a1, a2 = divmod(self.all_count, 10) if a2 == 0: return a1 else: return a1 + 1 p = Pager(101) ret = p.all_pager print(ret) #11
什么时候会使用属性而不是使用普通函数呢,下面是一个例子,这里创建了一个Person类,类里面有2个字段:公开的name和私有的magic_number,因为magic_number是私有并不对外公开,所以一般对象根本没有任何方法能够获取这个私有字段的值,此时,开发程序的作者就可以使用Python的属性解放字段的获取功能,也不会担心字段会有被修改的机会。
class Person(): def __init__(self, name): self.name = name self.__magic_number = 123 @property def ask_for_magic_number(self): return self.__magic_number peter = Person("Peter") print(peter.name) # Peter # print(peter.__magic_number) #代码会报错,因为这个字段不对外公开 print(peter.ask_for_magic_number) # Peter # 123
如果没有加上 @property 装饰器的话,它返回的是一个对象指针
class Pager: def __init__(self,all_count): self.all_count = all_count def all_pager(self): a1, a2 = divmod(self.all_count, 10) if a2 == 0: return a1 else: return a1 + 1 p = Pager(101) ret = p.all_pager print(ret) """ <bound method Pager.all_pager of <__main__.Pager object at 0x102378940>> """
- setter 功能 - @method.setter
如果想调用 setter 方法,只需要加上一个 @method.setter 的装饰器,Python 就會允許方法伪造成字段的賦值形式 e.g. p.all_pager = 222
class Pager: def __init__(self,all_count): self.all_count = all_count @property def all_pager(self): a1, a2 = divmod(self.all_count, 10) if a2 == 0: return a1 else: return a1 + 1 @all_pager.setter def all_pager(self, value): print(value) p = Pager(101) ret = p.all_pager print(ret) p.all_pager = 222 #11 #222
- delete 功能 - @method.deleter
最后,它也可以伪造成字段的删除形式,你只需要加上一个 @method.deleter 的装饰器,e.g. del p.all_pager
class Pager: def __init__(self,all_count): self.all_count = all_count @property def all_pager(self): a1, a2 = divmod(self.all_count, 10) if a2 == 0: return a1 else: return a1 + 1 @all_pager.setter def all_pager(self, value): print("Setting the value: {}".format(value)) @all_pager.deleter def all_pager(self): print("Deleting all_pager") p = Pager(101) ret = p.all_pager print(ret) p.all_pager = 222 del p.all_pager
除了用装饰器来创建属性以外,还有一种表达属性的代码,也就是调用 property( )方法
obj = property(fget=f1, fset=f2, fdel=f3)
class Pager: def __init__(self,all_count): self.all_count = all_count def f1(self): return 123 def f2(self,value): pass def f3(self): pass foo = property(fget=f1, fset=f2, fdel=f3) p = Pager(101) ret = p.foo # getter方法,然后执行 f1方法 print(ret) p.foo = 111 # setter方法,然后执行 f2方法 del p.foo # deleter方法,然后执行 f3方法
>>> class Rectangle(object): ... def __init__(self): ... self.width = 0 ... self.length = 0 ... ... def setSize(self,size): ... self.width, self.length = size ... ... def getSize(self): ... return self.width, self.length ... ... size = property(getSize, setSize) ... >>> r = Rectangle() >>> r.width = 3 >>> r.length = 4 >>> print(r.size) (3, 4) >>> r.size = 30, 40 >>> print(r.width) 30 >>> print(r.length) 40
这是Django的的源码例子,也有用到了property( )方法
总结,属性的诞生就是为了方便开发人员在写代码的时候,通过方法来对字段进行获取,赋值和删除的功能,而不是直接给你对这个私有字段进行修改。
成员修饰符
在Python 有以下两个成员修饰符
- 公共,它是在类的外部进行调用
class Foo: def __init__(self,name, ): self.name = name # 这是公共成员 # 通过 f1() 来访问公共成员 def f1(self): print(self.name) obj = Foo('janice') obj.f1() #通过调用公共方法来访问公共成员 obj.name #直接访问公共成员
- 私有,只能类自己本身成员内部访问,外部都不能访问。
class Foo: def __init__(self,name, ): self.__name = name # 这个是私有成员 # 通过 f1() 来访问私有成员 def f1(self): print(self.__name) obj = Foo('janice') obj.f1() #通过调用公有方法来访问私有成员 #obj.name 不能直接访问私有成员,这会报错!!!
如果想在外部访问私有字段,这会因为访问权限不够,导致报错
class Foo: def __init__(self,name, ): self.__name = name # 通过 f1() 来访文字段 def f1(self): print(self.__name) obj = Foo('janice') print(obj.name) # 会报错,因为字段变成私有字段,不对外公开访问 """ Traceback (most recent call last): File "/s13/Day8/ex/s11.py", line 16, in <module> print(obj.name) AttributeError: 'Foo' object has no attribute 'name' """
在父子继承关系中也不能访问私有字段
class Foo: def __init__(self,name, ): self.__name = name # 通过 f1() 来访文字段 def f1(self): print(self.__name) class Bar(Foo): def f2(self): print(self.__name) obj = Bar('janice') obj.f2() """ Traceback (most recent call last): File "/s13/Day8/ex/s11.py", line 17, in <module> obj.f2() File "/s13/Day8/ex/s11.py", line 13, in f2 print(self.__name) AttributeError: 'Bar' object has no attribute '_Bar__name' """
***但是在 Python里有一招绝密的招式,可以让你能够从外部访问私有字段/方法,这是一招绝密的召换术! ! !必须要在万不得而的时候才允許用,因为它并不符合典型的编程规则。
class Foo: def __init__(self,name, ): self.__name = name obj = Foo('janice') print(obj._Foo__name) # 访问召换术來了 #janice
特殊成员
这叫 operator overloading,当有特定函数被调用的时候,Python 会自动重载函数,比如说当两个对象相加时,会重载 __add__ 方法。
- __init__: 在类的对象创建好之后进行变量的初始化。Python 解析为 Foo( ).__init__(obj),__init__( )方法是实例方法。
class Foo: def __init__(self): print("在对象被创建的时候执行 __init__ 方法") obj = Foo() """ 在对象被创建的时候执行 __init__ 方法 """
- __new__: 从意义上这个才是 Python 的构造方法。它一般需要返回类的对象,当返回类的对象会自动调用 __init__方法进行初始化。如果没有返回类的对象,则__init__( ) 方法不会被调用。__new__( )方法是一个静态方法。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Author: Janice Cheng class Shape(object): def __init__(object): print("Shape __init__: I am a Shape") def draw(self): pass class Triangle(Shape): def __init__(self): print("Triangle __init__: I am a triangle", self) def draw(self): print("I am drawing triangle") class Rectangle(Shape): def __init__(self): print("Rectangle __init__: I am a rectangle", self) def draw(self): print("I am drawing rectangle") class Trapezoid(Shape): def __init__(self): print("Trapezoid __init__: I am a trapezoid", self) def draw(self): print("I am drawing trapezoid") class Diamond(Shape): def __init__(self): print("Diamond __init__: I am a diamond", self) def draw(self): print("I am drawing diamond")
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Author: Janice Cheng from others.design_pattern import Shape class ShapeFactory(object): shapes = { 'triangle':Shape.Triangle, 'rectangle':Shape.Rectangle, 'trapezoid':Shape.Trapezoid, 'diamond':Shape.Diamond } def __new__(cls, name): if name in ShapeFactory.shapes.keys(): print("ShapeFactory __new__: creating a new shape {} {}".format(name, cls)) return ShapeFactory.shapes[name]() else: print("creating a new shape {}".format(name)) return Shape.Shape() ShapeFactory('triangle').draw() ShapeFactory('rectangle').draw() ShapeFactory('trapezoid').draw() ShapeFactory('diamond').draw() ShapeFactory('circle').draw() """ ShapeFactory __new__: creating a new shape triangle <class '__main__.ShapeFactory'> Triangle __init__: I am a triangle <others.design_pattern.Shape.Triangle object at 0x1058dd4e0> I am drawing triangle ShapeFactory __new__: creating a new shape rectangle <class '__main__.ShapeFactory'> Rectangle __init__: I am a rectangle <others.design_pattern.Shape.Rectangle object at 0x1058dd4e0> I am drawing rectangle ShapeFactory __new__: creating a new shape trapezoid <class '__main__.ShapeFactory'> Trapezoid __init__: I am a trapezoid <others.design_pattern.Shape.Trapezoid object at 0x1058dd4e0> I am drawing trapezoid ShapeFactory __new__: creating a new shape diamond <class '__main__.ShapeFactory'> Diamond __init__: I am a diamond <others.design_pattern.Shape.Diamond object at 0x1058dd4e0> I am drawing diamond creating a new shape circle Shape __init__: I am a Shape """
- __del__: 在垃圾回收机制之前执行 __del__ 方法,也就是說在程序運行结束前的一個方法。
class Foo: def __init__(self): print("在对象被创建的时候执行 __init__ 方法") def __del__(self): print("在垃圾回收机制之前执行 __del__ 方法") obj = Foo() """ 在对象被创建的时候执行 __init__ 方法 在垃圾回收机制之前执行 __del__ 方法 """"
- __str__: 它是面向用户的,其目的是增加可读性,返回形式为用户友好性和可读性较强的字特串类型,在打印对象 print(a) 就会执行 __str__ 方法和执行str( ) 也就会执行 __str__ 方法。
class Foo: def __str__(self): return "在打印对象就会执行 __str__ 方法" obj = Foo() print(obj) """ 在打印对象就会执行 __str__ 方法 """
- __repr__: 它是面向解析器的,在解析器中输入对象 >>> a 就会执行 __repr__ 方法
class Decimal(object): """Floating point class for decimal arithmetic.""" def __repr__(self): """Represents the number as an instance of Decimal.""" # Invariant: eval(repr(d)) == d return "Decimal('%s')" % str(self) def __str__(self, eng=False, context=None): """Return string representation of the number in scientific notation. Captures all of the information in the underlying representation. """ sign = ['', '-'][self._sign] if self._is_special: if self._exp == 'F': return sign + 'Infinity' elif self._exp == 'n': return sign + 'NaN' + self._int else: # self._exp == 'N' return sign + 'sNaN' + self._int # number of digits of self._int to left of decimal point leftdigits = self._exp + len(self._int) # dotplace is number of digits of self._int to the left of the # decimal point in the mantissa of the output string (that is, # after adjusting the exponent) if self._exp <= 0 and leftdigits > -6: # no exponent required dotplace = leftdigits elif not eng: # usual scientific notation: 1 digit on left of the point dotplace = 1 elif self._int == '0': # engineering notation, zero dotplace = (leftdigits + 1) % 3 - 1 else: # engineering notation, nonzero dotplace = (leftdigits - 1) % 3 + 1 if dotplace <= 0: intpart = '0' fracpart = '.' + '0'*(-dotplace) + self._int elif dotplace >= len(self._int): intpart = self._int+'0'*(dotplace-len(self._int)) fracpart = '' else: intpart = self._int[:dotplace] fracpart = '.' + self._int[dotplace:] if leftdigits == dotplace: exp = '' else: if context is None: context = getcontext() exp = ['e', 'E'][context.capitals] + "%+d" % (leftdigits-dotplace) return sign + intpart + fracpart + exp
>>> class Foo: ... def __str__(self): ... return "在打印对象就会执行 __str__ 方法" ... def __repr__(self): ... return "在调用对象就会执行 __repr__ 方法" ... >>> obj = Foo() >>> print(obj) 在打印对象就会执行 __str__ 方法 >>> obj 在调用对象就会执行 __repr__ 方法 >>> from decimal import Decimal >>> a = Decimal(1.3) >>> a Decimal('1.3000000000000000444089209850062616169452667236328125') >>> print(a) 1.3000000000000000444089209850062616169452667236328125
- __dict__: 获取对象中封装的所有数据或者是类中的所有方法
class Foo: def __init__(self, name, age): self.name = name self.age = age def show(self, name): print("Hello my name is ", self.name) obj = Foo('Janice',22) print(obj.__dict__) print(Foo.__dict__) """ {'age': 22, 'name': 'Janice'} {'__doc__': None, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, 'show': <function Foo.show at 0x10f90b400>, '__init__': <function Foo.__init__ at 0x10f90b378>, '__module__': '__main__'} """
- __add__: 只要执行 + 就会执行 __add___方法
class Foo: def __init__(self, name, age): self.name = name self.age = age def __add__(self, other): print("只要执行 + 就会执行 __add___方法") res = self.name + " - " + str(other.age) return res obj1 = Foo('Janice',22) obj2 = Foo('Beatrice',7) ret = obj1 + obj2 print(ret) """ 只要执行 + 就会执行 __add___方法 Janice - 7 """
- __call__: 如果執行 obj( ),它就会自动去执行__call___方法
class Foo: def __init__(self, name, age): self.name = name self.age = age def __call__(self, *args, **kwargs): return "在对象后面加上括号执行 __call__ 方法" obj = Foo('Janice',22) ret = obj() print(ret) """ 在对象后面加上括号执行 __call__ 方法 """
class Foo: def __init__(self, name, age): self.name = name self.age = age obj = Foo('Janice',22) ret = obj() print(ret) """ Traceback (most recent call last): File "s13/Day8/ex/test.py", line 8, in <module> ret = obj() TypeError: 'Foo' object is not callable """
- __getitem__: 如果執行 obj['a'] 或者是 obj[1:5],它就會自動去執行 __getitem__方法,去创建了一个字典对象,根据索引去取值和根据切片去取值,它们返回的类型是不一样的,通过obj['a']返回的是字符串,而通过 obj[1:3] 返回的是 slice 类型。
class Foo: def __init__(self, name, age): self.name = name self.age = age def __getitem__(self, item): print(type(item)) print("在对象后面加上中括号, obj['abc'] 执行 __getitem__ 方法!", item) obj = Foo('Janice',22) obj['abc'] """ <class 'str'> 在对象后面加上中括号, obj['abc'] 执行 __getitem__ 方法! abc """
class Foo: def __init__(self, name, age): self.name = name self.age = age def __getitem__(self, item): print(type(item)) print(item.start) print(item.stop) print(item.step) print("在对象后面加上中括号, obj['abc'] 执行 __getitem__ 方法!", item) obj = Foo('Janice',22) obj[1:10:2] """ <class 'slice'> 1 10 2 在对象后面加上中括号, obj['abc'] 执行 __getitem__ 方法! slice(1, 10, 2) """
- __setitem__: 如果執行 obj['a']=111,它就會自動去執行 __setitem__方法,进行赋值的功能。
class Foo: def __init__(self, name, age): self.name = name self.age = age def __setitem__(self, key, value): print("在对象后面加上中括号然后赋值, obj['abc'] = 111 执行 __setitem__ 方法", key,':',value) obj = Foo('Janice',22) obj['abc'] = 111 """ 在对象后面加上中括号然后赋值, obj['abc'] = 111 执行 __setitem__ 方法 abc : 111 """
- __delitem__: 如果執行 del obj['a'],它就會自動去執行 __delitem__方法,
class Foo: def __init__(self, name, age): self.name = name self.age = age def __delitem__(self, key): print("在对象前面加上 del, del obj['k1'] 执行 __delitem__ 方法", key) obj = Foo('Janice',22) del obj['abc'] """ 在对象前面加上 del, del obj['k1'] 执行 __delitem__ 方法 abc """
- __iter__: 当你想进行的循环的时候,它是接受一个可以被迭代的东西(迭代器),才会在里面取值。
class Foo: def __init__(self, name, age): self.name = name self.age = age obj = Foo('Janice',22) for i in obj: print(i) """ Traceback (most recent call last): File "s13/Day8/ex/test.py", line 10, in <module> for i in obj: TypeError: 'Foo' object is not utterable """
class Foo: def __init__(self, name, age): self.name = name self.age = age def __iter__(self): print("当你迭代一个对象的话,就会执行__iter__方法") # __iter__返回的是一个迭代器,所以要先执行iter( )方法 return iter([11,22,33,44]) obj = Foo('Janice',22) for i in obj: print(i) """ 当你迭代一个对象的话,就会执行__iter__方法 11 22 33 44 """
yield 是生成器,执行之后返回的是一个迭代器,就可以在里面取值了
class Foo: def __init__(self, name, age): self.name = name self.age = age def __iter__(self): yield 1 yield 2 obj = Foo('Janice',22) for i in obj: print(i) #1 #2
- __enter__: 进入运行时的上下文,这是上下文管理,创建对象时调用构造方法之后便调用 __enter__ 方法,返回 self 对象本身
- __exit__: 退出运行时的上下文,定义在块执行(或者是终止)之后上下文管理器应该做什么。创建对象时调用构造方法之后最后调用 __exit__ 方法
class Log: def __init__(self,filename): self.filename=filename self.f=None def logging(self,text): self.f.write(text+'\n') def __enter__(self): print("__enter__") self.f=open(self.filename,"a+") return self def __exit__(self, exc_type, exc_val, exc_tb): print("__exit__") self.f.close() # 创建Log对象的时候调用 __init__ 方法's1.txt'赋值给filename变量 # 在这个过程会打开一个文件 with Log('s1.txt') as logfile: print("Main") # 打印 "Main" logfile.logging("Test1") # 把 "Test1" 写入到在__enter__打开的文件中 logfile.logging("Test2") # 把 "Test2" 再次写入到在__enter__打开的文件中 # 完成后调用 __exit__ 方法把文件 fp.close()
- __slots__: 能够限制属性的定义,它主要的目的是用来优化内存。
- __len__: 对象可以调用 len(obj) 方法来计算内容的长度
class Foo: def __init__(self, name): self.name = name def __len__(self): # 加了这个方法,对象才可以调用len(f1)方法 return len(self.name) f1 = Foo('Janice') print(len(f1)) # 6
- __metaclass__:
class Foo: """ __doc__ 就会显示这里的注 """ # 构造方法 def __init__(self): """ 在对象创建的时候就会执行这个函数 """ print("在对象被创建的时候执行 __init__ 方法") # 析构方法 def __del__(self): """ 在垃圾回收机制之前会执行这个函数 """ print("在垃圾回收机制之前执行 __del__ 方法") def __call__(self, *args, **kwargs): """ 让对象后面加上括号,然后就可以执行个函数 :param args: :param kwargs: :return: """ print("在对象后面加上括号执行 __call__ 方法") def __str__(self): """ 1) 在打印对象就会执行 __str__ 方法 2) 执行str( ) 也就会执行 __str__ 方法 没有这个方法就会打印该对象的内存地址 :return: """ return "在打印对象就会执行 __str__ 方法" def __add__(self, other): return "只要执行 + 就会执行 __add___方法" def __getitem__(self, item): print("在对象后面加上中括号, obj['abc'] 执行 __getitem__ 方法!", item) def __setitem__(self, key, value): print("在对象后面加上中括号然后赋值, obj['k1'] = 111 执行 __setitem__ 方法", key,':',value) def __delitem__(self, key): print("在对象前面加上 del, del obj['k1'] 执行 __delitem__ 方法", key) def __iter__(self): print("当你迭代一个对象的话,就会执行__iter__方法") return iter([11, 22, 33, 44]) # print(p.__class__) #<class '__main__.Foo'> obj1 = Foo() obj2 = Foo() obj1() print(obj1) ret = obj1 + obj2 print(ret) obj1['k1'] = 111 del obj1['k1'] for i in obj1: print(i) """ 在对象被创建的时候执行 __init__ 方法 在对象被创建的时候执行 __init__ 方法 在对象后面加上括号执行 __call__ 方法 在打印对象就会执行 __str__ 方法 只要执行 + 就会执行 __add___方法 在对象后面加上中括号然后赋值, obj['k1'] = 111 执行 __setitem__ 方法 k1 : 111 在对象前面加上 del, del obj['k1'] 执行 __delitem__ 方法 k1 当你迭代一个对象的话,就会执行__iter__方法 11 22 33 44 在垃圾回收机制之前执行 __del__ 方法 在垃圾回收机制之前执行 __del__ 方法 """
面向对象其他应用
- isinstance: 查看第一个参数是不是由第二个参数(类)创建的
class Foo: pass class Bar(Foo): pass obj = Foo() ret = isinstance(obj,Bar) print(ret) """ False """
- issubclass: 查看第一个参数是不是第二个参数的子类
class Bar: pass class Foo(Bar): pass obj = Foo() ret1 = issubclass(Foo,Bar) # 查看第一个参数是不是第二个参数的子类 print(ret1) ret2 = issubclass(Bar,Foo) # 查看第一个参数是不是第二个参数的子类 print(ret2)
- super( ): 主动执行父类的方法
>>> class C1: ... def f1(self): ... print('c1.f1') ... >>> class C2(C1): ... def f1(self): ... super(C2,self).f1() ... print('c2.f1') ... >>> obj=C2() >>> obj.f1() c1.f1 c2.f1
class Person: def __init__(self): self.height = 160 def about(self, name): print('{} is about {}'.format(name,self.height)) class Girls(Person): def __init__(self): super(Girls,self).__init__() self.hobbies = 'Yoga' def about(self, name): print('{} is a hot girl, she is about {}, and her hobbies is {}' .format(name,self.height,self.hobbies)) super(Girls,self).about(name) if __name__ == '__main__': jc = Girls() jc.about('Janice') """ Janice is a hot girl, she is about 160, and her hobbies is Yoga Janice is about 160 """
源码扩展
现在回顾一下怎么导入模块然后执行函数,这里有一个index.py和setting.py的程序,然後 index.py 中執行一個 execute( )函数,是打印setting中的字符串。
#!/usr/bin/env python # -*- coding: utf-8 -*- # Author: Janice Cheng ClassName = 'Foo'
#!/usr/bin/env python # -*- coding: utf-8 -*- # Author: Janice Cheng from setting import ClassName def execute(): print(ClassName) #这是一个字符串,等于'Foo' if __name__ == '__main__': execute() """ Foo """
现在假设想调用一个Web框架里Backend的一个方法,它是在一个Foo类里,叫 f1( ),此时可以调用的Python的反射功能,这样一执行就可以在不修改别人框架的情况下把类中的方法导入然后执行
#!/usr/bin/env python # -*- coding: utf-8 -*- # Author: Janice Cheng class Foo: def f1(self): print('Foo.f1')
#!/usr/bin/env python # -*- coding: utf-8 -*- # Author: Janice Cheng from setting import ClassName from backend import commons def execute(): print(ClassName) #这是一个字符串,等于'Foo' cls = getattr(commons,ClassName) obj = cls() obj.f1() if __name__ == '__main__': execute() """ Foo Foo.f1 """
如何在不需要修改别人源码的基础上在,在方法执行前打印一下 "Before",然后在方法执行完毕之后打印一下 "After",此时,可以运用类的继承概念来完成这个需求。
- 首先优化一下之前的程序,运用 __import__(Path, fromlist=True) 把模块以字符串参数的形式导入。
#!/usr/bin/env python # -*- coding: utf-8 -*- # Author: Janice Cheng from setting import ClassName from setting import Path def execute(): model = __import__(Path, fromlist=True) print(ClassName) #这是一个字符串,等于'Foo' cls = getattr(model,ClassName) obj = cls() obj.f1() if __name__ == '__main__': execute()
#!/usr/bin/env python # -*- coding: utf-8 -*- # Author: Janice Cheng class Foo: def f1(self): print('Foo.f1')
#!/usr/bin/env python # -*- coding: utf-8 -*- # Author: Janice Cheng Path = 'backend.commons' ClassName = 'Foo'
- 基于以上例子,我們有了一些启发。在lib.py中继承一下 Foo类,然后调用 super( )函数,如果现在需要调用MyFoo类中的 f1( )方法,此时就只需要修改配置文件就可以很轻松的把方法导入然后执行了。
#!/usr/bin/env python # -*- coding: utf-8 -*- # Author: Janice Cheng from setting import ClassName from setting import Path def execute(): model = __import__(Path, fromlist=True) print(ClassName) #这是一个字符串,等于'Foo' cls = getattr(model,ClassName) obj = cls() obj.f1() if __name__ == '__main__': execute()
#!/usr/bin/env python # -*- coding: utf-8 -*- # Author: Janice Cheng from backend.commons import Foo class MyFoo(Foo): def f1(self): print("before") super(MyFoo,self).f1() # 主动执行父类的 f1方法 print("after")
#!/usr/bin/env python # -*- coding: utf-8 -*- # Author: Janice Cheng class Foo: def f1(self): print('Foo.f1')
#!/usr/bin/env python # -*- coding: utf-8 -*- # Author: Janice Cheng Path = 'lib' ClassName = 'MyFoo'
总结:这样就可以在不修改源码的基础上执行源码的功能和我们自己新增的功能,这是一种源码功能扩展的开发技巧!!!!
有序字典
[更新中]
异常处理
错误是来至于语法或者是逻辑上的,语法错误指示软件的结构上有错误,导致不能被解释器解释或者是无法编译,这些错误必须在程序执行前纠正。异常是因为程序出现了错误而在正常控制流以外采取的行为,只要检测到错误并且意识到异常条件,解释器会触发一个异常,此时,Python 可以通过异常处理来捕获这些异常错误并采取相应的操作。一般异常处理的操作可以是忽略错误,或者是减轻问题影响后设法继续执行程序。常见的异常错误有以下几个:
- NameError:尝试访问一个还没申明的变量
#NameError: attempt to access an undeclared variable >>> foo Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'foo' is not defined
- ZeroDivisionError: 除数为零错误
>>> 1 / 0 Traceback (most recent call last): File "<stdin>", line 1, in <module> ZeroDivisionError: division by zero >>>
- SynatxError: Python 解释器语法错误
>>> def func() File "<stdin>", line 1 def func() ^ SyntaxError: invalid syntax
- IndexError: 请求的索引起出序列范围,在你尝试使用一个超出范围的值索引序时引发
>>> aList = [11,22,33,44,55] >>> aList[2] 33 >>> aList[5] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: list index out of range
- KeyError: 请求一个不存在的字典键时
>>> aDict = {'host': 'earth', 'port': 80} >>> print(aDict['host']) earth >>> print(aDict['server']) Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'server'
- IOError: 输入输出错误
- FileNotFoundError
>>> f = open("blah") Traceback (most recent call last): File "<stdin>", line 1, in <module> FileNotFoundError: [Errno 2] No such file or directory: 'blah'
- AttributeError: 尝试访问未知的对象属性
>>> class Foo(object): ... pass ... >>> foo = Foo() >>> foo.bar = 'spam' >>> foo.bar 'spam' >>> foo.bar2 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Foo' object has no attribute 'bar2'
- ValueError
>>> float('foo') Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: could not convert string to float: 'foo'
- TypeError
>>> float(['this is', 1, 'list']) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: float() argument must be a string or a number, not 'list'
- IndentationError
>>> for i in range(1,5): ... print(i) File "<stdin>", line 2 print(i) ^ IndentationError: expected an indented block
这是基本的异常处理语法。
try: pass except ValueError as ex: print(ex) except Exception as ex: print(ex) else: pass finally: pass
- 这是基本的异常处理语法
while True: num1 = input("num1: ") num2 = input("num2: ") try: num1 = int(num1) num2 = int(num2) except Exception as e: print("出现异常,信息如下:", e) """ num1: ALEX num2: ALEX 出现异常,信息如下: invalid literal for int() with base 10: 'ALEX' """
- 错误有很多种,如果你的程式需要基于不同的错误返回不同的功能,就需要指定错误类型。
while True: num1 = input("num1: ") num2 = input("num2: ") try: li = [] li[100] num1 = int(num1) num2 = int(num2) result = num1 + num2 except ValueError as e1: print("ValueError", e1) except IndexError as e2: print("IndexError", e2) except Exception as e: print("出现异常,信息如下:", e)
- 我们也可以主动报异常,就是调用 raise Exception("") 方法
try: raise Exception("主动错误一下") #self.message = '主动错误一下' print(1111) except ValueError as ex: print(ex) except Exception as ex: print(ex) # __str__, return self.message else: pass finally: pass
- 自定义的异常例子
class MyException(Exception): def __init__(self, msg): self.message = msg def __str__(self): return self.message try: raise MyException("Janice自定义的异常") except MyException as e: print(e) """ Janice自定义的异常 """
- 断言,如果条件成立就不报错,如果条件不成立就报错
assert 1==2 #如果条件判断 1==2 是 False 就会报错
-
class Foo(): def __init__(self,status=True): self.status = status def start(self): print("starting...") if __name__ == '__main__': f = Foo() #默认是 True assert f.status == True f.start() """ starting... """
class Foo(): def __init__(self,status=True): self.status = status def start(self): print("starting...") if __name__ == '__main__': f = Foo(False) assert f.status == True f.start() """ Traceback (most recent call last): File "/s13/Day8/ex/s17.py", line 12, in <module> assert f.status == True AssertionError """
总结:注意异常处理的粒度,应避免在 try 中放入过多的代码;谨慎使用单独的 except 语句来处理所有异常,最好能定位具体的异常;注意异常的顺序,在合适的层次处理异常
设计模式与单例模式
单例模式,总共有 23 种设计模式
什么是单例模式:一个例子,一个实例,也就是说它只有一个对象,它主要是用来创建单个实例。以下是一个数据库连接池的例子
作业
模拟人生:
- 定义三个人物,屌丝John,美女Liz,高富帅Peter。 John和Liz大学时是恋人,毕业工作后,Liz傍上了Peter,John伤心之余决定通过自己的努力追回Liz,多年后John通过努力变身高富帅,遇到被甩的Liz,Liz向John提出复合,John说……..(自由发挥)
- 定义人物信息:性别、年龄、工作、人种、国籍、特长,存款。房、车等信息,
- 用类实现上述故事中三者的关系和人物变化"
*****************The first scene****************** Welcome to the Secondlife Land. I want to show you a little story. The story has three people, they are Liz, Peter and John. [y=yes][s=skip] INTRODUCTION: Want to know more about them? >> y [b=back] Please pick one: 0.Liz; 1.John; 2.Peter; >> 0 My name is Liz. I am 22 years old. I am a Sales Person. I am a Chinese. I like dancing during my lesiure time. I am looking for my Mr.Right, someone who loves me and accept my everything. [b=back] Please pick one: 0.Liz; 1.John; 2.Peter; >> 1 My name is Peter. I am 24 years old. I am a Entrepreneur. I am a Candaian Born Chinese. I like playing sports during my lesiure time. I am amazing and excellent. I loves technology, I want to use technology to connect people all around the world. [b=back] Please pick one: 0.Liz; 1.John; 2.Peter; >> 2 My name is John. I am 25 years old. I am a Worker. I am a Chinese. I like listening to music during my lesiure time. I love Liz since we were at high-school [b=back] Please pick one: 0.Liz; 1.John; 2.Peter; >> b [y=yes][s=skip] INTRODUCTION: Want to know more about them? >> s Select your character: 0.Liz; 1.John; 2.Peter; >> 0 *****************The second scene***************** Imagine you are Liz...[High School Period] Select your partner: 1.John, 2.Peter >> 1 Liz is inlove with John [n=next stage] What do you want to do with John: 1.kissing [love index +10] 2.talking [love index +1] 3.arguing [love index -10] 4.getting marry [only can happen when love index = 100] 5.show current love index >>1 Liz is kissing John [n=next stage] What do you want to do with John: 1.kissing [love index +10] 2.talking [love index +1] 3.arguing [love index -10] 4.getting marry [only can happen when love index = 100] 5.show current love index >>2 What do you want to say? >> I love you Liz says: I love you [n=next stage] What do you want to do with John: 1.kissing [love index +10] 2.talking [love index +1] 3.arguing [love index -10] 4.getting marry [only can happen when love index = 100] 5.show current love index >>3 Liz is arguing with John. Love Index drop to 49 [n=next stage] What do you want to do with John: 1.kissing [love index +10] 2.talking [love index +1] 3.arguing [love index -10] 4.getting marry [only can happen when love index = 100] 5.show current love index >>4 John will not marry you [n=next stage] What do you want to do with John: 1.kissing [love index +10] 2.talking [love index +1] 3.arguing [love index -10] 4.getting marry [only can happen when love index = 100] 5.show current love index >>5 Your love index with John is 49 [n=next stage] What do you want to do with John: 1.kissing [love index +10] 2.talking [love index +1] 3.arguing [love index -10] 4.getting marry [only can happen when love index = 100] 5.show current love index >>n *****************The third scene****************** [After witj John for a few years...] Liz is arguing with John. Love Index drop to 39 Liz is arguing with John. Love Index drop to 29 Liz is arguing with John. Love Index drop to 19 Liz is arguing with John. Love Index drop to 9 Liz is arguing with John. Love Index drop to -1 Since you always argue with John, you have broken up with him *****************The fourth scene***************** [Meet Peter.....] You are very happy staying with Peter All Of a Sudden....... Liz is inlove with Peter [n=next stage] What do you want to do with Peter: 1.kissing [love index +10] 2.talking [love index +1] 3.arguing [love index -10] 4.getting marry [only can happen when love index = 100] 5.show current love index >>n *****************The fifth scene****************** [John work hard and become a Millionaire.....] *****************The sixth scene****************** [After with Peter for a few years...] Liz is arguing with Peter. Love Index drop to 40 Liz is arguing with Peter. Love Index drop to 30 Liz is arguing with Peter. Love Index drop to 20 Liz is arguing with Peter. Love Index drop to 10 Liz is arguing with Peter. Love Index drop to 0 Since you always argue with Peter, you have broken up with him ******************The last scene****************** Liz told John: I want to be with you again! John replied........ John says: I am getting married with my fiancée Process finished with exit code 0
參考資料
用断言的最佳时间: When to use assert