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

posted on 2017-03-02 18:01  simple_孙  阅读(351)  评论(0编辑  收藏  举报

导航