一、isinstance 和 issubclass

isinstance(obj,cls)检查obj是否是类cls的对象,是则返回True

  1. class Foo(object):
  2. pass
  3.  
  4. obj = Foo()
  5. print(isinstance(obj,Foo))

issubclass(sub,super)检查sub类是否是supper 类的派生类

  1. class Foo(object):
  2. pass
  3.  
  4. class Bar(Foo):
  5. pass
  6.  
  7. print(issubclass(Bar,Foo))

二、反射

1、什么是反射

反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。

2、python面向对象中的反射:

通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)

四个可以实现自省的函数

下列方法适用于类和对象(一切皆对象,类本身也是一个对象)

  1. def hasattr(*args, **kwargs): # real signature unknown
  2. """
  3. Return whether the object has an attribute with the given name.
  4.  
  5. This is done by calling getattr(obj, name) and catching AttributeError.
  6. """
  7. pass

hasattr

  1. def getattr(object, name, default=None): # known special case of getattr
  2. """
  3. getattr(object, name[, default]) -> value
  4.  
  5. Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y.
  6. When a default argument is given, it is returned when the attribute doesn't
  7. exist; without it, an exception is raised in that case.
  8. """
  9. pass

getattr

  1. def setattr(x, y, v): # real signature unknown; restored from __doc__
  2. """
  3. Sets the named attribute on the given object to the specified value.
  4.  
  5. setattr(x, 'y', v) is equivalent to ``x.y = v''
  6. """
  7. pass

setattr

  1. def delattr(x, y): # real signature unknown; restored from __doc__
  2. """
  3. Deletes the named attribute from the given object.
  4.  
  5. delattr(x, 'y') is equivalent to ``del x.y''
  6. """
  7. pass

delattr

  1. class Foo:
  2. f = '类的静态变量'
  3. def __init__(self,name,age):
  4. self.name=name
  5. self.age=age
  6.  
  7. def say_hi(self):
  8. print('hi,%s'%self.name)
  9.  
  10. obj=Foo('egon',73)
  11.  
  12. #检测是否含有某属性
  13. print(hasattr(obj,'name'))
  14. print(hasattr(obj,'say_hi'))
  15.  
  16. #获取属性
  17. n=getattr(obj,'name')
  18. print(n)
  19. func=getattr(obj,'say_hi')
  20. func()
  21.  
  22. print(getattr(obj,'aaaaaaaa','不存在啊')) #报错
  23.  
  24. #设置属性
  25. setattr(obj,'sb',True)
  26. setattr(obj,'show_name',lambda self:self.name+'sb')
  27. print(obj.__dict__)
  28. print(obj.show_name(obj))
  29.  
  30. #删除属性
  31. delattr(obj,'age')
  32. delattr(obj,'show_name')
  33. delattr(obj,'show_name111')#不存在,则报错
  34.  
  35. print(obj.__dict__)

四个方法的使用演示

  1. class Foo(object):
  2.  
  3. staticField = "old boy"
  4.  
  5. def __init__(self):
  6. self.name = 'wupeiqi'
  7.  
  8. def func(self):
  9. return 'func'
  10.  
  11. @staticmethod
  12. def bar():
  13. return 'bar'
  14.  
  15. print getattr(Foo, 'staticField')
  16. print getattr(Foo, 'func')
  17. print getattr(Foo, 'bar')

类也是对象

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3.  
  4. import sys
  5.  
  6. def s1():
  7. print 's1'
  8.  
  9. def s2():
  10. print 's2'
  11.  
  12. this_module = sys.modules[__name__]
  13.  
  14. hasattr(this_module, 's1')
  15. getattr(this_module, 's2')

反射当前模块成员

导入其他模块,利用反射查找该模块是否存在某个方法

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3.  
  4. def test():
  5. print('from the test')
  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3.  
  4. """
  5. 程序目录:
  6. module_test.py
  7. index.py
  8.  
  9. 当前文件:
  10. index.py
  11. """
  12.  
  13. import module_test as obj
  14.  
  15. #obj.test()
  16.  
  17. print(hasattr(obj,'test'))
  18.  
  19. getattr(obj,'test')()

类名 反射 静态属性

对象名 反射 对象属性和方

模块 反射 模块中的名字

反射 自己所在文件中的名字

x.y 这样的形式都可以用反射

  1. 'aaa'.startswith('a')
  2. print('aaa'.startswith)
  3.  
  4. ret = getattr('aaa','startswith') #把startswith转换为字符串来处理,就是可以通过字符串来拿打一个值
  5. print(ret)
  1. class Person:
  2. role = 'Person'
  3. def __init__(self,name):
  4. self.name =name
  5. def eat(self):print('eating')
  6. def drink(self):print('drinking')
  7. def play(self):print('playing')
  8. def sleep(self):print('sleeping')
  9.  
  10. alex = Person('alex')
  11.  
  12. print(getattr(alex,'name')) #反射 对象属性和方法
  13. print(getattr(Person,'role')) #反射 静态属性
  14.  
  15. while True:
  16. inp = input("请输入:")
  17. if hasattr(alex,inp): #判断alex,inp是否有内容
  18. getattr(alex,inp)()
  1. 首先 getattr获取一个名字,如果在这个对象的命名空间中没有这个名字会报错
    getattr的反射好伴侣hasatter 一般同时出现
    如果使用getattr获取一个方法,那么只能拿到这个方法的内存地址 加上括号执行

模块反射

  1. #模块 mymodule.py内容
  2. # 模块就是一个py文件
  3. # 所谓的模块导入 就是执行了这个文件而已
  4. money = 100
  5. def func1():
  6. print('func1')
  7.  
  8. def func2():
  9. print('func2')
  10.  
  11. class Manager:
  12. def eat(self):
  13. print('eating')

mymodule.py

  1. import mymodule
  2. import time
  3.  
  4. #原来方法
  5. print(mymodule.money)
  6. print(mymodule.func1())
  7. Manager = mymodule.Manager
  8. a = Manager()
  9. a.eat()
  10.  
  11. #反射
  12. print(getattr(mymodule,'money'))
  13. print(getattr(mymodule,'func1')())
  14. Manager = getattr(mymodule,'Manager')
  15. b = Manager()
  16. b.eat()

模块反射

反射自己所在文件中的名字

  1. import sys
  2. value = ''
  3.  
  4. print(sys.modules['__main__']) #拿到自己模块
  5. print(getattr(sys.modules['__main__'],'value')) #拿到123 反射自己模块名字

__str__和__repr__

改变对象的字符串显示__str__,__repr__

自定制格式化字符串__format__

  1. #_*_coding:utf-8_*_
  2.  
  3. format_dict={
  4. 'nat':'{obj.name}-{obj.addr}-{obj.type}',#学校名-学校地址-学校类型
  5. 'tna':'{obj.type}:{obj.name}:{obj.addr}',#学校类型:学校名:学校地址
  6. 'tan':'{obj.type}/{obj.addr}/{obj.name}',#学校类型/学校地址/学校名
  7. }
  8. class School:
  9. def __init__(self,name,addr,type):
  10. self.name=name
  11. self.addr=addr
  12. self.type=type
  13.  
  14. def __repr__(self):
  15. return 'School(%s,%s)' %(self.name,self.addr)
  16. def __str__(self):
  17. return '(%s,%s)' %(self.name,self.addr)
  18.  
  19. def __format__(self, format_spec):
  20. # if format_spec
  21. if not format_spec or format_spec not in format_dict:
  22. format_spec='nat'
  23. fmt=format_dict[format_spec]
  24. return fmt.format(obj=self)
  25.  
  26. s1=School('oldboy1','北京','私立')
  27. print('from repr: ',repr(s1))
  28. print('from str: ',str(s1))
  29. print(s1)
  30.  
  31. '''
  32. str函数或者print函数--->obj.__str__()
  33. repr或者交互式解释器--->obj.__repr__()
  34. 如果__str__没有被定义,那么就会使用__repr__来代替输出
  35. 注意:这俩方法的返回值必须是字符串,否则抛出异常
  36. '''
  37. print(format(s1,'nat'))
  38. print(format(s1,'tna'))
  39. print(format(s1,'tan'))
  40. print(format(s1,'asfdasdffd'))
  1. class B:
  2.  
  3. def __str__(self):
  4. return 'str : class B'
  5.  
  6. def __repr__(self):
  7. return 'repr : class B'
  8.  
  9. b=B()
  10. print('%s'%b)
  11. print('%r'%b)
  12.  
  13. %s和%r

%s和%r

__del__

析构方法,当对象在内存中被释放时,自动触发执行。

注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。

  1. class Foo:
  2.  
  3. def __del__(self):
  4. print('执行我啦')
  5.  
  6. f1=Foo()
  7. del f1
  8. print('------->')
  9.  
  10. #输出结果
  11. 执行我啦
  12. ------->

简单示范

item系列

__getitem__\__setitem__\__delitem__

  1. class Foo:
  2. def __init__(self,name):
  3. self.name=name
  4.  
  5. def __getitem__(self, item):
  6. print(self.__dict__[item])
  7.  
  8. def __setitem__(self, key, value):
  9. self.__dict__[key]=value
  10. def __delitem__(self, key):
  11. print('del obj[key]时,我执行')
  12. self.__dict__.pop(key)
  13. def __delattr__(self, item):
  14. print('del obj.key时,我执行')
  15. self.__dict__.pop(item)
  16.  
  17. f1=Foo('sb')
  18. f1['age']=18
  19. f1['age1']=19
  20. del f1.age1
  21. del f1['age']
  22. f1['name']='alex'
  23. print(f1.__dict__)

__new__

  1. class A:
  2. def __init__(self):
  3. self.x = 1
  4. print('in init function')
  5. def __new__(cls, *args, **kwargs):
  6. print('in new function')
  7. return object.__new__(A, *args, **kwargs)
  8.  
  9. a = A()
  10. print(a.x)
  1. class Singleton:
  2. def __new__(cls, *args, **kw):
  3. if not hasattr(cls, '_instance'):
  4. cls._instance = object.__new__(cls, *args, **kw)
  5. return cls._instance
  6.  
  7. one = Singleton()
  8. two = Singleton()
  9.  
  10. two.a = 3
  11. print(one.a)
  12. #
  13. # one和two完全相同,可以用id(), ==, is检测
  14. print(id(one))
  15. #
  16. print(id(two))
  17. #
  18. print(one == two)
  19. # True
  20. print(one is two)

单例模式

  1. #先执行new方法,object.__new__()
  2. #在执行init
  3.  
  4. # __new__ 构造方法 创建一个对象
  5. # __init__ 初始化方法
  6.  
  7. class Foo:
  8. def __new__(cls, *args, **kwargs):
  9. print('执行我啦')
  10. obj = object.__new__(cls)
  11. print(obj)
  12. return obj
  13. def __init__(self):
  14. print('',self)
  15.  
  16. Foo()
  17.  
  18. # 先执行new方法,object.new()
  19. # 再执行init
  20.  
  21. # Foo() --> python解释器接收到你的python代码
  22. # python解释器替你去做了很多操作
  23. # 包括 主动帮助你 调用 new方法 去创造一个对象 —— 开辟内存空间 —— python语言封装了开辟内存的工作
  24. # object的new方法里 —— 帮你创造了对象
  25. # 调用init用到的self参数 就是new帮你创造的对象
  1. 什么叫单例模式
  2. # 单例模式 : 某一类 只有一个实例
  3.  
  4. class Person:
  5. __isinstance = None
  6. def __new__(cls, *args, **kwargs):
  7. if not cls.__isinstance :
  8. obj = object.__new__(cls)
  9. cls.__isinstance = obj
  10. return cls.__isinstance
  11. def __init__(self,name):
  12. self.name = name
  13.  
  14. alex = Person('alex')
  15. alex.age = 18
  16. egon = Person('egon')
  17. print(egon.age)
  18. print(id(alex))
  19. print(id(egon))
  20. print(alex.name) #egon指向同一个内存地址,后覆盖前面的
  21. print(egon.name) #egon
  22.  
  23. # __new__生孩子
  24. # 类 : 生一个小孩__new__ 给这个小孩穿衣服 __init__
  25. # 单例模式下的类 : 只有一个小孩,内存空间还是原来的内存空间

单例模式

__call__

对象后面加括号,触发执行。

注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

  1. class Foo:
  2.  
  3. def __init__(self):
  4. pass
  5.  
  6. def __call__(self, *args, **kwargs):
  7.  
  8. print('__call__')
  9.  
  10. obj = Foo() # 执行 __init__
  11. obj() # 执行 __call__

__len__

  1. class A:
  2. def __init__(self):
  3. self.a = 1
  4. self.b = 2
  5.  
  6. def __len__(self):
  7. return len(self.__dict__)
  8. a = A()
  9. print(len(a))

__hash__

  1. class A:
  2. def __init__(self):
  3. self.a = 1
  4. self.b = 2
  5.  
  6. def __hash__(self):
  7. return hash(str(self.a)+str(self.b))
  8. a = A()
  9. print(hash(a))

__eq__

  1. class A:
  2. def __init__(self):
  3. self.a = 1
  4. self.b = 2
  5.  
  6. def __eq__(self,obj):
  7. if self.a == obj.a and self.b == obj.b:
  8. return True
  9. a = A()
  10. b = A()
  11. print(a == b)
  1. class FranchDeck:
  2. ranks = [str(n) for n in range(2,11)] + list('JQKA')
  3. suits = ['红心','方板','梅花','黑桃']
  4.  
  5. def __init__(self):
  6. self._cards = [Card(rank,suit) for rank in FranchDeck.ranks
  7. for suit in FranchDeck.suits]
  8.  
  9. def __len__(self):
  10. return len(self._cards)
  11.  
  12. def __getitem__(self, item):
  13. return self._cards[item]
  14.  
  15. deck = FranchDeck()
  16. print(deck[0])
  17. from random import choice
  18. print(choice(deck))
  19. print(choice(deck))

纸牌游戏

  1. class FranchDeck:
  2. ranks = [str(n) for n in range(2,11)] + list('JQKA')
  3. suits = ['红心','方板','梅花','黑桃']
  4.  
  5. def __init__(self):
  6. self._cards = [Card(rank,suit) for rank in FranchDeck.ranks
  7. for suit in FranchDeck.suits]
  8.  
  9. def __len__(self):
  10. return len(self._cards)
  11.  
  12. def __getitem__(self, item):
  13. return self._cards[item]
  14.  
  15. def __setitem__(self, key, value):
  16. self._cards[key] = value
  17.  
  18. deck = FranchDeck()
  19. print(deck[0])
  20. from random import choice
  21. print(choice(deck))
  22. print(choice(deck))
  23.  
  24. from random import shuffle
  25. shuffle(deck)
  26. print(deck[:5])
  27.  
  28. 纸牌游戏2

纸牌游戏2

  1. class Person:
  2. def __init__(self,name,age,sex):
  3. self.name = name
  4. self.age = age
  5. self.sex = sex
  6.  
  7. def __hash__(self):
  8. return hash(self.name+self.sex)
  9.  
  10. def __eq__(self, other):
  11. if self.name == other.name and self.sex == other.sex:return True
  12.  
  13. p_lst = []
  14. for i in range(84):
  15. p_lst.append(Person('egon',i,'male'))
  16.  
  17. print(p_lst)
  18. print(set(p_lst))
  19.  
  20. 一道面试题

面试题

  1. class Person:
  2. def __init__(self,name):
  3. self.name = name
  4. def __str__(self):
  5. return 'a object of Person named %s'%self.name
  6. # def __hash__(self): #
  7. # return 1231212
  8. # def __len__(self):
  9. # return 10
  10. a = Person('alex')
  11. b = Person('egon')
  12. # print(len(a))
  13. # print(hash(a))
  14. print(a)
  15. # print(b)
  16. # 类中的内置方法 很多都和 内置函数相关
  17.  
  18. # l = list([1,2,3]) # 实例化
  19. # print(l) # 之所以可以打印出结果,是因为def __str__(self): 定义了,需要定义str

详解python实现线程安全的单例模式

单例模式是一种常见的设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。

比如,服务器的配置信息写在一个文件中online.conf中,客户端通过一个 Config 的类来读取配置文件的内容。如果在程序运行期间,有很多地方都需要使用配置文件的内容,那么每个调用配置文件的地方都会创建 Config的实例,这就导致系统中存在多个Config 的实例对象,在配置文件内容很多的情况下,我们就浪费了大量的内存做了同样的事。事实上,对于Config类我们在程序运行期间时只需要一个实例对象即可,这时单例模式就是最好的选择。

  1. def Singleton(cls):
  2. instances = {}
  3.  
  4. def get_instance(*args, **kw):
  5. if cls not in instances:
  6. instances[cls] = cls(*args, **kw)
  7. return instances[cls]
  8.  
  9. return get_instance

代码也很简单,将类传入单例修饰器中,如果该类还未生成实例(instances中不存在该类),那么就生成一个新的实例返回,并记录在instances中。如果已经instances中已经存在该类,那么直接返回实例instances[cls]。

那么这段代码是完美的吗?答案是否定的,这段代码不是线程安全的。要实现线程安全需要配合锁的使用,只有占有锁的线程才能继续访问单例实例,看来我们需要再写一个修饰器来实现线程安全了,以下是完整的代码实现和简单的多线程测试用例。

  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. import threading
  4.  
  5. def synchronized(func):
  6. func.__lock__ = threading.Lock()
  7.  
  8. def synced_func(*args, **kws):
  9. with func.__lock__:
  10. return func(*args, **kws)
  11.  
  12. return synced_func
  13.  
  14. def Singleton(cls):
  15. instances = {}
  16.  
  17. @synchronized
  18. def get_instance(*args, **kw):
  19. if cls not in instances:
  20. instances[cls] = cls(*args, **kw)
  21. return instances[cls]
  22.  
  23. return get_instance
  24.  
  25. def worker():
  26. single_test = test()
  27. print "id----> %s" % id(single_test)
  28.  
  29. @Singleton
  30. class test():
  31. a = 1
  32. if __name__ == "__main__":
  33. task_list = []
  34. for one in range(30):
  35. t = threading.Thread(target=worker)
  36. task_list.append(t)
  37. for one in task_list:
  38. one.start()
  39. for one in task_list:
  40. one.join()

py---------面向对象进阶的更多相关文章

  1. Python全栈开发【面向对象进阶】

    Python全栈开发[面向对象进阶] 本节内容: isinstance(obj,cls)和issubclass(sub,super) 反射 __setattr__,__delattr__,__geta ...

  2. day26、面向对象进阶:多态、封装、反射

    一.多态 什么是多态: 类的继承有两层意义:1.改变 2.扩展 多态就是类的这两层意义的一个具体的实现机. 即:调用不同类实例化的对象,下的相同的方法,实现的过程不一样 python中的标准类型就是多 ...

  3. python基础——面向对象进阶

    python基础--面向对象进阶 1.isinstance(obj,cls)和issubclass(sub,super) isinstance(obj,cls)检查是否obj是否是类 cls 的对象 ...

  4. 周末班:Python基础之面向对象进阶

    面向对象进阶 类型判断 issubclass 首先,我们先看issubclass() 这个内置函数可以帮我们判断x类是否是y类型的子类. class Base: pass class Foo(Base ...

  5. Python面向对象进阶和socket网络编程-day08

    写在前面 上课第八天,打卡: 为什么坚持?想一想当初: 一.面向对象进阶 - 1.反射补充 - 通过字符串去操作一个对象的属性,称之为反射: - 示例1: class Chinese: def __i ...

  6. 铁乐学python_day23_面向对象进阶1_反射

    铁乐学python_day23_面向对象进阶1_反射 以下内容大部分摘自博客http://www.cnblogs.com/Eva-J/ isinstance()和issubclass() 两者的返回值 ...

  7. python_面向对象进阶(7)

    第1章 面向对象特性—继承(补充) 1.1 接口类.抽象类介绍 1.2 接口类 1.3 接口类应用过程 1.3.1 第一版:完成多种支付方式接口 1.3.2 第二版: 归一化设计,统一支付方式 1.3 ...

  8. ~~面向对象进阶——__name__=="__main__"~~

    进击のpython 面向对象进阶--__name__=="__main__" 前面我们在讲模块的时候,其实还有一个知识点没有很好的讲 那就是main和name 可能你们在看一些代码 ...

  9. Python面向对象进阶和socket网络编程

    写在前面 为什么坚持?想一想当初: 一.面向对象进阶 - 1.反射补充 - 通过字符串去操作一个对象的属性,称之为反射: - 示例1: class Chinese: def __init__(self ...

  10. Py-多态,封装,反射,描述符,包装标准类型,面向对象进阶

    多态: 对象可以通过他们共同的属性和动作来访问,而不需要考虑他们的类多态是继承的应用 class H2o: def __init__(self,temp): self.temp=temp def ht ...

随机推荐

  1. ROS Learning-014 learning_tf(编程) 坐标系变换(tf)广播员 (Python版)

    ROS Indigo learning_tf-01 坐标系变换(tf)广播员 (Python版) 我使用的虚拟机软件:VMware Workstation 11 使用的Ubuntu系统:Ubuntu ...

  2. 杭电acm 1038题

    本题比较简单,但是需要掌握几个小技巧,先上代码 /************************************* 杭电ACM 1038题,已AC ********************* ...

  3. Win10 VS2013 PCL1.8.1和依赖项VTK8.0.1, QHuall(2.15.2), FLANN1.9.1,Boost1.59.0,Zbil1.2.11和libPNG1.6.34编译安装

    编译和安装过程最好使用管理员权限去操作,避免不必要的错误. 一般而言为了区分Debug和Release库,添加输入变量 Name: CMAKE_DEBUG_POSTFIX Type: STRING V ...

  4. vue 之 指令系统介绍

    浏览目录 条件渲染 class 与style绑定 事件处理 指令系统介绍 所谓指令系统,大家可以联想咱们的cmd命令行工具,只要我输入一条正确的指令,系统就开始干活了. 在vue中,指令系统,设置一些 ...

  5. Charles常见问题汇总

    Charles是一款很好用的抓包修改工具,但是如果你不是很熟悉这个工具的话,肯定会遇到各种感觉很莫名其妙的状况,这里就来帮你一一解答下面再说说charles的一些其他常用的功能. 选择请求后,右键可以 ...

  6. 解决批处理命令执行完毕后自动关闭cmd窗口方法

    问题描述: 日常开发工作中,为了节省多余操作导致浪费时间,我们经常会自己建一些批处理脚本文件(xx.bat),文件中包含我们需要执行的命令,有时候我们希望执行完毕后看一下执行的结果,但是窗口执行完毕后 ...

  7. 安装windows系统备忘

    1.已写入系统镜像的U盘 2.激活工具(同时激活系统及office) 3.如果没有网口或网线,需要准备万能网卡驱动 4.office 5.360 6.输入法 7.微信 8.谷歌浏览器

  8. 依赖注入(DI)与控制反转(IOC)

    DI(依赖注入,Dependency Injection),和所谓的IoC(控制反转,Inversion of Control )是一个意思. DI是一种通过接口实现松耦合的设计模式.初学者可能会好奇 ...

  9. 前端的异步解决方案之Promise和Await-Async

    异步编程模式在前端开发过程中,显得越来越重要.从最开始的XHR到封装后的Ajax都在试图解决异步编程过程中的问题.随着ES6新标准的出来,处理异步数据流的解决方案又有了新的变化.Promise就是这其 ...

  10. [51nod1222] 最小公倍数计数(莫比乌斯反演)

    题面 传送门 题解 我此生可能注定要和反演过不去了--死都看不出来为啥它会突然繁衍反演起来啊-- 设\(f(n)=\sum_{i=1}^n\sum_{j=1}^n[{ij\over\gcd(i,j)} ...