randip.py

我知道可能有更好,更简单的方法。
为了使自己熟悉Python,这只是一次学习练习。

需要一个参数(位置参数):
4(IPv4)或6(IPv6)。


用法:


./randip.py 4
61.104.170.242


./randip.py 6
4bfc:391d:3ec8:68ef:0ec8:529b:166d:2ece



代码:

#!/usr/bin/env python3

from sys import argv

from random import randint, choice
from string import hexdigits

def random_ip(v):

    if v == 4:
        octets = []
        for x in range(4):
            octets.append(str(randint(0,255)))
        return '.'.join(octets)

    elif v == 6:
        octets = []
        for x in range(8):
            octet = []
            for x in range(4):
                octet.append(str(choice(hexdigits.lower())))
            octets.append(''.join(octet))
        return ':'.join(octets)

    else:
       return 

def main(): 
    print(random_ip(int(argv[1])))

if __name__ == '__main__':
    main()


#1 楼

Python通常被描述为一种“包含电池”的语言,这也不例外。

有一个仅用于IP地址操作的模块,另一个模块用于生成随机数。放在一起,它们以一种更具可读性(IMO)的方式完全满足您的要求。

在此示例中,我假设变量v包含46

from random import getrandbits
from ipaddress import IPv4Address, IPv6Address

if v == 4:
    bits = getrandbits(32) # generates an integer with 32 random bits
    addr = IPv4Address(bits) # instances an IPv4Address object from those bits
    addr_str = str(addr) # get the IPv4Address object's string representation
elif v == 6:
    bits = getrandbits(128) # generates an integer with 128 random bits
    addr = IPv6Address(bits) # instances an IPv6Address object from those bits
    # .compressed contains the short version of the IPv6 address
    # str(addr) always returns the short address
    # .exploded is the opposite of this, always returning the full address with all-zero groups and so on
    addr_str = addr.compressed 

print(addr_str)


这里,addr_str将拥有完全随机的IPv4或IPv6地址。

您甚至可以从这样的子网中生成随机地址:

from random import getrandbits
from ipaddress import IPv4Network, IPv4Address

# network containing all addresses from 10.0.0.0 to 10.0.0.255
subnet = IPv4Network("10.0.0.0/24") 

# subnet.max_prefixlen contains 32 for IPv4 subnets and 128 for IPv6 subnets
# subnet.prefixlen is 24 in this case, so we'll generate only 8 random bits
bits = getrandbits(subnet.max_prefixlen - subnet.prefixlen)

# here, we combine the subnet and the random bits
# to get an IP address from the previously specified subnet
addr = IPv4Address(subnet.network_address + bits)
addr_str = str(addr)

print(addr_str)


此处,addr_str将始终包含IP地址,例如10.0.0.18410.0.0.42等。它与IPv6地址的工作方式相同,除了在这种情况下,您必须导入IPv6NetworkIPv6Address

评论


\ $ \ begingroup \ $
不错的解决方案-您是否考虑过在有条件之后将addr_str = addr.compressed推入?
\ $ \ endgroup \ $
–cmh
18年7月26日在15:07

\ $ \ begingroup \ $
@ tjt263是的,我想说这可能比您的方法更具教育意义,即使您真正想要的是生成随机IP地址的一种更好的方法。
\ $ \ endgroup \ $
– David Z
18年7月26日在17:46

\ $ \ begingroup \ $
在CodeReview中,用户并不是试图通过内置函数来尽可能高效地执行某些操作,而是希望正确地采用其方法。告诉某人从头开始编写IP生成器只是导入它并不能真正为社区服务。
\ $ \ endgroup \ $
–user1717828
18年7月26日在20:42

\ $ \ begingroup \ $
在发布它之前,我曾考虑在我的答案前加上诸如“这不是Code Review的正确答案,但是...”这样的内容,但我认为它不会像这样爆炸。我打算将其作为对将来的人们通过Google进行查找的有用资源,因为就在几个月前,我的处境与OP完全相同:想编写一个IP随机化器,却不知道Python可以为我做这件事。我在示例中添加了一些注释,希望它们使代码的工作方式更加明显。
\ $ \ endgroup \ $
– Peter W.
18年7月27日在6:58

\ $ \ begingroup \ $
@ user1717828不是。如果您愿意就CRSE提出反馈意见,那么您就天生就可以选择该问题的替代解决方案。如果您有目的地选择重新发明轮子(作为个人挑战或其他挑战),则有重新发明轮子的标签。
\ $ \ endgroup \ $
–丹尼尔(Daniel)
18年7月27日在23:12

#2 楼

以下是有关您的代码的一些想法。

检查命令行参数

如果在不使用命令行参数的情况下调用代码,则该代码将失败,因为它尝试使用argv[1]也没有我建议,如果用户输入无效或无参数,则打印“使用情况”消息会很好。

使用列表推导

列表推导是非常有用,非常Pythonic。精通他们真的很好。以下是使用它生成随机IPv4地址的方法:

'.'.join([str(randint(0,255)) for x in range(4)])


IPv6地址有点棘手,因为我们需要十六进制数字。

':'.join([hex(randint(2**16,2**17))[-4:] for x in range(8)])


之所以可行,是因为randint生成一个介于0x10000到0x20000之间的数字,然后我们选择最后四个十六进制数字。

评论


\ $ \ begingroup \ $
谢谢。最后一部分有点聪明。我有一个类似的想法,但无法实现。列表理解也很不错,表达起来有些棘手。
\ $ \ endgroup \ $
–声音
18年7月26日在13:37

\ $ \ begingroup \ $
您可以使用0x10000和0x20000代替2 ** 16和2 ** 17,以提高可读性。
\ $ \ endgroup \ $
–301_Moved_Permanently
18年7月26日在13:40

\ $ \ begingroup \ $
值得一提的是,您不需要列表理解就可以成为列表。它可以代替发电机。
\ $ \ endgroup \ $
– David Z
18年7月26日在17:48

\ $ \ begingroup \ $
对于IPv6,可以省略组中的前导零,因此您不需要该技巧,只需对range中的x做':'。join(hex(randrange(0x10000))[2:] 8))
\ $ \ endgroup \ $
–茉莉花
18年7月27日在12:38



\ $ \ begingroup \ $
@Robin:的确如此,但我想使其看起来一致。就个人而言,我更喜欢PeterW。的答案。十分优雅!
\ $ \ endgroup \ $
–爱德华
18年7月27日在12:40

#3 楼

对于不熟悉Python的人,您已经养成了很好的习惯。并不是每个人都使用函数或先尝试使用if __name__ == '__main__'后卫。

话虽如此,我认为提供2个函数而不是单个函数会更有意义:random_ipv4random_ipv6

您还可以将生成器表达式输入join。它们的处理速度都比[] + for + append更快,并且更易于阅读: :

def random_ipv4():
    return '.'.join(str(randint(0,255)) for _ in range(4))


def random_ipv6():
    return ':'.join(
            ''.join(choice(hexdigits).lower() for _ in range(4))
            for _ in range(8)
    )


评论


\ $ \ begingroup \ $
CMIIW,但是当内存不是问题时,列表理解不是比生成器理解要快一点吗?
\ $ \ endgroup \ $
–处置不良
18年7月26日在14:01

\ $ \ begingroup \ $
@Ludisposed在bytes.com/topic/python/answers/…上发现了旧的计时,但是对于联接来说,它并不重要,因为当前的实现立即将输入转换为列表。
\ $ \ endgroup \ $
–301_Moved_Permanently
18年7月26日在14:49

\ $ \ begingroup \ $
@Ludisposed我自己的测试表明,使用生成器表达式对100万次random_ipv4调用的响应时间为5.792979179001122秒,使用列表理解对5.526552320003248的响应为5.526552320003248。没有哪个比另一个更确切地说服了。由于我们不打算重复使用结果,因此生成器表达式可以很好地说明这一点。
\ $ \ endgroup \ $
–301_Moved_Permanently
18年7月26日在15:01



\ $ \ begingroup \ $
是的,没关系,它取决于数据。我同意您选择发电机的理由。
\ $ \ endgroup \ $
–处置不良
18年7月26日在15:11

#4 楼

检查无效的输出,然后重新生成。
您可能会产生保留用于特定目的的输出,例如回送(127.0.0.1::1)或广播(255.255.255.255ff02::1ff02::2)。积累有关此类地址的知识,如果发现您已经生成了一个,则将其替换。您可以递归执行此操作:
def make_address(v):
    address = random_ip(v)
    if is_reserved(address):
        return make_address(v)
    return address
   


评论


\ $ \ begingroup \ $
无需递归。 is_reserved(address):循环可能还需要一段时间。但是再说一次,如果生成有效IP的机会仅为千分之一(因此您有可能会定期达到堆栈大小限制),那么您可能做错了...
\ $ \ endgroup \ $
–地狱
18年7月26日在15:20

\ $ \ begingroup \ $
是的,我只是展示一种方式。 Python对我而言只是偶尔使用的一种语言,我不记得它是否可以保证消除尾部调用...
\ $ \ endgroup \ $
– Toby Speight
18年7月26日在16:13

\ $ \ begingroup \ $
然后,您可能需要其他选项来创建由映射IPv4地址和硬件MAC地址形成的IPv6地址。
\ $ \ endgroup \ $
– Toby Speight
18年7月26日在16:59



\ $ \ begingroup \ $
@TobySpeight没有,但是有一种方法可以通过使用装饰器来手动保证:stackoverflow.com/q/27417874/2415524
\ $ \ endgroup \ $
–mbomb007
18年7月27日在21:52

#5 楼

验证输入:目前,您的程序


如果不带参数调用则中止IndexError
如果使用非整数参数调用则中止ValueError
打印None(如果使用不是4
6的整数参数调用)。

缺少或无效的参数应显示一条有用的错误消息。
大多数Unix命令行工具都将显示此消息。到标准错误
,并在失败的情况下以非零退出状态终止。

将给定参数与
字符串"4""6"而不是将其转换为整数
(可能会失败)。


使用列表理解而不是在循环中附加到数组。
_用作如果不需要具体值,则使用迭代器变量。

例如,可以将“ IPv4”情况实现为

if v == 4:
    return '.'.join(str(randint(0,255)) for _ in range(4))


评论


\ $ \ begingroup \ $
为什么_?这是人们做的事情吗?我从没看过
\ $ \ endgroup \ $
–声音
18年7月26日在17:45

\ $ \ begingroup \ $
@ tjt263:按照惯例,它只是用作“丢弃变量”,例如,请参见stackoverflow.com/q/5893163/1187415
\ $ \ endgroup \ $
–马丁R
18年7月26日在18:27

#6 楼

由于您已经得到了一些答案,告诉您验证输入,因此这是使用argparse模块的一种方法:

import argparse

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('-v',
                        type=int,
                        choices=(4, 6),
                        default=4,
                        help="Whether to generate a random IPv4 or IPv6 address. Default: IPv4.")
    args = parser.parse_args()
    print(random_ip(args.v))


然后就可以使用它了在这样的命令行上:

./randip
./randip -v 4
./randip -v4
./randip -v 6


如果执行其他任何操作,则会打印一条有用的使用消息:

usage: [-h] [-v {4,6}]

optional arguments:
  -h, --help  show this help message and exit
  -v {4,6}    Whether to generate a random IPv4 or IPv6 address. Default:
              IPv4.