python 中的内部 class 和内部内部 class 有什么区别?

What's the difference between an inner class and an inner inner class in python?

field 不应该在第 50 行未定义吗?据我了解,内部嵌套 类 对外部 类 不可见,因为我 运行 进入第 65 行......看起来有点不一致,我希望有一个更好地理解外部嵌套 类 可见性以避免将来出现任何混淆。

"""######################################### Imports ############################################"""
import turtle
import time
import functools
"""######################################### Key press ##########################################"""

def key_space():  # Resets Level
    field.setLevel()

def key_p():      # Skips Level
    field.nextLevel()

def key_k():      # For making levels
    print([(b.x,b.y) for b in field.getBoxes()])
    
"""######################################## Interface(s) ########################################"""

def stamp(self):  # Interface for drawing polygons.
    turtle.goto(self.canvasX+field.offset[0],self.canvasY+field.offset[1])
    turtle.shape(self.shape)
    turtle.color(self.color)
    turtle.stamp()

def frameBoiler(func): #
    @functools.wraps(func)
    def wrapper_frame_boiler(*args,**kwargs):
        turtle.clear()
        #turtle.getscreen().bgpic('Untitled.png')
        r = func(*args, **kwargs)
        turtle.update()
        time.sleep(0.05)
        return r
    return wrapper_frame_boiler

"""######################################### Game Logic #########################################"""

class field:      # Field is an abstract class which facilitates the game Mechanics
    def place(o): # Place translates an objects board position to a pixel or canvas position
        o.canvasX = (o.x-31)*field.spaceSize # 31 beacuse it's half of the magic number...
        o.canvasY = (o.y-31)*field.spaceSize
        return(o) # This method should be a void //TODO refactor...
    class square:
        padding = 2
        color = "#078900"
        shape = "sq" # "sq" is the name of the stamp polygon geometry.
        #//TODO refactor: eliminate redundant literals. see def turtleSetup
        def __init__(self,x,y):
            self.x = x
            self.y = y
            field.place(self)
        def draw(self):
            stamp(self)
    class level:  # Levels are what make the game fun.
        class mando: # These are the spots you have to populate
            thickness = 2 # 
            trim = 1
            color = "#ee0000"
            shape = "bx" # "bx" is the name of the stamp polygon geometry. 
            #//TODO refactor: eliminate redundant literals. see def turtleSetup
            def draw(self): # each mando needs to be visable...
                stamp(self) # line: 15: `def stamp(self)`
            def __init__(self,x,y): # make a mandatory space
                self.x = x
                self.y = y
                self.canvasX = 0 # field isn't visible from this deeply nested class
                self.canvasY = 0 # so these are "unplaced"
        def __init__(self,text,spots): # make a level
            self.text = text
            self.mandos = []
            self.complete = False
            for spot in spots:
                self.mandos.append(self.mando(spot[0],spot[1]))
        def checkWin(self): # If the boxes on the board match the mandos you beat the level.
            oneLiner = set([(b.x,b.y) for b in field.getBoxes()])
            isTooLong = set([(m.x,m.y) for m in self.mandos])
            self.complete = oneLiner == isTooLong
        def draw(self): # This draws the mandos and the level's acompanying text.
            for mando in self.mandos:
                mando.draw()
            if self.complete:
                field.nextLevel()
    spaces = [[None]*63 for k in range(0,63)] # 63 is the magic number, ralistically it could be 8
    levels =[level("letsGo",[(31,33),(31,29),(29,31),(33,31)]),
            level("Never\nEat\nShreaded\nWheat",[(27, 31), (28, 30), (28, 32), (29, 30), (29, 32),
            (30, 28), (30, 29), (30, 33), (30, 34), (31, 27), (31, 35), (32, 28), (32, 29),
            (32, 33), (32, 34), (33, 30), (33, 32), (34, 30), (34, 32), (35, 31)]),
            level("Try:\nPress Space",[(29, 31), (30, 30), (31, 31), (31, 33), (32, 29), (33, 32),
            (34, 31)]),
            level("Flex",[(28, 27), (28, 28), (28, 29), (29, 26), (29, 30), (29, 35), (29, 36),
            (30, 29), (30, 31), (30, 33), (30, 34), (30, 35), (30, 37), (31, 28), (31, 29),
            (31, 33), (31, 35), (31, 36), (32, 29), (32, 31), (32, 33), (32, 34), (32, 35),
            (32, 37), (33, 26), (33, 30), (33, 35), (33, 36), (34, 27), (34, 28), (34, 29)]),
            level("Blast Off",[(28, 28), (28, 29), (28, 31), (29, 27), (29, 31), (29, 32), (30, 28),
            (30, 29), (30, 31), (30, 32), (30, 33), (30, 34), (31, 33), (31, 35), (32, 28), 
            (32, 29), (32, 31), (32, 32), (32, 33), (32, 34), (33, 27), (33, 31), (33, 32),
            (34, 28), (34, 29), (34, 31)]),
            level("Space\nInvaders",[(27, 31), (28, 30), (28, 32), (29, 27), (29, 28), (29, 30),
            (29, 31), (30, 26), (30, 28), (30, 29), (30, 33), (31, 27), (31, 28), (31, 31),
            (31, 32), (32, 26), (32, 28), (32, 29), (32, 33), (33, 27), (33, 28), (33, 30),
            (33, 31), (34, 30), (34, 32), (35, 31)]),
            level("big oof",[(31,31),(32,31),(31,33)])]
    levelIndex = 0   # literally the number indicating what level you're on
    spaceSize = 40   # the number of pixels to a in gmae space.
    offset = [-80,0] # you can arbitrailly move the gmae around the screen...
    def hit(x,y):    # toggle the presence of a box in a space on the board/field.
        try:
            if field.spaces[x][y] is None:
                field.spaces[x][y] = field.place(field.square(x,y))
            else:
                field.spaces[x][y] = None
        except IndexError:
            pass
    def setLevel():  # clears the board puts a box in the middle and places the mandos.
        field.spaces = [[None]*63 for k in range(0,63)]
        field.hit(31,31)
        [field.place(mando) for mando in field.levels[field.levelIndex].mandos]
    def nextLevel(): # the first level is also the level after the last level.
        field.levelIndex += 1
        if field.levelIndex >= len(field.levels):
            field.levelIndex = 0
        field.setLevel()
    @frameBoiler
    def draw():      # this is the draw method for the 
        field.levels[field.levelIndex].draw()
        for box in field.getBoxes():
            box.draw()
        turtle.color("#bad4af")# // Todo figure out why the text causes a flicker.
        turtle.goto(field.levels[field.levelIndex].
        mandos[-1].canvasX,field.levels[field.levelIndex].mandos[-1].canvasY)
        turtle.write(field.levels[field.levelIndex].text,
                    font=('Courier', 33, 'italic'), align='left')
    def click(x,y):
        spacex = int((float(x)-field.offset[0])/field.spaceSize +31) # more magic numbers...
        spacey = int((float(y)-field.offset[1])/field.spaceSize +32) # change them I dare you.
        try:
            field.bop(spacex,spacey)
        except IndexError:
            pass
        field.levels[field.levelIndex].checkWin()
    def getBoxes(): # in reality field.spaces should just be a dictionary... // TODO
        return sum([[box for box in boxes if not box is None] for boxes in field.spaces],[])
    def bop(x,y):
        if field.spaces[x][y] is None:
            pass
        else:
            field.hit(x,y)
            field.hit(x+1,y)
            field.hit(x-1,y)
            field.hit(x,y+1)
            field.hit(x,y-1)

"""##############################################################################################"""
def turtleSetup():
    turtle.tracer(0,0)
    turtle.register_shape("sq", # This is a square that denotes the boxes... wait
                        ((field.square.padding,
                            field.square.padding),
                        (field.spaceSize-field.square.padding,
                            field.square.padding),
                        (field.spaceSize-field.square.padding,
                            field.spaceSize-field.square.padding),
                        (field.square.padding,
                            field.spaceSize-field.square.padding)))
    turtle.register_shape("bx", # this is a box that is used to denote the mandos... woops.
                        ((field.level.mando.trim,field.level.mando.trim),
                        (field.level.mando.thickness,
                            field.level.mando.thickness),
                        (field.spaceSize-field.level.mando.thickness,
                            field.level.mando.thickness),
                        (field.spaceSize-field.level.mando.thickness,
                            field.spaceSize-field.level.mando.thickness),
                        (field.level.mando.thickness,
                            field.spaceSize-field.level.mando.thickness),
                        (field.level.mando.thickness,
                            field.level.mando.thickness),
                        (field.level.mando.trim,
                            field.level.mando.trim),
                        (field.level.mando.trim,
                            field.spaceSize-field.level.mando.trim),
                        (field.spaceSize-field.level.mando.trim,
                            field.spaceSize-field.level.mando.trim),
                        (field.spaceSize-field.level.mando.trim,
                            field.level.mando.trim)))
    turtle.ht() # Hide Turtle so you don't get a indicator on the rendering mechanism
    turtle.pu() # Pen up so you don't see the path of the rendering mechanism.
    turtle.onkey(key_space, "space") # Register key events.
    turtle.onkey(key_k, "k")
    turtle.onkey(key_p, "q")
    turtle.getscreen().onclick(field.click) # register the click event
    turtle.listen() # this probably needs to be here for some reason, idk delete it & see for yaself

"""##############################################################################################"""

class main:
    def __init__(self):
        turtleSetup()
        field.setLevel()
        while None is None:
            field.draw()
main() # Look at how clean that main function is! 

https://gist.github.com/Krewn/073a8cf8ed32d0d78171d409d71c74f0?fbclid=IwAR1yb7jREw91wloquHVGEM_bd2whzxPaBJ8GxJR7CcdigLJGlkfmI2RVrg0

我写了一个非常简单的例子:

class A:
    class B:
        def __init__(self):
            print(A)
    class C:
        class D:
            def __init__(self):
                print(A)
A.B()
A.C.D()
A().C().D()

并且class A 可用在inner as in inner inner class.

通过在定义游戏字段后初始化关卡而不是在游戏字段定义中初始化关卡,我们能够避免未解析的名称错误。查看修改后的代码。

import turtle
import time
import functools
"""######################################### Key press ##########################################"""

def key_space():  # Resets Level
    field.setLevel()

def key_p():      # Skips Level
    field.nextLevel()

def key_k():      # For making levels
    print([(b.x,b.y) for b in field.getBoxes()])
    
"""######################################## Interface(s) ########################################"""

def stamp(self):  # Interface for drawing polygons.
    turtle.goto(self.canvasX+field.offset[0],self.canvasY+field.offset[1])
    turtle.shape(self.shape)
    turtle.color(self.color)
    turtle.stamp()

def frameBoiler(func): #
    @functools.wraps(func)
    def wrapper_frame_boiler(*args,**kwargs):
        turtle.clear()
        #turtle.getscreen().bgpic('Untitled.png')
        r = func(*args, **kwargs)
        turtle.update()
        time.sleep(0.05)
        return r
    return wrapper_frame_boiler

"""######################################### Game Logic #########################################"""

class field:      # Field is an abstract class which facilitates the game Mechanics
    def place(o): # Place translates an objects board position to a pixel or canvas position
        o.canvasX = (o.x-31)*field.spaceSize # 31 beacuse it's half of the magic number...
        o.canvasY = (o.y-31)*field.spaceSize
        return(o) # This method should be a void //TODO refactor...
    class square:
        padding = 2
        color = "#078900"
        shape = "sq" # "sq" is the name of the stamp polygon geometry.
        #//TODO refactor: eliminate redundant literals. see def turtleSetup
        def __init__(self,x,y):
            self.x = x
            self.y = y
            field.place(self)
        def draw(self):
            stamp(self)
    class level:  # Levels are what make the game fun.
        class mando: # These are the spots you have to populate
            thickness = 2 # 
            trim = 1
            color = "#ee0000"
            shape = "bx" # "bx" is the name of the stamp polygon geometry. 
            #//TODO refactor: eliminate redundant literals. see def turtleSetup
            def draw(self): # each mando needs to be visable...
                stamp(self) # line: 15: `def stamp(self)`
            def __init__(self,x,y): # make a mandatory space
                self.x = x
                self.y = y
                field.place(self)
        def __init__(self,text,spots): # make a level
            self.text = text
            self.mandos = []
            self.complete = False
            for spot in spots:
                self.mandos.append(self.mando(spot[0],spot[1]))
        def checkWin(self): # If the boxes on the board match the mandos you beat the level.
            oneLiner = set([(b.x,b.y) for b in field.getBoxes()])
            isTooLong = set([(m.x,m.y) for m in self.mandos])
            self.complete = oneLiner == isTooLong
        def draw(self): # This draws the mandos and the level's acompanying text.
            for mando in self.mandos:
                mando.draw()
            if self.complete:
                field.nextLevel()
    spaces = [[None]*63 for k in range(0,63)] # 63 is the magic number, ralistically it could be 8
    levelIndex = 0   # literally the number indicating what level you're on
    spaceSize = 40   # the number of pixels to a in gmae space.
    offset = [-80,0] # you can arbitrailly move the gmae around the screen...
    def hit(x,y):    # toggle the presence of a box in a space on the board/field.
        try:
            if field.spaces[x][y] is None:
                field.spaces[x][y] = field.place(field.square(x,y))
            else:
                field.spaces[x][y] = None
        except IndexError:
            pass
    def setLevel():  # clears the board puts a box in the middle and places the mandos.
        field.spaces = [[None]*63 for k in range(0,63)]
        field.hit(31,31)
        [field.place(mando) for mando in field.levels[field.levelIndex].mandos]
    def nextLevel(): # the first level is also the level after the last level.
        field.levelIndex += 1
        if field.levelIndex >= len(field.levels):
            field.levelIndex = 0
        field.setLevel()
    @frameBoiler
    def draw():      # this is the draw method for the 
        field.levels[field.levelIndex].draw()
        for box in field.getBoxes():
            box.draw()
        turtle.color("#bad4af")# // Todo figure out why the text causes a flicker.
        turtle.goto(field.levels[field.levelIndex].
        mandos[-1].canvasX,field.levels[field.levelIndex].mandos[-1].canvasY)
        turtle.write(field.levels[field.levelIndex].text,
                    font=('Courier', 33, 'italic'), align='left')
    def click(x,y):
        spacex = int((float(x)-field.offset[0])/field.spaceSize +31) # more magic numbers...
        spacey = int((float(y)-field.offset[1])/field.spaceSize +32) # change them I dare you.
        try:
            field.bop(spacex,spacey)
        except IndexError:
            pass
        field.levels[field.levelIndex].checkWin()
    def getBoxes(): # in reality field.spaces should just be a dictionary... // TODO
        return sum([[box for box in boxes if not box is None] for boxes in field.spaces],[])
    def bop(x,y):
        if field.spaces[x][y] is None:
            pass
        else:
            field.hit(x,y)
            field.hit(x+1,y)
            field.hit(x-1,y)
            field.hit(x,y+1)
            field.hit(x,y-1)


"""################################## Initialize Leveles #########################################"""

field.levels =[field.level("Space For Resets",[(31,33),(31,29),(29,31),(33,31)]),
            field.level("Never\nEat\nShreaded\nWheat",[(27, 31), (28, 30), (28, 32), (29, 30), (29, 32),
            (30, 28), (30, 29), (30, 33), (30, 34), (31, 27), (31, 35), (32, 28), (32, 29),
            (32, 33), (32, 34), (33, 30), (33, 32), (34, 30), (34, 32), (35, 31)]),
            field.level("Ok?",[(27, 31), (31, 27), (31, 35), (35, 31)]),
            field.level("Try:\nPress Space",[(29, 31), (30, 30), (31, 31), (31, 33), (32, 29), (33, 32),
            (34, 31)]),
            field.level("Flex",[(28, 27), (28, 28), (28, 29), (29, 26), (29, 30), (29, 35), (29, 36),
            (30, 29), (30, 31), (30, 33), (30, 34), (30, 35), (30, 37), (31, 28), (31, 29),
            (31, 33), (31, 35), (31, 36), (32, 29), (32, 31), (32, 33), (32, 34), (32, 35),
            (32, 37), (33, 26), (33, 30), (33, 35), (33, 36), (34, 27), (34, 28), (34, 29)]),
            field.level("Blast Off",[(28, 28), (28, 29), (28, 31), (29, 27), (29, 31), (29, 32), (30, 28),
            (30, 29), (30, 31), (30, 32), (30, 33), (30, 34), (31, 33), (31, 35), (32, 28), 
            (32, 29), (32, 31), (32, 32), (32, 33), (32, 34), (33, 27), (33, 31), (33, 32),
            (34, 28), (34, 29), (34, 31)]),
            field.level("Space\nInvaders",[(27, 31), (28, 30), (28, 32), (29, 27), (29, 28), (29, 30),
            (29, 31), (30, 26), (30, 28), (30, 29), (30, 33), (31, 27), (31, 28), (31, 31),
            (31, 32), (32, 26), (32, 28), (32, 29), (32, 33), (33, 27), (33, 28), (33, 30),
            (33, 31), (34, 30), (34, 32), (35, 31)]),
            field.level("big oof",[(31,31),(32,31),(31,33)])]

"""##############################################################################################"""
def turtleSetup():
    turtle.tracer(0,0)
    turtle.register_shape("sq", # This is a square that denotes the boxes... wait
                        ((field.square.padding,
                            field.square.padding),
                        (field.spaceSize-field.square.padding,
                            field.square.padding),
                        (field.spaceSize-field.square.padding,
                            field.spaceSize-field.square.padding),
                        (field.square.padding,
                            field.spaceSize-field.square.padding)))
    turtle.register_shape("bx", # this is a box that is used to denote the mandos... woops.
                        ((field.level.mando.trim,field.level.mando.trim),
                        (field.level.mando.thickness,
                            field.level.mando.thickness),
                        (field.spaceSize-field.level.mando.thickness,
                            field.level.mando.thickness),
                        (field.spaceSize-field.level.mando.thickness,
                            field.spaceSize-field.level.mando.thickness),
                        (field.level.mando.thickness,
                            field.spaceSize-field.level.mando.thickness),
                        (field.level.mando.thickness,
                            field.level.mando.thickness),
                        (field.level.mando.trim,
                            field.level.mando.trim),
                        (field.level.mando.trim,
                            field.spaceSize-field.level.mando.trim),
                        (field.spaceSize-field.level.mando.trim,
                            field.spaceSize-field.level.mando.trim),
                        (field.spaceSize-field.level.mando.trim,
                            field.level.mando.trim)))
    turtle.ht() # Hide Turtle so you don't get a indicator on the rendering mechanism
    turtle.pu() # Pen up so you don't see the path of the rendering mechanism.
    turtle.onkey(key_space, "space") # Register key events.
    turtle.onkey(key_k, "k")
    turtle.onkey(key_p, "q")
    turtle.getscreen().onclick(field.click) # register the click event
    turtle.listen() # this probably needs to be here for some reason, idk delete it & see for yaself

"""##############################################################################################"""

class main:
    def __init__(self):
        turtleSetup()
        field.setLevel()
        while None is None:
            field.draw()
main() # Look at how clean that main function is! ```

  

in inner class和inner inner class唯一的区别是inner class/parent class可以有多个inner class但是inner class主要避免 it.In python 内部 class 被称为嵌套 class.

这与class是外层还是内层无关。它只与命名范围有关。

def 将一个函数赋给一个名字

取下面的语句:

myfunction = lambda x: x

上面的语句定义了一个指向函数的名字myfunction。与这个比较:

def myfunction(x):
    return x

虽然这种形式有一些细微差别(例如 myfunction.__name__ 在第二种形式之后是 myfunction,但是在第一种形式之后是 <lambda>),第二个语句完全等同于第一条语句,它定义了一个恰好指向函数的名称 myfunction

class 将 class 分配给名称

Class 定义工作相同(尽管它们没有等效的 lambda):

class Outer:
    def myfunc(self):
        Outer

    class Inner:
        def myfunc(self):
            Outer

Outer.Inner().myfunc()  # No error

它的作用是定义一个(全局)名称 Outer,恰好指向 class。当上述代码片段的最后一行运行时,Outer 名称已定义。当 Outer.Inner.myfunc() 正在执行时,它除了访问名称 Outer.

之外什么都不做

事实上,你也可以这样写:

class A:
    def myfunc(self):
        B

class B:
    pass

A().myfunc()

执行此代码段的最后一行时,(全局)名称 AB 已定义;当 A.myfunc() 运行时,这些名称存在。

创建函数时,指向它的名称不存在

myfunction = lambda x: x

此赋值将表达式 lambda x: x 赋给名称 myfunction。首先计算表达式,然后构造函数,只有当表达式的结果(函数)准备就绪时,解释器才会创建 myfunction 名称并使其指向新创建的函数。

同样,

def myfunction(x):
    return x

同样的事情发生了。创建一个函数,当函数准备就绪时,创建名称 myfunction 并指向新创建的函数。

创建class时,指向它的名称不存在

class Outer:
    class Inner:
        my_class_attribute = Outer  # This will cause an error

虽然正在执行此代码段的最后一行,但仍在创建 class Outer。由于其创建尚未完成,因此 name Outer 尚未创建。只有在创建 class Outer 之后,解释器才会创建一个 name Outer 并使其成为指向新创建的class。由于在执行代码段的最后一行时尚不存在这样的名称,因此将抛出名称 Outer 未定义的错误。

如果你这样做,它会抛出同样的错误:

class A:
    my_class_attribute = A

但请注意,您可以这样做:

class A:
    pass


class A:
    this_points_to_the_previous_A = A