python-类
概述
class Document(): WELCOME_STR = 'Welcome! The context for this book is {}.' def __init__(self, title, author, context): print('init function called') self.title = title self.author = author self.__context = context # 类函数 @classmethod def create_empty_book(cls, title, author): return cls(title=title, author=author, context='nothing') # 成员函数 def get_context_length(self): return len(self.__context) # 静态函数 @staticmethod def get_welcome(context): return Document.WELCOME_STR.format(context) empty_book = Document.create_empty_book('What Every Man Thinks About Apart from Sex', 'Professor Sheridan Simove') print(empty_book.get_context_length()) print(empty_book.get_welcome('indeed nothing')) ########## 输出 ########## init function called 7 Welcome! The context for this book is indeed nothing.
1、一般用类自带的__init__函数完成类变量的初始化工作,并且类函数的第一个参数一般都是self,用来表示对象自身,类似于c++对象里面的this指针。并且定义了__init__函数的类就必须按照这个函数参数的形式来构造对象。当然也有特殊的函数,比如说__new__,它的第一个参数是cls,表示类本身,是因为这个函数的作用相当于是用来构造对象的,而不是初始化对象的。
2、不像c++,变量必须都要包括在类定义里面,每个类对象可以随时添加变量,当然,这个变量也只属于这个对象,其他对象引用是会出错的。
3、定义私有变量,是在正常名字前面加上双下划线,就成了私有的,不像c++是通过关键字private来定义的。并且也不是说完全不能访问,是因为解释器把它解释成了别的名字,例如Student类的__name私有变量,直接通过__name是不能访问的,但是用它解释后的名字_Student__name则是可以访问的。
4、对于带有前双下划线和后双下划綫的变量,这些变量可以在外部访问,但这些是特殊变量,自己定义的变量不要写成这种形式。
5、前面带有单下划线的变量,也可以在外部访问,但一般也把它们当做私有变量不要去访问它。
6、使用dir(对象名)函数可以得到这个对象所属类型的所有可用函数和变量
类中常量需要和函数并列地声明并赋值,例如这段代码中的 WELCOME_STR。一种很常规的做法,是用全大写来表示常量,因此我们可以在类中使用 self.WELCOME_STR ,或者在类外使用 Entity.WELCOME_STR ,来表达这个字符串。
提出了类函数、成员函数和静态函数三个概念。它们其实很好理解,前两者产生的影响是动态的,能够访问或者修改对象的属性;而静态函数则与类没有什么关联,最明显的特征便是,静态函数的第一个参数没有任何特殊性。类函数最常用的功能是实现不同的 init 构造函数,比如上文代码中,我们使用 create_empty_book 类函数,来创造新的书籍对象,其 context 一定为 'nothing'。这样的代码,就比你直接构造要清晰一些。
继承与派生
class A(): def __init__(self): print('enter A') print('leave A') class B(A): def __init__(self): print('enter B') super().__init__()
#A.__init(self) print('leave B') class C(A): def __init__(self): print('enter C') super().__init__() print('leave C') class D(B, C): def __init__(self): print('enter D') super().__init__() print('leave D') D()
- //输出 enter D enter B enter C enter A leave A leave C leave B leave D
- 必须在子类的__init__方法中先调用父类的__init()方法,多继承,如果多个父类的初始化参数不一致,不能用super,还是要逐个调用父类的初始化方法
- 如果是多继承,并且有共同的父类,这个父类也只会被初始化一次,例如上面的初始化顺序是DBCA的顺序。
抽象函数和抽象类
from abc import ABCMeta, abstractmethod class Entity(metaclass=ABCMeta): @abstractmethod def get_title(self): pass @abstractmethod def set_title(self, title): pass class Document(Entity): def get_title(self): return self.title def set_title(self, title): self.title = title document = Document() document.set_title('Harry Potter') print(document.get_title()) entity = Entity() ########## 输出 ########## Harry Potter --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-7-266b2aa47bad> in <module>() 21 print(document.get_title()) 22 ---> 23 entity = Entity() 24 entity.set_title('Test') TypeError: Can't instantiate abstract class Entity with abstract methods get_title, set_title
抽象函数定义在抽象类之中,子类必须重写该函数才能使用。相应的抽象函数,则是使用装饰器 @abstractmethod 来表示。
isinstance
判断某个变量是否是某个类型可用isinstance判断,会返回True或False,并且在判断类对象时,只要类型名是该对象的类或该类的基类,返回值都是True,用法为:
isinstance(变量名,类型名)
type
判断某个对象的类型可用type()判断,会直接返回类型名,用法为
type(对象名)
attr
有了上面判断对象的类型,也有判断判断对象中是否含有某属性的方法,常用的方法有以下三个:
hasattr(对象名,属性变量名) #判断对象中是否含有某变量,这个方法在构造单例时非常有用 getattr(对象名,属性变量,默认值) #获取指定属性的值,如果属性不存在,就返回默认值 setattr(对象名,属性变量,设定值) #设置指定属性的值
类自带的特殊方法
__slots__
1、类对象可以动态添加自己的属性,如果要限制变量的名字,就可以使用这个属性,将一个元组赋给它,这样就只能添加元组里每个元素的指定变量名
2、该属性只对当前类起作用,如果继承类没使用该属性,就不会继承该特性,使用了就会继承。
__str__()和__repr__()
类对象的输出处理函数,类似于c++中的cout重载
__iter__
如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的next()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
我们以斐波那契数列为例,写一个Fib类,可以作用于for循环:
class Fib(object): def __init__(self): self.a, self.b = 0, 1 # 初始化两个计数器a,b def __iter__(self): return self # 实例本身就是迭代对象,故返回自己 def next(self): self.a, self.b = self.b, self.a + self.b # 计算下一个值 if self.a > 100000: # 退出循环的条件 raise StopIteration(); return self.a # 返回下一个值
__getitem__
可以像列表那样用索引来去除第几个元素
__getattr__()
用于动态返回一个属性
__call__
可以是类对象像函数一样,通过对象名()的方式调用类的__call__方法,类似于一个语法糖,其作用和对象名.__call__()调用形式的作用是一样的。
python中可调用对象都有这个属性,可调用对象包括内置和自定义的函数,类对象这三种。
除此之外,该函数与hasattr()一起使用,可以判断对象中有没有指定的函数或者属性,因为只有函数才有__call__的属性.
通过与wrapper使用还是将该类定义成一个装饰器
http://c.biancheng.net/view/2380.html
type()
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。
比方说我们要定义一个Hello的class,就写一个hello.py模块:
class Hello(object): def hello(self, name='world'): print('Hello, %s.' % name)
当Python解释器载入hello模块时,就会依次执行该模块的所有语句,执行结果就是动态创建出一个Hello的class对象,测试如下:
>>> from hello import Hello >>> h = Hello() >>> h.hello() Hello, world. >>> print(type(Hello)) <type 'type'> >>> print(type(h)) <class 'hello.Hello'>
type()函数可以查看一个类型或变量的类型,Hello是一个class,它的类型就是type,而h是一个实例,它的类型就是class Hello。
我们说class的定义是运行时动态创建的,而创建class的方法就是使用type()函数。
type()函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过type()函数创建出Hello类,而无需通过class Hello(object)...的定义:
>>> def fn(self, name='world'): # 先定义函数 ... print('Hello, %s.' % name) ... >>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class >>> h = Hello() >>> h.hello() Hello, world. >>> print(type(Hello)) <type 'type'> >>> print(type(h)) <class '__main__.Hello'>要创建一个class对象,type()函数依次传入3个参数:
1. class的名称;
2. 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的
单元素写法;
3. class的方法名称与函数绑定,这里我们把函数fn 绑定到方法名hello上。
通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。
正常情况下,我们都用class Xxx...来定义类,但是,type()函数也允许我们动态创建出类来,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会非常复杂。
metaclass
YAMLObject 的一个超越变形能力,就是它的任意子类支持序列化和反序列化(serialization & deserialization)。比如说下面这段代码:
class Monster(yaml.YAMLObject): yaml_tag = u'!Monster' def __init__(self, name, hp, ac, attacks): self.name = name self.hp = hp self.ac = ac self.attacks = attacks def __repr__(self): return "%s(name=%r, hp=%r, ac=%r, attacks=%r)" % ( self.__class__.__name__, self.name, self.hp, self.ac, self.attacks) yaml.load(""" --- !Monster name: Cave spider hp: [2,6] # 2d6 ac: 16 attacks: [BITE, HURT] """) Monster(name='Cave spider', hp=[2, 6], ac=16, attacks=['BITE', 'HURT']) print yaml.dump(Monster( name='Cave lizard', hp=[3,6], ac=16, attacks=['BITE','HURT'])) # 输出 !Monster ac: 16 attacks: [BITE, HURT] hp: [3, 6] name: Cave lizard
你看,调用统一的 yaml.load(),就能把任意一个 yaml 序列载入成一个 Python Object;而调用统一的 yaml.dump(),就能把一个 YAMLObject 子类序列化。对于 load() 和 dump() 的使用者来说,他们完全不需要提前知道任何类型信息,这让超动态配置编程成了可能。
对于 YAML 的使用者,这一点也很方便,你只要简单地继承 yaml.YAMLObject,就能让你的 Python Object 具有序列化和逆序列化能力。是不是相比普通 Python 类,有一点“变态”,有一点“超越”?
YAML 的动态序列化 / 逆序列化是由 metaclass 实现的,我们这里只看 YAMLObject 的 load() 功能。简单来说,我们需要一个全局的注册器,让 YAML 知道,序列化文本中的 !Monster 需要载入成 Monster 这个 Python 类型。
一个很自然的想法就是,那我们建立一个全局变量叫 registry,把所有需要逆序列化的 YAMLObject,都注册进去。比如下面这样:
registry = {} def add_constructor(target_class): registry[target_class.yaml_tag] = target_class
然后,在 Monster 类定义后面加上下面这行代码:
add_constructor(Monster)
但这样的缺点也很明显,对于 YAML 的使用者来说,每一个 YAML 的可逆序列化的类 Foo 定义后,都需要加上一句话,add_constructor(Foo)。这无疑给开发者增加了麻烦,也更容易出错,毕竟开发者很容易忘了这一点。
那么,更优的实现方式是什么样呢?如果你看过 YAML 的源码,就会发现,正是 metaclass 解决了这个问题。
# Python 2/3 相同部分 class YAMLObjectMetaclass(type): def __init__(cls, name, bases, kwds): super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds) if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None: cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml) # 省略其余定义 # Python 3 class YAMLObject(metaclass=YAMLObjectMetaclass): yaml_loader = Loader # 省略其余定义 # Python 2 class YAMLObject(object): __metaclass__ = YAMLObjectMetaclass yaml_loader = Loader # 省略其余定义
YAML 应用 metaclass,拦截了所有 YAMLObject 子类的定义。也就说说,在你定义任何 YAMLObject 子类时,Python 会强行插入运行下面这段代码,把我们之前想要的add_constructor(Foo)给自动加上。
metaclass 能够拦截 Python 类的定义。它是怎么做到的?要理解 metaclass 的底层原理,你需要深入理解 Python 类型模型。下面,我将分三点来说明。
第一,所有的 Python 的用户定义类,都是 type 这个类的实例。
# Python 3和Python 2类似 class MyClass: pass instance = MyClass() type(instance) # 输出 <class '__main__.C'> type(MyClass) # 输出 <class 'type'>
你可以看到,instance 是 MyClass 的实例,而 MyClass 不过是“上帝”type 的实例。
第二,用户自定义类,只不过是 type 类的__call__运算符重载。
简单来说,当你定义一个类时,写成下面这样时:
class MyClass: data = 1
Python 真正执行的是下面这段代码:
class = type(classname, superclasses, attributedict)
这里等号右边的type(classname, superclasses, attributedict),就是 type 的__call__运算符重载,它会进一步调用:
type.__new__(typeclass, classname, superclasses, attributedict) type.__init__(class, classname, superclasses, attributedict)
第三,metaclass 是 type 的子类,通过替换 type 的__call__运算符重载机制,“超越变形”正常的类。
一旦你把一个类型 MyClass 的 metaclass 设置成 MyMeta,MyClass 就不再由原生的 type 创建,而是会调用 MyMeta 的__call__运算符重载。
class = type(classname, superclasses, attributedict) # 变为了 class = MyMeta(classname, superclasses, attributedict)
验证调用顺序代码如下:
class Mymeta(type): def __init__(self, name, bases, dic): super().__init__(name, bases, dic) print('===>Mymeta.__init__') print(self.__name__) print(dic) print(self.yaml_tag) def __new__(cls, *args, **kwargs): print('===>Mymeta.__new__') print(cls.__name__) return type.__new__(cls, *args, **kwargs) def __call__(cls, *args, **kwargs): print('===>Mymeta.__call__') obj = cls.__new__(cls) cls.__init__(cls, *args, **kwargs) return obj class Foo(metaclass=Mymeta): yaml_tag = '!Foo' def __init__(self, name): print('Foo.__init__') self.name = name def __new__(cls, *args, **kwargs): print('Foo.__new__') return object.__new__(cls)
输出如下:
===>Mymeta.__new__ Mymeta ===>Mymeta.__init__ Foo {'__module__': '__main__', '__qualname__': 'Foo', 'yaml_tag': '!Foo', '__init__': <function Foo.__init__ at 0x105115e50>, '__new__': <function Foo.__new__ at 0x105115ee0>} !Foo ===>Mymeta.__call__ Foo.__new__ Foo.__init__
meatclass其实跟Java中的类加载机制比较像,可以通过反射修改类定义。
类的__new__和meatclass的区别
- 类的metaclass先执行,是在类定义的时候,只会调用一次;__new__是在每次类创建实例的时候,会调用多次
- 对于不创建实例,就能生效使用的需要使用metaclass,就像上面的yamlObject