10 python魔法、静态、类方法,特性和迭代器
1、魔法方法
- 在Python中,有些名称的开头和结尾都是两个下划线。这些名称特殊意义,很大一部分都是魔法(特殊)方法的名称,因此绝不要在程序中创建这样的名称。
- 魔法(特殊)方法将在特定情况下(具体是哪种情况取决于方法的名称)被Python调用,而几乎不需要直接调用。
旧式类和新式类
- 如果你使用的不是Python3,即便你使用的是较新的Python2版本,有些功能(如特性和函数super)也不适用于旧式类。要让python2中的类是新式的(python2默认使用的是旧式类),要么在模块开头包含赋值语句__metaclass__ = type,要么直接或间接地继承内置类object或其他新式类。
- 在Python3中没有旧式类,因此无需显式地继承object或将__metaclass__设置为type。所有的类都将隐式地继承object。如果没有指定超类,将直接继承它,否则将间接地继承它。
- 新创建的类,必须是新式类。在Python2中非必要不要创建旧式类。
1.1、魔术方法__init__(构造函数)
- 构造函数在创建对象时自动被调用。
- 构造函数用于初始化新建对象(实例)的状态。
def __init__(self, [参数1, 参数2...]) 语句
1、无参构造函数
class FooBar: def __init__(self): self.somevar = 'hi' print(self.somevar, '我在__init__中') hh = FooBar() #在创建对象的时候自动调用构造函数 <<< hi 我在__init__中
2、有参构造函数
class FooBar: def __init__(self, semevar, name): self.somevar = semevar self.hh = name print('{} {},我在__init__中'.format(self.somevar, self.hh)) hh = FooBar('hello', 'hengha') #构造函数带参时,创建对象的时候就要传参 <<< hello hengha,我在__init__中
1.2、重写构造函数
- 要在子类中添加功能,一种方式是添加方法,另一种方式是重写超类的某些方法,以定制继承而来的行为。
- 对于大多数子类来说,除超类的初始化代码外,还需要有自己的初始化代码。
- 虽然所有方法的重写机制都相同,但与重写普通方法相比,重写构造函数时更有可能遇到一个特别的问题:重写构造函数时,必须调用父类的构造函数,否则可能无法正确地初始化对象。
- 重写构造函数的两种方法:调用未关联的超类构造函数,以及使用函数super。
1、不调用父类的构造函数,可能有异常
- 异常清楚地指出了问题出在什么地方:SongBird没有属性hungry。为何会这样呢?因为在SongBird中重写了构造函数,但新的构造函数没有包含任何初始化属性hungry的代码。
class Bird: #声明父类 def __init__(self, food): self.hungry = True self.food = food def eat(self): if self.hungry: print('Aaaah ...小鸟在吃{}'.format(self.food)) self.hungry = False else: print('No, thanks!') class SongBird(Bird): #声明子类 def __init__(self, sound): self.sound = sound def sing(self): print(self.sound) b = Bird('小米') b.eat() #结果是:Aaaah ...小鸟在吃小米 b.eat() #结果是:No, thanks! sb=SongBird('啾啾...') sb.sing() #结果是:啾啾... sb.eat() #调用sb.eat <<< #调用sb.eat的结果 #子类继承了父类的方法,但由于子类重写构造函数,因此没有调用父类的构造函数,也就没有初始化到hungry变量 Traceback (most recent call last): File "D:/test/python/day/test.py", line 24, in <module> sb.eat() File "D:/test/python/day/test.py", line 6, in eat if self.hungry: AttributeError: 'SongBird' object has no attribute 'hungry'
2、调用未关联的超类构造函数
- 实例调用方法时,方法的参数self将自动关联到实例,称为关联的方法。
- 如果通过类调用方法,就没有实例与其相关联。在这种情况下,可以随便设置参数self。这样的方法称为未关联的方法。
###声明父类 class 父类名: def __init__(self, 参数名a1, 参数名a2): self.val_a1 = 参数名a1 self.val_a2 = 参数名a2 声明子类 class 子类名(父类名): def __init__(self, 参数名a1, 参数名a2, 参数名b1, 参数名b2): 子类名.__init__(self, 参数名a1, 参数名a2) #注意,这里带self了 self.val_b1 = 参数名a1 self.val_b2 = 参数名a2 ###创建对象 对象名1 = 父类名(参数名a1, 参数名a2) 对象名2 = 子类名(参数名a1, 参数名a2, 参数名b1, 参数名b2)
示例:
- 在SongBird类中,只添加了一行代码Bird.__init__(self, food, sound)。
- 通过将这个未关联方法的self参数设置为当前实例,将使用超类的构造函数来初始化SongBird对象。这意味着将设置其属性hungry。
class Bird: def __init__(self, food): self.hungry = True self.food = food def eat(self): if self.hungry: print('Aaaah ...小鸟在吃{}'.format(self.food)) self.hungry = False else: print('No, thanks!') class SongBird(Bird): def __init__(self, food, sound): Bird.__init__(self, food) #父类名.__init(self,[参数1,参数2,...]),参数是父类的构造函数的参数 self.sound = sound def sing(self): print(self.sound) b = Bird('小米') b.eat() #结果是:Aaaah ...小鸟在吃小米 b.eat() #结果是:No, thanks! sb = SongBird('高粱', '啾啾...') sb.sing() #结果是:啾啾... sb.eat() #结果是:Aaaah ...小鸟在吃高粱 sb.eat() #No, thanks!
3、使用super函数
- super函数只能用在新式类中。
- 调用super函数时,将当前类和当前实例作为参数。其返回的对象调用的方法是超类(而不是当前类)的方法。
- 在Python3中调用super函数时,可不提供任何参数(通常也应该这样做)。
-
即便有多个超类,也只需调用super函数一次(条件是所有超类的构造函数也使用super函数)。
对于使用旧式类时处理起来很棘手的问题(如两个超类从同一个类派生而来),在使用新式类和super函数时将自动得到处理。
无需知道super函数的内部工作原理,但必须要知道的是,使用super函数比调用超类的未关联构造函数(或其他方法)要好得多。 - super函数返回的到底是什么呢?通常,你无需关心这个问题,只管假定它返回你所需的超类即可。实际上,它返回的是一个super对象,这个对象将负责为你执行方法解析。当你访问它的属性时,它将在所有的超类(以及超类的超类,等等)中查找,直到找到指定的属性或引发AttributeError异常。
###声明父类 class 父类名: def __init__(self, 参数名a1, 参数名a2): self.val_a1 = 参数名a1 self.val_a2 = 参数名a2 ###声明子类 class 子类名(父类名): def __init__(self, 参数名a1, 参数名a2, 参数名b1, 参数名b2): super.__init__(参数名a1, 参数名a2) #注意,这里没有self self.val_b1 = 参数名a1 self.val_b2 = 参数名a2 ###创建对象 对象名1 = 父类名(参数名a1, 参数名a2) 对象名2 = 子类名(参数名a1, 参数名a2, 参数名b1, 参数名b2)
示例:
- 在SongBird的构造函数中,可不使用Bird,而是使用super(SongBird,self)调用父类的__init__方法。在python3中super函数可以不带参数。
class Bird: def __init__(self, food): self.hungry = True self.food = food def eat(self): if self.hungry: print('Aaaah ...小鸟在吃{}'.format(self.food)) self.hungry = False else: print('No, thanks!') class SongBird(Bird): def __init__(self, food, sound): super().__init__(food) #子类重写构造函数,必须调用父类的构造函数 self.sound = sound def sing(self): print(self.sound) b = Bird('小米') b.eat() #结果是:Aaaah ...小鸟在吃小米 b.eat() #结果是:No, thanks! sb = SongBird('高粱', '啾啾...') sb.sing() #结果是:啾啾... sb.eat() #结果是:Aaaah ...小鸟在吃高粱 sb.eat() #No, thanks!
1.3、魔术方法__str__
- __str__()方法在使用print()和str()函数打印对象名时自动被调用。
- 必须在__str__方法中添加return, return后面内容就是打印对象名时的提示信息,且必须时字符串。
- 单纯打印对象名称,出来的是一个地址。地址对于开发者来书没有太大意义。如果想在打印对象名的时候能够给开发者更多一些信息量,就可以使用__str__方法。
class FooBar: def __init__(self): self.somevar = 'hi' def __str__(self): return '{},我在__str__中'.format(self.somevar) hh = FooBar() print(hh) #结果是:hi,我在__str__中 hh1 = str(hh) print(hh1) #结果是:hi,我在__str__中 hh2 = repr(hh) print(hh2) #结果是:<__main__.FooBar object at 0x0000027C7AB48CF8>
1.4、其他魔术方法
1、魔术方法__repr__
- __repr__()方法在使用repr()函数打印对象名时自动被调用。
- 当__repr__和__str__同时存在时,使用print()函数打印对象名,__str__方法生效。
class FooBar: def __init__(self): self.somevar = 'hi' def __repr__(self): return 'hhh' def __str__(self): return '{},我在__str__中'.format(self.somevar) hh = FooBar() print(hh) hh1 = repr(hh) print(hh1)
2、魔术方法__new__
- __new__方法在创建对象的时自动被调用。申请内存,开辟内存空间,并返回内存地址给类对象__main__(),然后加载__init__()进行初始化对象。
- 实例化对象是Object类底层实现的,其他类继承了Object的__new__才能够实现实例化对象。
- 创建对象时先触发__new__后才会触发__init__。
3、魔术方法__del__(析构方法)
- 当一块空间(对象)没有任何引用时自动执行__del__,回收内存。
###对象赋值 p = Person() p1 = p ###删除地址的引用 del p #删除p1对地址的引用 ###查看对地址的引用次数 import sys sys. getcefcount (p)
2、特性
- 在Python中,实际上有两种创建特性的机制
2.1、使用property函数创建特性
- 通过存取方法定义的属性通常称为特性(property)。
- 特性(property)以前是通过魔法方法处理的,现在通过property函数处理。
- 使用property函数创建特性。property其实并不是函数,而是一个类。
- property函数只能用于新式类。
- 新式类应使用特性而不是存取方法。
-
调用property函数时,可以不指定参数、指定一个参数、指定两个参数、指定三个参数或指定四个参数。
如果没有指定任何参数,创建的特性将既不可读也不可写。
如果只指定一个参数(获取方法),创建的特性将是只读的。
如果只指定两个参数(获取方法,设置方法),创建的特性可读可写。
第三个参数是可选的,指定用于删除属性的方法(这个方法不接受任何参数)。
第四个参数也是可选的,指定一个文档字符串。
这些参数分别名为fget、fset、fdel和doc。
如果你要创建一个只可写且带文档字符串的特性,可使用它们作为关键字参数来实现。
1、不使用property函数
class Rectangle: def __init__(self): self.width = 0 self.height = 0 def set_size(self, size): #设置属性的方法 self.width, self.height = size def get_size(self): #获取属性的方法 return self.width, self.height r = Rectangle() r.width = 10 r.height = 5 print(r.get_size()) #结果是:(10, 5) r.set_size((100, 50)) print(r.width) #结果是:100
2、使用property函数
- 在这个新版的Rectangle中,通过调用property函数并将存取方法作为参数(获取方法在前,设置方法在后)创建了一个特性,然后将名称size关联到这个特性。这样,你就能以同样的方式对待width、height和size,而无需关心它们是如何实现的。
class Rectangle: def __init__(self): self.width = 0 self.height = 0 def set_size(self, size): self.width, self.height = size def get_size(self): return self.width, self.height size = property(get_size, set_size) #size是特性,通过存取方法定义的属性 r = Rectangle() r.width = 10 r.height = 5 print(r.size) #结果是:(10, 5) r.size = (100, 50) print(r.width) #结果是:100
3、@property装饰器的使用
- 先使用“@property”装饰“get方法”,再用“@get方法名.setter”装饰“set方法”。
- “get方法”和“set方法”的名字是一样的
class Rectangle: def __init__(self): self.width = 0 self.height = 0 @property def size(self): #get方法 return self.width, self.height @size.setter def size(self, size): #set方法 self.width, self.height = size r = Rectangle() r.width = 10 r.height = 5 print(r.size) #结果是:(10, 5) r.size = (100, 50) print(r.width) #结果是:100
2.2、__getattr__、__setattr__等魔法方法
- 要在属性被访问时执行一段代码,必须使用下面的魔法方法(在旧式类中,只有后面三个)。。
- __getattribute__(self, name):在属性被访问时自动调用(只适用于新式类)。
- __getattr__(self, name):在属性被访问而对象没有这样的属性时自动调用。
- __setattr__(self, name, value):试图给属性赋值时自动调用。
- __delattr__(self, name):试图删除属性时自动调用。
- 编写__setattr__和__getattribute__方法时需要避开无限循环陷阱。
- __getattr__和__setattr__方法用途之一是在旧式类中实现特性(在旧式类中,函数property的行为可能不符合预期)。
示例:使用__getattr__、__setattr__方法实现特性
class Rectangle: def __init__(self): self.width = 0 self.height = 0 def __setattr__(self, name, value): if name == 'size': self.width, self.height = value else: self.__dict__[name] = value def __getattr__(self, name): if name == 'size': return self.width, self.height else: raise AttributeError()
- 对于这个代码示例,需要注意如下两点。
- 即便涉及的属性不是size,也将调用方法__setattr__。因此这个方法必须考虑如下两种情形:如果涉及的属性为size,就执行与以前一样的操作;否则就使用魔法属性__dict__。__dict__属性是一个字典,其中包含所有的实例属性。之所以使用它而不是执行常规属性赋值,是因为旨在避免再次调用__setattr__,进而导致无限循环。
- 仅当没有找到指定的属性时,才会调用方法__getattr__。这意味着如果指定的名称不是size,这个方法将引发AttributeError异常。这在要让类能够正确地支持hasattr和getattr等内置函数时很重要。如果指定的名称为size,就使用前一个实现中的表达式。
3、静态方法和类方法
- 静态方法和类方法分别包装在staticmethod和classmethod类的对象中。
class MyClass: def smeth(): print('This is a static method') smeth = staticmethod(smeth) #静态方法 def cmeth(cls): print('This is a class method of', cls) cmeth = classmethod(cmeth) #类方法
- 像这样手工包装和替换方法有点繁琐,可以使用装饰器。
class MyClass: @staticmethod #静态方法 def smeth(): print('This is a static method') @classmethod #类方法 def cmeth(cls): print('This is a class method of', cls) MyClass.smeth() MyClass.cmeth()
1、静态方法的特点和作用
- 静态方法的定义中没有参数self。
- 静态方法直接通过类调用。
- 静态方法只能访问类属性和类方法。
- 静态方法的作用,静态方法只能访问类属性和类方法,因此可以在创建对象之前完成一些动作(功能)
2、类方法的特点和作用
- 类方法中参数不是一个对象,而是类,通常被命名为cls。
- 类方法可以通过类调用,也可以通过对象直接调用,但参数cls将自动关联到类。
- 类方法中只可以使用类属性。
- 类方法中不可以使用普通方法
- 类方法的作用,类方法只能访问类属性和类方法,因此可以在创建对象之前完成一些动作(功能)。
3、静态方法和类方法的比较
- 不同之处:
- 装饰器不同
- 类方法有参数,静态方法没有参数
- 相同之处:
- 都只能访问类的属性和方法,无法访问对象的属性和方法。
- 都可以通过类名调用访问
- 都可以在创建对象之前使用,因为是不依赖于对象
4、普通方法与两者的区别
- 普通方法没有装饰器
- 普通方法永远都要依赖对象,因为每个普通方法都有self参数
- 只有创建了对象才可以调用普通方法,否则无法调用。
4、迭代器
- 迭代(iterate)重复多次,就像循环那样。
- 可迭代对象:可以使用for循环进行遍历的对象。
- 迭代器:可以被next()函数调用,并返回下一个值的对象。
- 魔法方法__iter__是迭代器协议的基础。
- __iter__方法返回一个迭代器(迭代器自己),它是包含__next__方法的对象,而调用这个方法时可不提供任何参数。当调用__next__方法时,迭代器应返回其下一个值。如果迭代器没有可供返回的值,应引发StopIteration异常。还可以使用内置的便利函数next,next(it)与it.__next__()等效。
- 在Python3以前的迭代器协议中,要求迭代器对象包含next方法而不是__next__。
- 可迭代对象:
- 迭代器(包含生成器)
- 序列(包含字符串、列表、元组)
- 字典
- 结论:
- 可迭代对象包含迭代器。(迭代器是可迭代的,但可迭代的不一定是迭代器。)
- 实现了方法__iter__的对象是可迭代的,而实现了方法__next__的对象是迭代器。
- 定义可迭代对象,必须实现__iter__方法;定义迭代器,必须实现__iter__和__next__方法。
1、判断一个对象是否是可迭代的
from collections import Iterable list1 = [1, 2, 3, 4, 5] str1 = 'hengha' int1 = 100 print(isinstance(list1, Iterable)) #结果是:True print(isinstance(str1, Iterable)) #结果是:True print(isinstance(int1, Iterable)) #结果是:False
2、使用类实现迭代器
- 注意到这个迭代器实现了方法__iter__,而这个方法返回迭代器本身。在很多情况下,都在另一个对象中实现返回迭代器的方法__iter__,并在for循环中使用这个对象。
- 推荐在迭代器中也实现方法__iter__(并像刚才那样让它返回self),这样迭代器就可直接用于for循环中。
class Fibs: def __init__(self): self.a = 0 self.b = 1 def __next__(self): self.a, self.b = self.b, self.a + self.b return self.a def __iter__(self): return self fibs = Fibs() print(fibs.__next__()) #结果是:1 print(fibs.__next__()) #结果是:1 print(next(fibs)) #结果是:2 print(next(fibs)) #结果是:3
3、将可迭代对象转换为迭代器
- 内置函数iter可以将可迭代对象转换为迭代器
list1 = [1, 2, 3, 4, 5] iter1 = iter(list1) print(next(iter1)) #结果是:1 print(next(iter1)) #结果是:2
4、从迭代器创建序列
- 使用构造函数list显式地将迭代器转换为列表。
class TestIterator: value = 0 def __next__(self): self.value += 1 if self.value > 10: raise StopIteration return self.value def __iter__(self): return self ti = TestIterator() print(list(ti)) #结果是:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
5、生成器
- 在Python中,包含yield语句的函数都被称为生成器(generator)。
- 生成器是一种使用普通函数语法定义的迭代器。
- 生成器由两个单独的部分组成:生成器的函数和生成器的迭代器。生成器的函数是由def语句定义的,其中包含yield。生成器的迭代器是这个函数返回的结果。
- 生成器被调用时不会执行函数体内的代码,而是返回一个迭代器。每次请求值时,都将执行生成器的代码,直到遇到yield或return。yield意味着应生成一个值,而return意味着生成器应停止执行(即不再生成值;仅当在生成器调用return时,才能不提供任何参数)。
- 生成器的行为与普通函数不同。生成器不是使用return返回一个值,而是可以生成多个值,每次一个。每次使用yield生成一个值后,函数都将冻结,即在此停止执行,等待被重新唤醒。被重新唤醒后,函数将从停止的地方开始继续执行。
- 内置函数next()和__next__方法,可以获取生成器的下一个值。
1、生成器推导(也叫生成器表达式)
- 生成器推导工作原理与列表推导相同,但使用的是圆括号,且不是创建一个列表(即不立即执行循环),而是返回一个生成器。
###列表推导 list1 = [x * x for x in range(10)] print(list1) #结果是:[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] print(type(list1)) #结果是:<class 'list'> ###生成器推到 list1 = (x * x for x in range(10)) print(list1) #结果是:<generator object <genexpr> at 0x000001C6AB88F570> print(type(list1)) #结果是:<class 'generator'> print(list1.__next__()) #结果是:0 print(list1.__next__()) #结果是:1 print(next(list1)) #结果是:4 print(next(list1)) #结果是:9
2、使用函数声明生成器
###函数 def func(): n = 0 while True: n += 1 print(n) func() #结果是:死循环 ###生成器 def func(): n = 0 while True: n += 1 yield n #yield的作用相当于,return n + 暂停 gen = func() #返回一个迭代器 print(gen) #结果是:<generator object func at 0x000001F25C02F570> print(gen.__next__()) #结果是:1 print(next(gen)) #结果是:2
3、在生成器中的return
- return可以在发生StopIteration异常时提供提示信息。
###斐波那契生成器 def fib(length): a, b = 0, 1 n = 0 while n < length: yield b #yield的作用相当于,return b + 暂停 a, b = b, a + b n += 1 return '没有更多的元素了' gen =fib(3) print(gen.__next__()) #结果是:1 print(gen.__next__()) #结果是:1 print(gen.__next__()) #结果是:2 print(gen.__next__()) #异常,StopIteration: 没有更多的元素了
4、生成器的send方法
- send()方法可以在调用生成器时向其传值。
- 仅当生成器被挂起(即遇到第一个yield)后,使用send(而不是next)才有意义。要在此之前向生成器提供信息,可使用生成器的函数的参数。
- 如果一定要在生成器刚启动时对其调用方法send,可向它传递参数None。
def gen(): i = 1 while i < 10: temp = yield i print('temp:', temp) for x in range(temp): print('---------------------->', x) print('***********************') i += 1 return '没有更多数据了' gen = gen() gen.send(None) gen.send(2) gen.send(4)
5、生成器的使用
- 生成器可用于协程,模拟多进程。(进程 > 线程 > 协程)
def task1(n): for i in range(1, n): print('正在搬第{}块砖!'.format(i)) yield None def task2(n): for i in range(1, n): print('正在听第{}首歌!'.format(i)) yield None t1 = task1(4) t2 = task2(4) while True: try: t1.__next__() t2.__next__() except: break
6、反射
- python中的反射功能是由四个内置函数提供:hasattr、getattr、setattr、delattr。这四个函数分别用于:检查对象中是否含有某成员、获取对象中的成员、设置对象中的成员、删除对象中成员。
- 对象、类、模块都可以使用反射。
class Foo(object): def __init__(self): self.name = 'hengha' def func(self): return 'func' obj = Foo() #检查是否含有成员 hasattr(obj, 'name') hasattr(obj, 'func') #获取成员 getattr(obj, 'name') getattr(obj, 'func') #设置成员 setattr(obj, 'age', 18) setattr(obj, 'show', lambda num: num + 1) #删除成员 delattr(obj, 'name') delattr(obj, 'func')
- 不使用obj.name获取其值
示例1:
class Foo(object): def __init__(self): self.name = 'hengha' def func(self): return 'func' obj = Foo() print obj.__dict__['name']
示例2:
class Foo(object): def __init__(self): self.name = 'hengha' def func(self): return 'func' obj = Foo() print getattr(obj, 'name')
7、单例模式
- 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。
1、使用__new__创建单例(推荐使用)
class Singleton: __instence = None #使用私有变量来判断该类是否已有实例,并存储实例的内存地址 def __new__(cls): if cls.__instence is None: cls.__instence = object.__new__(cls) return cls.__instence else: return cls.__instence s1 = Singleton() s2 = Singleton() print(s1) print(s2)
2、使用类方法来创建单例
class Singleton: __instence = None @classmethod def get_instance(cls): if cls.__instence: return cls.__instence else: cls.__instence = Singleton() return cls.__instence s1 = Singleton.get_instance() s2 = Singleton.get_instance() print(s1) print(s2)